All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.phoenixnap.oss.ramlapisync.generation.RamlParser Maven / Gradle / Ivy

Go to download

Components including the parsing of RAML documents and Spring MVC Annotations to create RAML models

The newest version!
/*
 * Copyright 2002-2017 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License 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.phoenixnap.oss.ramlapisync.generation;

import java.util.LinkedHashSet;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.phoenixnap.oss.ramlapisync.data.ApiResourceMetadata;
import com.phoenixnap.oss.ramlapisync.naming.RamlHelper;
import com.phoenixnap.oss.ramlapisync.pojo.PojoGenerationConfig;
import com.phoenixnap.oss.ramlapisync.raml.RamlAction;
import com.phoenixnap.oss.ramlapisync.raml.RamlActionType;
import com.phoenixnap.oss.ramlapisync.raml.RamlResource;
import com.phoenixnap.oss.ramlapisync.raml.RamlResponse;
import com.phoenixnap.oss.ramlapisync.raml.RamlRoot;
import com.sun.codemodel.JCodeModel;


/**
 * 
 * Class containing methods that are used to parse raml files for code generation from the RAML
 * 
 * @author Kurt Paris
 * @since 0.2.1
 *
 */	
public class RamlParser {

	/**
	 * Class Logger
	 */
	protected static final Logger logger = LoggerFactory.getLogger(RamlParser.class);
	
	public static final int DEFAULT_RESOURCE_DEPTH = 1;

	public static final int DEFAULT_RESOURCE_TOP_LEVEL = 0;

	public static final boolean DEFAULT_REVERSE_ORDER = false;

	/**
	 * Base configuration for code generation
	 */
	private PojoGenerationConfig config;

	/**
	 * The start URL that every controller should be prefixed with
	 */
	private String startUrl = "";
	
	/**
	 * If set to true, we will generate seperate methods for different content types in the RAML
	 */
	protected boolean seperateMethodsByContentType = false;

	/**
	 * If set to true, we will add a HttpHeaders parameter in the action methods
	 */
	protected boolean injectHttpHeadersParameter = false;

	/**
	 * How many levels of uri will be included in generated class names. Default
	 * is 1 which means that only current resource will be in included in
	 * controller/decorator names.
	 */
	protected int resourceDepthInClassNames = DEFAULT_RESOURCE_DEPTH;
	
	/**
	 * Top level of URI included in generated class names. Default is 0 which means
	 * that all resources will be in included in controller/decorator names.
	 */
	protected int resourceTopLevelInClassNames = DEFAULT_RESOURCE_TOP_LEVEL;

	/**
	 * Reverse order of resource path that will be included in generated class names. Default is false which means
	 * that resources will be in included in controller/decorator names from left to right.
	 */
	protected boolean reverseOrderInClassNames = DEFAULT_REVERSE_ORDER;

	public RamlParser (PojoGenerationConfig config) {
		this.config = config;
	}
	
	public RamlParser (String basePackage) {
		config = new PojoGenerationConfig().withPackage(basePackage, null);
	}
	
	public RamlParser(String basePackage, String startUrl, boolean seperateMethodsByContentType, boolean injectHttpHeadersParameter) {
		this(new PojoGenerationConfig().withPackage(basePackage, null), startUrl, seperateMethodsByContentType, injectHttpHeadersParameter, DEFAULT_RESOURCE_DEPTH, DEFAULT_RESOURCE_TOP_LEVEL, DEFAULT_REVERSE_ORDER);
	}
	
	public RamlParser(PojoGenerationConfig config, String startUrl, boolean seperateMethodsByContentType, boolean injectHttpHeadersParameter, int resourceDepthInClassNames, int resourceTopLevelInClassNames, boolean reverseOrderInClassNames) {
		this(config);
		this.seperateMethodsByContentType = seperateMethodsByContentType;
		this.injectHttpHeadersParameter = injectHttpHeadersParameter;
		this.startUrl = startUrl;
		this.resourceDepthInClassNames = resourceDepthInClassNames;
		this.resourceTopLevelInClassNames = resourceTopLevelInClassNames;
		this.reverseOrderInClassNames = reverseOrderInClassNames;
	}

