Java tutorial
/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.module.apikit; import static org.mule.module.apikit.UrlUtils.getBaseSchemeHostPort; import static org.raml.parser.rule.ValidationResult.Level.ERROR; import static org.raml.parser.rule.ValidationResult.Level.WARN; import static org.raml.parser.rule.ValidationResult.UNKNOWN; import org.mule.api.MuleContext; import org.mule.api.MuleEvent; import org.mule.api.construct.FlowConstruct; import org.mule.api.context.MuleContextAware; import org.mule.api.endpoint.ImmutableEndpoint; import org.mule.api.lifecycle.Initialisable; import org.mule.api.lifecycle.InitialisationException; import org.mule.construct.Flow; import org.mule.module.apikit.exception.ApikitRuntimeException; import org.mule.module.apikit.exception.NotFoundException; import org.mule.module.apikit.injector.RamlUpdater; import org.mule.module.apikit.uri.URIPattern; import org.mule.module.apikit.uri.URIResolver; import org.mule.util.BeanUtils; import org.mule.util.IOUtils; import org.mule.util.StringMessageUtils; import org.mule.util.StringUtils; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import org.apache.commons.lang.SerializationUtils; import org.raml.emitter.RamlEmitter; import org.raml.model.Action; import org.raml.model.ActionType; import org.raml.model.Raml; import org.raml.model.Resource; import org.raml.parser.loader.ResourceLoader; import org.raml.parser.rule.ValidationResult; import org.raml.parser.visitor.RamlDocumentBuilder; import org.raml.parser.visitor.RamlValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AbstractConfiguration implements Initialisable, MuleContextAware { public static final String APPLICATION_RAML = "application/raml+yaml"; private static final String CONSOLE_URL_FILE = "consoleurl"; private static final int URI_CACHE_SIZE = 1000; protected final Logger logger = LoggerFactory.getLogger(getClass()); protected FlowConstruct flowConstruct; protected MuleContext muleContext; private String name; protected String raml; private Raml baseApi; //original raml protected Raml api; //current raml private String baseSchemeHostPort; private Map<String, String> apikitRaml = new ConcurrentHashMap<String, String>(); private boolean disableValidations; protected Map<String, FlowResolver> restFlowMapWrapper; protected Map<URIPattern, Resource> routingTable; protected LoadingCache<String, URIResolver> uriResolverCache; protected LoadingCache<String, URIPattern> uriPatternCache; private List<String> consoleUrls = new ArrayList<String>(); private boolean started; @Override public void initialise() throws InitialisationException { if (muleContext == null) { return; } ResourceLoader loader = getRamlResourceLoader(); validateRaml(loader); RamlDocumentBuilder builder = new RamlDocumentBuilder(loader); api = builder.build(raml); cleanBaseUriParameters(api); baseSchemeHostPort = getBaseSchemeHostPort(api.getBaseUri()); initializeRestFlowMap(); initializeRestFlowMapWrapper(); loadRoutingTable(); buildResourcePatternCaches(); } private void loadRoutingTable() { if (routingTable == null) { routingTable = new ConcurrentHashMap<URIPattern, Resource>(); } buildRoutingTable(getApi().getResources()); } private void buildResourcePatternCaches() { logger.info("Building resource URI cache..."); uriResolverCache = CacheBuilder.newBuilder().maximumSize(URI_CACHE_SIZE) .build(new CacheLoader<String, URIResolver>() { public URIResolver load(String path) throws IOException { return new URIResolver(path); } }); uriPatternCache = CacheBuilder.newBuilder().maximumSize(URI_CACHE_SIZE) .build(new CacheLoader<String, URIPattern>() { public URIPattern load(String path) throws Exception { URIResolver resolver = uriResolverCache.get(path); URIPattern match = resolver.find(routingTable.keySet(), URIResolver.MatchRule.BEST_MATCH); if (match == null) { logger.warn("No matching patterns for URI " + path); throw new NotFoundException(path); } return match; } }); } private void buildRoutingTable(Map<String, Resource> resources) { for (Resource resource : resources.values()) { String parentUri = resource.getParentUri(); if (parentUri.contains("{version}")) { resource.setParentUri(parentUri.replaceAll("\\{version}", getApi().getVersion())); } String uri = resource.getUri(); logger.debug("Adding URI to the routing table: " + uri); routingTable.put(new URIPattern(uri), resource); if (resource.getResources() != null) { buildRoutingTable(resource.getResources()); } } } public void loadApiDefinition(FlowConstruct flowConstruct) { this.flowConstruct = flowConstruct; injectEndpointUri(api); resetRamlMap(); } public void updateApi(Raml newApi) { api = newApi; loadRoutingTable(); resetRamlMap(); } private void resetRamlMap() { apikitRaml = new ConcurrentHashMap<String, String>(); apikitRaml.put(baseSchemeHostPort, new RamlEmitter().dump(api)); } protected abstract void initializeRestFlowMap(); protected void validateRaml(ResourceLoader resourceLoader) { List<ValidationResult> results = RamlValidationService.createDefault(resourceLoader).validate(raml); List<ValidationResult> errors = ValidationResult.getLevel(ERROR, results); if (!errors.isEmpty()) { String msg = aggregateMessages(errors, "Invalid API descriptor -- errors found: "); throw new ApikitRuntimeException(msg); } List<ValidationResult> warnings = ValidationResult.getLevel(WARN, results); if (!warnings.isEmpty()) { logger.warn(aggregateMessages(warnings, "API descriptor Warnings -- warnings found: ")); } } private String aggregateMessages(List<ValidationResult> results, String header) { StringBuilder sb = new StringBuilder(); sb.append(header).append(results.size()).append("\n\n"); for (ValidationResult result : results) { sb.append(result.getMessage()).append(" -- "); sb.append(" file: "); sb.append(result.getIncludeName() != null ? result.getIncludeName() : raml); if (result.getLine() != UNKNOWN) { sb.append(" -- line "); sb.append(result.getLine()); } sb.append("\n"); } return sb.toString(); } public abstract ResourceLoader getRamlResourceLoader(); private void injectEndpointUri(Raml ramlApi) { String address = getEndpointAddress(flowConstruct); ramlApi.setBaseUri(address); baseSchemeHostPort = getBaseSchemeHostPort(address); } private void cleanBaseUriParameters(Raml ramlApi) { ramlApi.getBaseUriParameters().clear(); cleanBaseUriParameters(ramlApi.getResources()); } private void cleanBaseUriParameters(Map<String, Resource> resources) { for (Resource resource : resources.values()) { resource.getBaseUriParameters().clear(); for (Action action : resource.getActions().values()) { action.getBaseUriParameters().clear(); } if (!resource.getResources().isEmpty()) { cleanBaseUriParameters(resource.getResources()); } } } public String getEndpointAddress(FlowConstruct flowConstruct) { ImmutableEndpoint endpoint = (ImmutableEndpoint) ((Flow) flowConstruct).getMessageSource(); String address = endpoint.getAddress(); String path = endpoint.getEndpointURI().getPath(); String scheme = endpoint.getEndpointURI().getScheme(); String chAddress = System.getProperty("fullDomain"); String chBaseUri = scheme + "://" + chAddress + path; if (logger.isDebugEnabled()) { if (api != null) { logger.debug("yaml baseUri: " + api.getBaseUri()); } logger.debug("mule baseUri: " + address); logger.debug("chub baseUri: " + chBaseUri); } if (chAddress != null) { address = chBaseUri; } if (address.endsWith("/")) { logger.debug("removing trailing slash from baseuri -> " + address); address = address.substring(0, address.length() - 1); } return address; } /** * Returns the RAML descriptor of the API. * The schemeHostPort parameter is used to rewrite the base uri with the actual host received. */ public String getApikitRaml(String schemeHostPort) { if (schemeHostPort == null) { return apikitRaml.get(baseSchemeHostPort); } String hostRaml = apikitRaml.get(schemeHostPort); if (hostRaml == null) { Raml clone = shallowCloneRaml(api); clone.setBaseUri(api.getBaseUri().replace(baseSchemeHostPort, schemeHostPort)); hostRaml = new RamlEmitter().dump(clone); apikitRaml.put(schemeHostPort, hostRaml); } return hostRaml; } public String getApikitRaml(MuleEvent event) { return getApikitRaml(getBaseSchemeHostPort(event)); } /** * returns the raml descriptor using the host from the console request event * only when the bind to all interfaces ip (0.0.0.0) is used for the router endpoint. * Otherwise it uses the router endpoint address as it is */ public String getApikitRamlConsole(MuleEvent event) { String schemeHostPort = baseSchemeHostPort; String bindAllInterfaces = "0.0.0.0"; if (schemeHostPort.contains(bindAllInterfaces)) { try { URL url = new URL(getBaseSchemeHostPort(event)); schemeHostPort = schemeHostPort.replace(bindAllInterfaces, url.getHost()); } catch (MalformedURLException e) { throw new ApikitRuntimeException(e); } } return getApikitRaml(schemeHostPort); } private Raml deepCloneRaml(Raml source) { return (Raml) SerializationUtils.deserialize(SerializationUtils.serialize(source)); } private Raml shallowCloneRaml(Raml source) { try { return (Raml) BeanUtils.cloneBean(source); } catch (Exception e) { throw new RuntimeException(e); } } public boolean isDisableValidations() { return disableValidations; } public void setDisableValidations(boolean disableValidations) { this.disableValidations = disableValidations; } public Raml getApi() { return api; } public Map<String, FlowResolver> getRestFlowMap() { return restFlowMapWrapper; } public MuleContext getMuleContext() { return muleContext; } public void setMuleContext(MuleContext muleContext) { this.muleContext = muleContext; } protected void initializeRestFlowMapWrapper() { restFlowMapWrapper = populateFlowMapWrapper(); } private Map<String, FlowResolver> populateFlowMapWrapper() { Map<String, FlowResolver> map = new HashMap<String, FlowResolver>(); populateMapKeys(map, api.getResources()); return map; } private void populateMapKeys(Map<String, FlowResolver> wrapperFlowMap, Map<String, Resource> resources) { for (Map.Entry<String, Resource> resourceEntry : resources.entrySet()) { String resource = resourceEntry.getValue().getUri(); for (Map.Entry<ActionType, Action> actionEntry : resourceEntry.getValue().getActions().entrySet()) { String key = actionEntry.getKey().name().toLowerCase() + ":" + resource; wrapperFlowMap.put(key, getFlowResolver(this, key)); } populateMapKeys(wrapperFlowMap, resourceEntry.getValue().getResources()); } } protected abstract FlowResolver getFlowResolver(AbstractConfiguration abstractConfiguration, String key); public void addConsoleUrl(String url) { if (StringUtils.isNotBlank(url)) { consoleUrls.add(url); } } public void publishConsoleUrls(String parentDirectory) { started = true; if (isLastRouterToStart()) { dumpUrlsFile(parentDirectory); } if (logger.isInfoEnabled()) { for (String consoleUrl : consoleUrls) { String msg = String.format("APIKit Console URL: %s", consoleUrl); logger.info(StringMessageUtils.getBoilerPlate(msg)); } } } private boolean isLastRouterToStart() { for (AbstractConfiguration configuration : getAllConfigurations(muleContext)) { if (!configuration.started) { return false; } } return true; } private void dumpUrlsFile(String parentDirectory) { File urlFile = new File(parentDirectory, CONSOLE_URL_FILE); FileWriter writer = null; try { if (!urlFile.exists()) { urlFile.createNewFile(); } writer = new FileWriter(urlFile, true); for (String consoleUrl : getAllConsoleUrls()) { writer.write(consoleUrl + "\n"); } writer.flush(); } catch (IOException e) { logger.error("cannot publish console url for studio", e); } finally { IOUtils.closeQuietly(writer); } } private List<String> getAllConsoleUrls() { List<String> urls = new ArrayList<String>(); for (AbstractConfiguration configuration : getAllConfigurations(muleContext)) { urls.addAll(configuration.consoleUrls); } return urls; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRaml() { return raml; } public void setRaml(String raml) { this.raml = raml; } public Action getEventAction(MuleEvent event) { HttpRestRequest request = getHttpRestRequest(event); String path = request.getResourcePath(); URIPattern uriPattern; try { uriPattern = uriPatternCache.get(path); } catch (ExecutionException e) { return null; } Resource resource = routingTable.get(uriPattern); return resource.getAction(request.getMethod()); } protected abstract HttpRestRequest getHttpRestRequest(MuleEvent muleEvent); public static Collection<AbstractConfiguration> getAllConfigurations(MuleContext muleContext) { Collection<AbstractConfiguration> configurations = new ArrayList<AbstractConfiguration>(); configurations.addAll(muleContext.getRegistry().lookupObjects(Configuration.class)); configurations.addAll(muleContext.getRegistry().lookupObjects(ProxyConfiguration.class)); return configurations; } public RamlUpdater getRamlUpdater() { if (baseApi == null) { baseApi = deepCloneRaml(api); } return new RamlUpdater(deepCloneRaml(baseApi), this); } public Set<String> getFlowActionRefs(Flow flow) { Set<String> actionRefs = new HashSet<String>(); for (Map.Entry<String, FlowResolver> entry : restFlowMapWrapper.entrySet()) { if (flow == entry.getValue().getFlow()) { actionRefs.add(entry.getKey()); } } return actionRefs; } }