com.amazonaws.services.cloudtrail.processinglibrary.serializer.AbstractEventSerializer.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.services.cloudtrail.processinglibrary.serializer.AbstractEventSerializer.java

Source

/*******************************************************************************
 * Copyright 2010-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 ******************************************************************************/

package com.amazonaws.services.cloudtrail.processinglibrary.serializer;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEvent;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEventMetadata;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEventData;
import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.CloudTrailEventField;
import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.Resource;
import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.SessionContext;
import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.SessionIssuer;
import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.UserIdentity;
import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.WebIdentitySessionContext;
import com.amazonaws.services.cloudtrail.processinglibrary.utils.LibraryUtils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;

public abstract class AbstractEventSerializer implements EventSerializer {

    private static final Log logger = LogFactory.getLog(AbstractEventSerializer.class);
    private static final String RECORDS = "Records";
    private static final double SUPPORTED_EVENT_VERSION = 1.03d;

    /**
     * A Jackson JSON Parser object.
     */
    private JsonParser jsonParser;

    /**
     * Construct an AbstractEventSerializer object
     *
     * @param jsonParser a Jackson
     * <a href="http://jackson.codehaus.org/1.4.0/javadoc/org/codehaus/jackson/JsonParser.html">JsonParser</a> object to
     *     use for interpreting JSON objects.
     * @throws IOException under no conditions.
     */
    public AbstractEventSerializer(JsonParser jsonParser) throws IOException {
        this.jsonParser = jsonParser;
    }

    /**
     * An abstract class that returns an
     * {@link com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEventMetadata} object.
     *
     * @param charStart the character count at which to begin reading event data.
     * @param charEnd the character count at which to stop reading event data.
     * @return the event metadata.
     */
    public abstract CloudTrailEventMetadata getMetadata(int charStart, int charEnd);

    /**
     * Read the header of an AWS CloudTrail log.
     *
     * @throws JsonParseException if the log could not be parsed.
     * @throws IOException if the log could not be opened or accessed.
     */
    protected void readArrayHeader() throws JsonParseException, IOException {
        if (this.jsonParser.nextToken() != JsonToken.START_OBJECT) {
            throw new JsonParseException("Not a Json object", this.jsonParser.getCurrentLocation());
        }

        this.jsonParser.nextToken();
        if (!jsonParser.getText().equals(RECORDS)) {
            throw new JsonParseException("Not a CloudTrail log", this.jsonParser.getCurrentLocation());
        }

        if (this.jsonParser.nextToken() != JsonToken.START_ARRAY) {
            throw new JsonParseException("Not a CloudTrail log", this.jsonParser.getCurrentLocation());
        }
    }

    /**
     * Indicates whether the CloudTrail log has more events to read.
     *
     * @return <code>true</code> if the log contains more events; <code>false</code> otherwise.
     * @throws IOException if the log could not be opened or accessed.
     */
    public boolean hasNextEvent() throws IOException {
        /* In Fasterxml parser, hasNextEvent will consume next token. So do not call it multiple times. */
        JsonToken nextToken = this.jsonParser.nextToken();
        return nextToken == JsonToken.START_OBJECT || nextToken == JsonToken.START_ARRAY;
    }

    /**
     * Close the JSON parser object used to read the CloudTrail log.
     *
     * @throws IOException if the log could not be opened or accessed.
     */
    public void close() throws IOException {
        this.jsonParser.close();
    }

    /**
     * Get the next event from the CloudTrail log and parse it.
     *
     * @return a {@link com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEvent} that represents the
     *     parsed event.
     * @throws IOException if the event could not be parsed.
     */
    public CloudTrailEvent getNextEvent() throws IOException {
        CloudTrailEventData eventData = new CloudTrailEventData();
        String key = null;

        /* Get next CloudTrailEvent event from log file. When failed to parse a event,
        * IOException will be thrown. In this case, the charEnd index the place we
        * encountered parsing error. */

        // return the starting location of the current token; that is, position of the first character
        // from input that starts the current token
        int charStart = (int) this.jsonParser.getTokenLocation().getCharOffset();

        while (this.jsonParser.nextToken() != JsonToken.END_OBJECT) {
            key = jsonParser.getCurrentName();

            switch (key) {
            case "eventVersion":
                String eventVersion = this.jsonParser.nextTextValue();
                if (Double.parseDouble(eventVersion) > SUPPORTED_EVENT_VERSION) {
                    logger.warn(String.format("EventVersion %s is not supported by CloudTrail.", eventVersion));
                }
                eventData.add(key, eventVersion);
                break;
            case "userIdentity":
                this.parseUserIdentity(eventData);
                break;
            case "eventTime":
                eventData.add(CloudTrailEventField.eventTime.name(),
                        this.convertToDate(this.jsonParser.nextTextValue()));
                break;
            case "eventID":
            case "requestID":
                eventData.add(key, this.convertToUUID(this.jsonParser.nextTextValue()));
                break;
            case "readOnly":
                this.parseReadOnly(eventData);
                break;
            case "resources":
                this.parseResources(eventData);
                break;
            default:
                eventData.add(key, this.parseDefaultValue(key));
                break;
            }
        }
        this.setAccountId(eventData);

        // event's last character position in the log file.
        int charEnd = (int) this.jsonParser.getTokenLocation().getCharOffset();

        CloudTrailEventMetadata metaData = this.getMetadata(charStart, charEnd);

        return new CloudTrailEvent(eventData, metaData);
    }

    /**
     * Set AccountId in CloudTrailEventData top level from either UserIdentity top level or from
     * SessionIssuer. The AccountId in UserIdentity has higher precedence than AccountId in
     * SessionIssuer (if exists).
     *
     * @param eventData the event data to set.
     */
    private void setAccountId(CloudTrailEventData eventData) {
        if (eventData.getUserIdentity() == null) {
            return;
        }

        if (eventData.getUserIdentity().getAccountId() != null) {
            eventData.add("accountId", eventData.getUserIdentity().getAccountId());
        } else {
            SessionContext sessionContext = eventData.getUserIdentity().getSessionContext();
            if (sessionContext != null && sessionContext.getSessionIssuer() != null) {
                eventData.add("accountId", sessionContext.getSessionIssuer().getAccountId());
            }
        }
    }

    /**
     * Parse user identity in CloudTrailEventData
     *
     * @param eventData
     * @throws IOException
     */
    private void parseUserIdentity(CloudTrailEventData eventData) throws IOException {
        JsonToken nextToken = this.jsonParser.nextToken();
        if (nextToken == JsonToken.VALUE_NULL) {
            eventData.add(CloudTrailEventField.userIdentity.name(), null);
            return;
        }

        if (nextToken != JsonToken.START_OBJECT) {
            throw new JsonParseException("Not a UserIdentity object", this.jsonParser.getCurrentLocation());
        }

        UserIdentity userIdentity = new UserIdentity();

        while (this.jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String key = this.jsonParser.getCurrentName();

            switch (key) {
            case "type":
                userIdentity.add(CloudTrailEventField.type.name(), this.jsonParser.nextTextValue());
                break;
            case "principalId":
                userIdentity.add(CloudTrailEventField.principalId.name(), this.jsonParser.nextTextValue());
                break;
            case "arn":
                userIdentity.add(CloudTrailEventField.arn.name(), this.jsonParser.nextTextValue());
                break;
            case "accountId":
                userIdentity.add(CloudTrailEventField.accountId.name(), this.jsonParser.nextTextValue());
                break;
            case "accessKeyId":
                userIdentity.add(CloudTrailEventField.accessKeyId.name(), this.jsonParser.nextTextValue());
                break;
            case "userName":
                userIdentity.add(CloudTrailEventField.userName.name(), this.jsonParser.nextTextValue());
                break;
            case "sessionContext":
                this.parseSessionContext(userIdentity);
                break;
            case "invokedBy":
                userIdentity.add(CloudTrailEventField.invokedBy.name(), this.jsonParser.nextTextValue());
                break;
            default:
                userIdentity.add(key, this.parseDefaultValue(key));
                break;
            }
        }
        eventData.add(CloudTrailEventField.userIdentity.name(), userIdentity);
    }

    /**
     * Parse session context object
     *
     * @param userIdentity the {@link com.amazonaws.services.cloudtrail.processinglibrary.model.internal.UserIdentity}
     * @throws IOException
     * @throws JsonParseException
     */
    private void parseSessionContext(UserIdentity userIdentity) throws IOException {
        if (this.jsonParser.nextToken() != JsonToken.START_OBJECT) {
            throw new JsonParseException("Not a SessionContext object", this.jsonParser.getCurrentLocation());
        }

        SessionContext sessionContext = new SessionContext();

        while (this.jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String key = this.jsonParser.getCurrentName();

            switch (key) {
            case "attributes":
                sessionContext.add(CloudTrailEventField.attributes.name(), this.parseAttributes());
                break;
            case "sessionIssuer":
                sessionContext.add(CloudTrailEventField.sessionIssuer.name(),
                        this.parseSessionIssuer(sessionContext));
                break;
            case "webIdFederationData":
                sessionContext.add(CloudTrailEventField.webIdFederationData.name(),
                        this.parseWebIdentitySessionContext(sessionContext));
                break;
            default:
                sessionContext.add(key, this.parseDefaultValue(key));
                break;
            }
        }

        userIdentity.add(CloudTrailEventField.sessionContext.name(), sessionContext);

    }

    /**
     * Parse web identify session object
     *
     * @param sessionContext
     * @return the web identity session context
     * @throws IOException
     */
    private WebIdentitySessionContext parseWebIdentitySessionContext(SessionContext sessionContext)
            throws IOException {
        if (this.jsonParser.nextToken() != JsonToken.START_OBJECT) {
            throw new JsonParseException("Not a WebIdentitySessionContext object",
                    this.jsonParser.getCurrentLocation());
        }

        WebIdentitySessionContext webIdFederationData = new WebIdentitySessionContext();

        while (this.jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String key = this.jsonParser.getCurrentName();

            switch (key) {
            case "attributes":
                webIdFederationData.add(CloudTrailEventField.attributes.name(), this.parseAttributes());
                break;
            case "federatedProvider":
                webIdFederationData.add(CloudTrailEventField.federatedProvider.name(),
                        this.jsonParser.nextTextValue());
                break;
            default:
                webIdFederationData.add(key, this.parseDefaultValue(key));
                break;
            }
        }

        return webIdFederationData;
    }

    /**
     * Parse session issuer object. It only happened on role session and federated session.
     *
     * @param sessionContext
     * @return the session issuer object.
     * @throws IOException
     */
    private SessionIssuer parseSessionIssuer(SessionContext sessionContext) throws IOException {
        if (this.jsonParser.nextToken() != JsonToken.START_OBJECT) {
            throw new JsonParseException("Not a SessionIssuer object", this.jsonParser.getCurrentLocation());
        }

        SessionIssuer sessionIssuer = new SessionIssuer();

        while (this.jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String key = this.jsonParser.getCurrentName();

            switch (key) {
            case "type":
                sessionIssuer.add(CloudTrailEventField.type.name(), this.jsonParser.nextTextValue());
                break;
            case "principalId":
                sessionIssuer.add(CloudTrailEventField.principalId.name(), this.jsonParser.nextTextValue());
                break;
            case "arn":
                sessionIssuer.add(CloudTrailEventField.arn.name(), this.jsonParser.nextTextValue());
                break;
            case "accountId":
                sessionIssuer.add(CloudTrailEventField.accountId.name(), this.jsonParser.nextTextValue());
                break;
            case "userName":
                sessionIssuer.add(CloudTrailEventField.userName.name(), this.jsonParser.nextTextValue());
                break;
            default:
                sessionIssuer.add(key, this.parseDefaultValue(key));
                break;
            }
        }

        return sessionIssuer;
    }