	/**
	 * This method will extract a set of controllers from the RAML file.
	 * These controllers will contain the metadata required by the code generator, including name
	 * any annotations as well as conatining methods
	 * 
	 * @param bodyCodeModel the code model containing body objects
	 * @param raml The raml document to be parsed
	 * @return A set of Controllers representing the inferred resources in the system
	 */
	public Set extractControllers (JCodeModel bodyCodeModel, RamlRoot raml) {
		
		Set controllers = new LinkedHashSet<>();
		if (raml == null) {
			return controllers;
		}
		if (bodyCodeModel == null) {
			bodyCodeModel = new JCodeModel();
		}

		Set names = new LinkedHashSet<>();
		Set namesToDisable = new LinkedHashSet<>();
		//Iterate on all parent resources
		//if we have child resources, just append the url and go down the chain until we hit the first action.
		//if an action is found we need to 
		for (Entry resource : raml.getResources().entrySet()) {
			Set resources = checkResource(bodyCodeModel, startUrl, resource.getValue(), null, raml);
			for (ApiResourceMetadata resourceMetadata : resources) {
				if (names.contains(resourceMetadata.getResourceName())) {
					//collision has occured, lets mark this for 2nd pass
					namesToDisable.add(resourceMetadata.getResourceName());
				}
				names.add(resourceMetadata.getResourceName());
				controllers.add(resourceMetadata);
			}
		}
		
		//second pass, disabling singularisation
		for (ApiResourceMetadata resourceMetadata : controllers) {
			if (namesToDisable.contains(resourceMetadata.getResourceName())) {
				resourceMetadata.setSingularizeName(false);
			}
		}
		
		return controllers;
	}
	
	private boolean shouldCreateController (RamlResource resource) {
		
		//If controller has actions create it
		if (resource.getActions() != null && !resource.getActions().isEmpty()) {
			return true;
		} 
		
		//Lookahead to child resource - if the child has a uriParameter then it's likely that we are at a good resource depth
		if (resource.getResources() != null &&  !resource.getResources().isEmpty()) {
			for (RamlResource childResource : resource.getResources().values()) {
				if (childResource.getUriParameters() != null && !childResource.getUriParameters().isEmpty() 
						|| (childResource.getResolvedUriParameters() != null && !childResource.getResolvedUriParameters().isEmpty())) {
					return true;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Recursive method to parse resources in a Raml File. It tries to go as deep as possible before creating the root Resource. Once this is done, methods and
	 * child resources will be relative to the root resource
	 * 
	 * @param bodyCodeModel The code model containing body pojos
	 * @param baseUrl The url currently being checked. Used to keep depth
	 * @param resource The Resource in the RAML file being parsed
	 * @param controller The root controller if created for this branch
	 * @param document The raml Document being parse
	 * @return A set of Controllers representing resources in this branch of the tree
	 */
	public Set checkResource(JCodeModel bodyCodeModel, String baseUrl, RamlResource resource, ApiResourceMetadata controller, RamlRoot document) {
		Set controllers = new LinkedHashSet<>();
		//append resource URL to url.
		String url = baseUrl + resource.getRelativeUri();
		if (controller == null && shouldCreateController(resource)) {
			controller = new ApiResourceMetadata(config, bodyCodeModel, url, resource, document, this.resourceDepthInClassNames, this.resourceTopLevelInClassNames, this.reverseOrderInClassNames);
			controllers.add(controller);
		}
		//extract actions for this resource
		if (resource.getActions() != null && !resource.getActions().isEmpty()) {	
			for (RamlActionType actionType : RamlActionType.values()) {
				if (resource.getActions().containsKey(actionType)) {
					RamlAction childResource = resource.getActions().get(actionType);
					
					//if we have multiple response types in the raml, this should produce different calls
					RamlResponse response = null;
					
					if (childResource.getResponses() != null) {
						response = RamlHelper.getSuccessfulResponse(childResource);
					}
					
					if (seperateMethodsByContentType && response != null && response.hasBody() && response.getBody().size() > 1) {
							for (String responseType : response.getBody().keySet()) {
								controller.addApiCall(resource, actionType, childResource, responseType, injectHttpHeadersParameter);
							}
						
					} else {
						controller.addApiCall(resource, actionType, childResource, null, injectHttpHeadersParameter);
					}
				}
			}
		}
		if (resource.getResources() != null &&  !resource.getResources().isEmpty()) {
			for (Entry childResource : resource.getResources().entrySet()) {
				controllers.addAll(checkResource(bodyCodeModel, url, childResource.getValue(), controller,document));
			}
		}
		return controllers;	
	}

	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy