public List<CloudtrailSNSNotification> parse(Message message) {

        LOG.debug("Parsing message.");
        try {
            LOG.debug("Reading message body {}.", message.getBody());
            final SQSMessage envelope = objectMapper.readValue(message.getBody(), SQSMessage.class);

            if (envelope.message == null) {
                LOG.warn("Message is empty. Processing of message has been aborted. Verify that the SQS subscription in AWS is NOT set to send raw data.");
                return Collections.emptyList();
            }

            LOG.debug("Reading message envelope {}.", envelope.message);
            if (envelope.message.contains(CLOUD_TRAIL_VALIDATION_MESSAGE)) {
                return Collections.emptyList();
            }

            final CloudtrailWriteNotification notification = objectMapper.readValue(envelope.message, CloudtrailWriteNotification.class);

            final List<String> s3ObjectKeys = notification.s3ObjectKey;
            if (s3ObjectKeys == null) {
                LOG.debug("No S3 object keys parsed.");
                return Collections.emptyList();
            }

            LOG.debug("Processing [{}] S3 keys.", s3ObjectKeys.size());
            final List<CloudtrailSNSNotification> notifications = new ArrayList<>(s3ObjectKeys.size());
            for (String s3ObjectKey : s3ObjectKeys) {
                notifications.add(new CloudtrailSNSNotification(message.getReceiptHandle(), notification.s3Bucket, s3ObjectKey));
            }

            LOG.debug("Returning [{}] notifications.", notifications.size());
            return notifications;
        } catch (IOException e) {
            LOG.error("Parsing exception.", e);
            /* Don't throw an exception that would halt processing for one parsing failure.
             * Sometimes occasional non-JSON test messages will come through. If this happens,
             * just log the error and keep processing.
              *
              * Returning an empty list here is OK and should be caught by the caller. */
            return new ArrayList<>();
        }
    }
@Test
    public void testParseWithUnknownProperty() throws Exception {
        final Message message = new Message()
                .withBody("{\"Message\" : \"{\\\"Foobar\\\" : \\\"Quux\\\",\\\"s3Bucket\\\":\\\"cloudtrailbucket\\\",\\\"s3ObjectKey\\\":[\\\"example/AWSLogs/459220251735/CloudTrail/eu-west-1/2014/09/27/459220251735_CloudTrail_eu-west-1_20140927T1625Z_UPwzr7ft2mf0Q1SS.json.gz\\\"]}\"}");

        CloudtrailSNSNotificationParser parser = new CloudtrailSNSNotificationParser(objectMapper);

        List<CloudtrailSNSNotification> notifications = parser.parse(message);
        assertEquals(1, notifications.size());

        CloudtrailSNSNotification notification = notifications.get(0);

        assertEquals(notification.getS3Bucket(), "cloudtrailbucket");
        assertEquals(notification.getS3ObjectKey(), "example/AWSLogs/459220251735/CloudTrail/eu-west-1/2014/09/27/459220251735_CloudTrail_eu-west-1_20140927T1625Z_UPwzr7ft2mf0Q1SS.json.gz");
    }