    /**
     * Parse event read only attribute.
     *
     * @param eventData
     *
     * @throws JsonParseException
     * @throws IOException
     */
    private void parseReadOnly(CloudTrailEventData eventData) throws JsonParseException, IOException {
        this.jsonParser.nextToken();
        Boolean readOnly = null;
        if (this.jsonParser.getCurrentToken() != JsonToken.VALUE_NULL) {
            readOnly = this.jsonParser.getBooleanValue();
        }
        eventData.add(CloudTrailEventField.readOnly.name(), readOnly);
    }

    /**
     * Parse a list of Resource
     *
     * @param eventData the resources belong to
     * @throws IOException
     */
    private void parseResources(CloudTrailEventData eventData) throws IOException {
        JsonToken nextToken = this.jsonParser.nextToken();
        if (nextToken == JsonToken.VALUE_NULL) {
            eventData.add(CloudTrailEventField.resources.name(), null);
            return;
        }

        if (nextToken != JsonToken.START_ARRAY) {
            throw new JsonParseException("Not a list of resources object", this.jsonParser.getCurrentLocation());
        }

        List<Resource> resources = new ArrayList<Resource>();

        while (this.jsonParser.nextToken() != JsonToken.END_ARRAY) {
            resources.add(this.parseResource());
        }

        eventData.add(CloudTrailEventField.resources.name(), resources);
    }

    /**
     * Parse a single Resource
     *
     * @return a single resource
     * @throws IOException
     */
    private Resource parseResource() throws IOException {
        //current token is ready consumed by parseResources
        if (this.jsonParser.getCurrentToken() != JsonToken.START_OBJECT) {
            throw new JsonParseException("Not a Resource object", this.jsonParser.getCurrentLocation());
        }

        Resource resource = new Resource();

        while (this.jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String key = this.jsonParser.getCurrentName();

            switch (key) {
            default:
                resource.add(key, this.parseDefaultValue(key));
                break;
            }
        }

        return resource;
    }

    /**
     * Parse the event with key as default value.
     *
     * If the value is JSON null, then we will return null.
     * If the value is JSON object (of starting with START_ARRAY or START_OBject) , then we will convert the object to String.
     * If the value is JSON scalar value (non-structured object), then we will return simply return it as String.
     *
     * @param key
     * @throws IOException
     */
    private String parseDefaultValue(String key) throws IOException {
        this.jsonParser.nextToken();
        String value = null;
        JsonToken currentToken = this.jsonParser.getCurrentToken();
        if (currentToken != JsonToken.VALUE_NULL) {
            if (currentToken == JsonToken.START_ARRAY || currentToken == JsonToken.START_OBJECT) {
                JsonNode node = this.jsonParser.readValueAsTree();
                value = node.toString();
            } else {
                value = this.jsonParser.getValueAsString();
            }
        }
        return value;
    }

    /**
     * Parse attributes as a Map, used in both parseWebIdentitySessionContext and parseSessionContext
     *
     * @return attributes for either session context or web identity session context
     * @throws IOException
     */
    private Map<String, String> parseAttributes() throws IOException {
        if (this.jsonParser.nextToken() != JsonToken.START_OBJECT) {
            throw new JsonParseException("Not a Attributes object", this.jsonParser.getCurrentLocation());
        }

        Map<String, String> attributes = new HashMap<>();

        while (this.jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String key = this.jsonParser.getCurrentName();
            String value = this.jsonParser.nextTextValue();
            attributes.put(key, value);
        }

        return attributes;
    }

    /**
     * This method convert a String to UUID type. Currently EventID is in UUID type.
     *
     * @param str that need to convert to UUID
     * @return the UUID.
     */
    private UUID convertToUUID(String str) {
        return UUID.fromString(str);
    }

    /**
     * This method convert a String to Date type. When parse error happened return current date.
     *
     * @param dateInString the String to convert to Date
     * @return Date the date and time in coordinated universal time
     * @throws IOException
     */
    private Date convertToDate(String dateInString) throws IOException {
        Date date = null;
        if (dateInString != null) {
            try {
                date = LibraryUtils.getUtcSdf().parse(dateInString);
            } catch (ParseException e) {
                throw new IOException("Cannot parse " + dateInString + " as Date", e);
            }
        }
        return date;
    }
}