/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.camel.maven; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.camel.util.component.ApiMethodArg; import org.apache.camel.util.component.ApiMethodParser; import org.apache.camel.util.component.ArgumentSubstitutionParser; import org.apache.commons.lang.ClassUtils; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Parameter; import org.apache.velocity.VelocityContext; /** * Base Mojo class for ApiMethod generators. */ public abstract class AbstractApiMethodGeneratorMojo extends AbstractApiMethodBaseMojo { private static final Map<Class<?>, String> PRIMITIVE_VALUES; @Parameter(required = true, property = PREFIX + "proxyClass") protected String proxyClass; // cached fields private Class<?> proxyType; private Pattern propertyNamePattern; private Pattern propertyTypePattern; @Override public void execute() throws MojoExecutionException, MojoFailureException { setCompileSourceRoots(); // load proxy class and get enumeration file to generate final Class proxyType = getProxyType(); // parse pattern for excluded endpoint properties if (excludeConfigNames != null) { propertyNamePattern = Pattern.compile(excludeConfigNames); } if (excludeConfigTypes != null) { propertyTypePattern = Pattern.compile(excludeConfigTypes); } // create parser ApiMethodParser parser = createAdapterParser(proxyType); parser.setSignatures(getSignatureList()); parser.setClassLoader(getProjectClassLoader()); // parse signatures @SuppressWarnings("unchecked") final List<ApiMethodParser.ApiMethodModel> models = parser.parse(); // generate enumeration from model mergeTemplate(getApiMethodContext(models), getApiMethodFile(), "/api-method-enum.vm"); // generate EndpointConfiguration for this Api mergeTemplate(getEndpointContext(models), getConfigurationFile(), "/api-endpoint-config.vm"); // generate junit test if it doesn't already exist under test source directory // i.e. it may have been generated then moved there and populated with test values final String testFilePath = getTestFilePath(); if (!new File(project.getBuild().getTestSourceDirectory(), testFilePath).exists()) { mergeTemplate(getApiTestContext(models), new File(generatedTestDir, testFilePath), "/api-route-test.vm"); } } @SuppressWarnings("unchecked") protected ApiMethodParser createAdapterParser(Class proxyType) { return new ArgumentSubstitutionParser(proxyType, getArgumentSubstitutions()); } public abstract List<String> getSignatureList() throws MojoExecutionException; public Class<?> getProxyType() throws MojoExecutionException { if (proxyType == null) { // load proxy class from Project runtime dependencies try { proxyType = getProjectClassLoader().loadClass(proxyClass); } catch (ClassNotFoundException e) { throw new MojoExecutionException(e.getMessage(), e); } } return proxyType; } private VelocityContext getApiMethodContext(List<ApiMethodParser.ApiMethodModel> models) throws MojoExecutionException { VelocityContext context = getCommonContext(models); context.put("enumName", getEnumName()); return context; } public File getApiMethodFile() throws MojoExecutionException { final StringBuilder fileName = new StringBuilder(); fileName.append(outPackage.replaceAll("\\.", Matcher.quoteReplacement(File.separator))).append(File.separator); fileName.append(getEnumName()).append(".java"); return new File(generatedSrcDir, fileName.toString()); } private String getEnumName() throws MojoExecutionException { String proxyClassWithCanonicalName = getProxyClassWithCanonicalName(proxyClass); return proxyClassWithCanonicalName.substring(proxyClassWithCanonicalName.lastIndexOf('.') + 1) + "ApiMethod"; } private VelocityContext getApiTestContext(List<ApiMethodParser.ApiMethodModel> models) throws MojoExecutionException { VelocityContext context = getCommonContext(models); context.put("testName", getUnitTestName()); context.put("scheme", scheme); context.put("componentPackage", componentPackage); context.put("componentName", componentName); context.put("enumName", getEnumName()); return context; } private String getTestFilePath() throws MojoExecutionException { final StringBuilder fileName = new StringBuilder(); fileName.append(componentPackage.replaceAll("\\.", Matcher.quoteReplacement(File.separator))).append(File.separator); fileName.append(getUnitTestName()).append(".java"); return fileName.toString(); } private String getUnitTestName() throws MojoExecutionException { String proxyClassWithCanonicalName = getProxyClassWithCanonicalName(proxyClass); return proxyClassWithCanonicalName.substring(proxyClassWithCanonicalName.lastIndexOf('.') + 1) + "IntegrationTest"; } private VelocityContext getEndpointContext(List<ApiMethodParser.ApiMethodModel> models) throws MojoExecutionException { VelocityContext context = getCommonContext(models); context.put("configName", getConfigName()); context.put("componentName", componentName); context.put("componentPackage", componentPackage); // generate parameter names and types for configuration, sorted by parameter name Map<String, ApiMethodArg> parameters = new TreeMap<String, ApiMethodArg>(); for (ApiMethodParser.ApiMethodModel model : models) { for (ApiMethodArg argument : model.getArguments()) { final String name = argument.getName(); final Class<?> type = argument.getType(); final String typeName = type.getCanonicalName(); if (!parameters.containsKey(name) && (propertyNamePattern == null || !propertyNamePattern.matcher(name).matches()) && (propertyTypePattern == null || !propertyTypePattern.matcher(typeName).matches())) { parameters.put(name, argument); } } } // add custom parameters if (extraOptions != null && extraOptions.length > 0) { for (ExtraOption option : extraOptions) { final String name = option.getName(); final String argWithTypes = option.getType().replaceAll(" ", ""); final int rawEnd = argWithTypes.indexOf('<'); String typeArgs = null; Class<?> argType; try { if (rawEnd != -1) { argType = getProjectClassLoader().loadClass(argWithTypes.substring(0, rawEnd)); typeArgs = argWithTypes.substring(rawEnd + 1, argWithTypes.lastIndexOf('>')); } else { argType = getProjectClassLoader().loadClass(argWithTypes); } } catch (ClassNotFoundException e) { throw new MojoExecutionException(String.format("Error loading extra option [%s %s] : %s", argWithTypes, name, e.getMessage()), e); } parameters.put(name, new ApiMethodArg(name, argType, typeArgs)); } } context.put("parameters", parameters); return context; } private File getConfigurationFile() throws MojoExecutionException { final StringBuilder fileName = new StringBuilder(); // endpoint configuration goes in component package fileName.append(componentPackage.replaceAll("\\.", Matcher.quoteReplacement(File.separator))).append(File.separator); fileName.append(getConfigName()).append(".java"); return new File(generatedSrcDir, fileName.toString()); } private String getConfigName() throws MojoExecutionException { String proxyClassWithCanonicalName = getProxyClassWithCanonicalName(proxyClass); return proxyClassWithCanonicalName.substring(proxyClassWithCanonicalName.lastIndexOf('.') + 1) + "EndpointConfiguration"; } private String getProxyClassWithCanonicalName(String proxyClass) { return proxyClass.replace("$", ""); } private VelocityContext getCommonContext(List<ApiMethodParser.ApiMethodModel> models) throws MojoExecutionException { VelocityContext context = new VelocityContext(); context.put("models", models); context.put("proxyType", getProxyType()); context.put("helper", this); return context; } public ArgumentSubstitutionParser.Substitution[] getArgumentSubstitutions() { ArgumentSubstitutionParser.Substitution[] subs = new ArgumentSubstitutionParser.Substitution[substitutions.length]; for (int i = 0; i < substitutions.length; i++) { final Substitution substitution = substitutions[i]; subs[i] = new ArgumentSubstitutionParser.Substitution(substitution.getMethod(), substitution.getArgName(), substitution.getArgType(), substitution.getReplacement(), substitution.isReplaceWithType()); } return subs; } public static String getType(Class<?> clazz) { if (clazz.isArray()) { // create a zero length array and get the class from the instance return "new " + getCanonicalName(clazz).replaceAll("\\[\\]", "[0]") + ".getClass()"; } else { return getCanonicalName(clazz) + ".class"; } } public static String getTestName(ApiMethodParser.ApiMethodModel model) { final StringBuilder builder = new StringBuilder(); final String name = model.getMethod().getName(); builder.append(Character.toUpperCase(name.charAt(0))); builder.append(name.substring(1)); // find overloaded method suffix from unique name final String uniqueName = model.getUniqueName(); if (uniqueName.length() > name.length()) { builder.append(uniqueName.substring(name.length())); } return builder.toString(); } public static boolean isVoidType(Class<?> resultType) { return resultType == Void.TYPE; } public String getExchangePropertyPrefix() { // exchange property prefix return "Camel" + componentName + "."; } public static String getResultDeclaration(Class<?> resultType) { if (resultType.isPrimitive()) { return ClassUtils.primitiveToWrapper(resultType).getSimpleName(); } else { return getCanonicalName(resultType); } } static { PRIMITIVE_VALUES = new HashMap<Class<?>, String>(); PRIMITIVE_VALUES.put(Boolean.TYPE, "Boolean.FALSE"); PRIMITIVE_VALUES.put(Byte.TYPE, "(byte) 0"); PRIMITIVE_VALUES.put(Character.TYPE, "(char) 0"); PRIMITIVE_VALUES.put(Short.TYPE, "(short) 0"); PRIMITIVE_VALUES.put(Integer.TYPE, "0"); PRIMITIVE_VALUES.put(Long.TYPE, "0L"); PRIMITIVE_VALUES.put(Float.TYPE, "0.0f"); PRIMITIVE_VALUES.put(Double.TYPE, "0.0d"); } public static String getDefaultArgValue(Class<?> aClass) { if (aClass.isPrimitive()) { // lookup default primitive value string return PRIMITIVE_VALUES.get(aClass); } else { // return type cast null string return "null"; } } public static String getBeanPropertySuffix(String parameter) { // capitalize first character StringBuilder builder = new StringBuilder(); builder.append(Character.toUpperCase(parameter.charAt(0))); builder.append(parameter.substring(1)); return builder.toString(); } public String getCanonicalName(ApiMethodArg argument) throws MojoExecutionException { // replace primitives with wrapper classes final Class<?> type = argument.getType(); if (type.isPrimitive()) { return getCanonicalName(ClassUtils.primitiveToWrapper(type)); } // get default name prefix String canonicalName = getCanonicalName(type); final String typeArgs = argument.getTypeArgs(); if (typeArgs != null) { // add generic type arguments StringBuilder parameterizedType = new StringBuilder(canonicalName); parameterizedType.append('<'); // Note: its ok to split, since we don't support parsing nested type arguments final String[] argTypes = typeArgs.split(","); boolean ignore = false; final int nTypes = argTypes.length; int i = 0; for (String argType : argTypes) { // try loading as is first try { parameterizedType.append(getCanonicalName(getProjectClassLoader().loadClass(argType))); } catch (ClassNotFoundException e) { // try loading with default java.lang package prefix try { if (log.isDebugEnabled()) { log.debug("Could not load " + argType + ", trying to load java.lang." + argType); } parameterizedType.append( getCanonicalName(getProjectClassLoader().loadClass("java.lang." + argType))); } catch (ClassNotFoundException e1) { log.warn("Ignoring type parameters <" + typeArgs + "> for argument " + argument.getName() + ", unable to load parametric type argument " + argType, e1); ignore = true; } } if (ignore) { // give up break; } else if (++i < nTypes) { parameterizedType.append(","); } } if (!ignore) { parameterizedType.append('>'); canonicalName = parameterizedType.toString(); } } return canonicalName; } }