/* * Copyright 2016 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.eclipse.lambda.serverless; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import com.amazonaws.eclipse.lambda.serverless.model.EventSourceType; import com.amazonaws.eclipse.lambda.serverless.model.ResourceType; import com.amazonaws.eclipse.lambda.serverless.model.ServerlessTemplate; import com.amazonaws.eclipse.lambda.serverless.model.TypeProperties; import com.amazonaws.eclipse.lambda.serverless.model.transform.ServerlessFunction; import com.amazonaws.eclipse.lambda.serverless.model.transform.ServerlessModel; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; /** * A class for processing serverless template file, including loading Serverless template file into a model, * writing a model back to a Serverless template file, etc. */ public class Serverless { private static final ObjectMapper MAPPER = new ObjectMapper(); static { MAPPER.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); } public static ServerlessModel load(String templatePath) throws JsonParseException, JsonMappingException, IOException { File serverlessFile = new File(templatePath); return load(serverlessFile); } public static ServerlessModel load(File templateFile) throws JsonParseException, JsonMappingException, IOException { ServerlessTemplate serverlessTemplate = MAPPER.readValue(templateFile, ServerlessTemplate.class); return convert(serverlessTemplate); } public static File write(ServerlessModel model, String path) throws JsonGenerationException, JsonMappingException, IOException { File file = new File(path); ServerlessTemplate template = convert(model); MAPPER.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(file), template); return file; } /** * Make this raw Serverless model to one that could be recognized by CloudFormation. * * @param model - The raw Serverless model. * @param packagePrefix - The package prefix holding all the Lambda Functions. * @param filePath - The zipped jar file path. It should be a s3 path since CloudFormation only support this. * @return A CloudFormation recognized Serverless Model. */ public static ServerlessModel cookServerlessModel(ServerlessModel model, String packagePrefix, String filePath) { for (Entry<String, ServerlessFunction> entry : model.getServerlessFunctions().entrySet()) { ServerlessFunction function = entry.getValue(); function.setHandler(NameUtils.toHandlerClassFqcn(packagePrefix, function.getHandler())); function.setCodeUri(filePath); } return model; } private static ServerlessTemplate convert(ServerlessModel model) { ServerlessTemplate template = new ServerlessTemplate(); template.setAWSTemplateFormatVersion(model.getAWSTemplateFormatVersion()); template.setDescription(model.getDescription()); template.setTransform(model.getTransform()); template.setAdditionalProperties(model.getAdditionalProperties()); Map<String, TypeProperties> resources = new HashMap<String, TypeProperties>(); resources.putAll(model.getAdditionalResources()); // Put Serverless resources to the map. for (Entry<String, ServerlessFunction> entry : model.getServerlessFunctions().entrySet()) { String resourceLogicalId = entry.getKey(); ServerlessFunction serverlessFunction = entry.getValue(); resources.put(resourceLogicalId, serverlessFunction.toTypeProperties()); } template.setResources(resources); return template; } private static ServerlessModel convert(ServerlessTemplate template) { ServerlessModel model = new ServerlessModel(); model.setAWSTemplateFormatVersion(template.getAWSTemplateFormatVersion()); model.setDescription(template.getDescription()); model.setTransform(template.getTransform()); model.setAdditionalProperties(template.getAdditionalProperties()); if (template.getResources() == null) return model; for (Entry<String, TypeProperties> entry : template.getResources().entrySet()) { String key = entry.getKey(); TypeProperties resource = entry.getValue(); convertResource(model, key, resource); } return model; } /** * Convert a general representation of a resource to a modeled resource and put it to ServerlessModel. * * @param model The Serverless model in which the converted resource will be put. * @param resourceLogicalId The logical id for the resource. * @param resource The general representation for the resource. */ private static void convertResource(ServerlessModel model, String resourceLogicalId, TypeProperties resource) { ResourceType type = ResourceType.fromValue(resource.getType()); switch (type) { case AWS_SERVERLESS_FUNCTION: ServerlessFunction function = convertServerlessFunction(resource.getProperties()); model.getServerlessFunctions().put(resourceLogicalId, function); break; // Unrecognized resources put to additionalResources default: model.getAdditionalResources().put(resourceLogicalId, resource); break; } } private static ServerlessFunction convertServerlessFunction(Map<String, Object> resource) { ServerlessFunction function = MAPPER.convertValue(resource, ServerlessFunction.class); Object eventsObject = resource.get("Events"); if (eventsObject != null) { Map<String, TypeProperties> events = convert((Map<String, Object>) eventsObject); Map<String, TypeProperties> additionalEvents = new HashMap<String, TypeProperties>(); for (Entry<String, TypeProperties> entry : events.entrySet()) { String eventLogicalId = entry.getKey(); TypeProperties event = entry.getValue(); EventSourceType eventSourceType = EventSourceType .fromValue(event.getType()); switch (eventSourceType) { // TODO: Treating all the events as additional event for // now. Need to add Api Event handler to better handle this // event. default: additionalEvents.put(eventLogicalId, event); break; } } function.setAdditionalEvents(additionalEvents); } return function; } private static Map<String, TypeProperties> convert(Map<String, Object> map) { Map<String, TypeProperties> typeProperties = new HashMap<String, TypeProperties>(); for (Entry<String, Object> entry : map.entrySet()) { typeProperties.put(entry.getKey(), MAPPER.convertValue(entry.getValue(), TypeProperties.class)); } return typeProperties; } }