Java tutorial
/******************************************************************************* * 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; } }