From f2614e766147fd345058f137852eeaf892181fc1 Mon Sep 17 00:00:00 2001 From: Joey Sullivan Date: Fri, 22 Dec 2017 15:32:48 +0000 Subject: Automate Updates to the API Client Library Removed client-kit generated code from git repo. This code is now generated under the target folder. client-kit now uses latest appc-provider-lcm.yang on the branch. Change-Id: I7a20774b60bb447343886997c32ca3b3c5dcbafa Issue-ID: APPC-360 Signed-off-by: Joey Sullivan --- .../org/onap/appc/tools/generator/api/CLI.java | 4 +- .../appc/tools/generator/api/ContextBuilder.java | 4 +- .../onap/appc/tools/generator/api/MavenPlugin.java | 86 +++++++++- .../extensions/JsonContextBuilderImpl.java | 11 +- .../extensions/YangContextBuilderImpl.java | 190 +++++++++++++++++---- .../appc/tools/generator/impl/CodeGenWriter.java | 5 +- .../appc/tools/generator/impl/ModelGenerator.java | 9 +- .../resources/configuration/client-kit.properties | 2 +- .../templates/open-api/yang-to-open-api.ftl | 9 +- 9 files changed, 259 insertions(+), 61 deletions(-) (limited to 'appc-client/code-generator') diff --git a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/CLI.java b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/CLI.java index e28b3706a..bcd68c8ab 100644 --- a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/CLI.java +++ b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/CLI.java @@ -26,6 +26,8 @@ package org.onap.appc.tools.generator.api; import org.onap.appc.tools.generator.impl.ModelGenerator; +import java.io.File; + public class CLI { public static void main(String... args) throws Exception { String sourceFile = args[0]; @@ -61,6 +63,6 @@ public class CLI { + " "+builderName + " '"); ModelGenerator generator = new ModelGenerator(); - generator.execute(sourceFile, destinationFile, templateFile, builderName, contextConfName); + generator.execute((new File(sourceFile)).toURI().toURL(), destinationFile, templateFile, builderName, contextConfName); } } diff --git a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/ContextBuilder.java b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/ContextBuilder.java index a1826f270..e2831dec8 100644 --- a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/ContextBuilder.java +++ b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/ContextBuilder.java @@ -24,11 +24,11 @@ package org.onap.appc.tools.generator.api; -import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URL; import java.util.Map; public interface ContextBuilder { - Map buildContext(String sourceFile, String contextConf) throws IOException; + Map buildContext(URL sourceURL, String contextConf) throws IOException; } diff --git a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/MavenPlugin.java b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/MavenPlugin.java index 9e160eed2..d26170774 100644 --- a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/MavenPlugin.java +++ b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/api/MavenPlugin.java @@ -24,7 +24,6 @@ package org.onap.appc.tools.generator.api; -import org.onap.appc.tools.generator.impl.ModelGenerator; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -32,12 +31,17 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.onap.appc.tools.generator.impl.ModelGenerator; +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Paths; @Mojo( - name = "generate", + name = "generate-sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES ) public class MavenPlugin extends AbstractMojo { @@ -65,20 +69,84 @@ public class MavenPlugin extends AbstractMojo { ModelGenerator generator = new ModelGenerator(); try { trace("\t === Called MavenPlugin on builder <" + contextBuilderClassName +">\n"); - generator.execute(sourceFileName,outputFileName,templateName,contextBuilderClassName,contextConfigFileName); - String workDirectory = getWorkDirectory(outputFileName); + + //the source file may be in the class path + //or on the file system + URL sourceFileURL = lookupURL(sourceFileName); + + //prefix with the project absolute path to the output file + outputFileName = toAbsoluteFile(outputFileName); + String workDirectory = Paths.get(outputFileName).getParent().toString(); + generator.execute(sourceFileURL,outputFileName,templateName,contextBuilderClassName,contextConfigFileName); project.addCompileSourceRoot(workDirectory); } catch (Exception e) { - e.printStackTrace(); - throw new MojoExecutionException(e.getMessage()); + throw new MojoExecutionException(e.getMessage(),e); } } - private String getWorkDirectory(String outputFileName) throws IOException { - String workDirPath = Paths.get(outputFileName.toString()).getParent().toString(); - return workDirPath; + + /** + * Converts the file to absolute path. If the file does not exist prefix the maven project absolute path. + * @param filePath + * @return + */ + private String toAbsoluteFile(String filePath){ + + File file = new File(filePath); + + //if the file already exist just return the absolutePath + if(file.exists()){ + return file.getAbsolutePath(); + } + + + //prefix with the project absolute path to the output file + if(!file.isAbsolute()){ + File projectDir = new File(this.project.getBuild().getDirectory()).getParentFile(); + filePath = projectDir.getAbsolutePath() + "/" + filePath; + } + + return filePath; } + /** + * Tries three lookups + * First try to lookup the file in the classpath. + * else try relative path + * else try prefixing the relative path with the maven project path. + + * @param filePath - A String denoting the source yang file path. + * @return URL - to the source yang file + * @throws MalformedURLException + */ + private URL lookupURL(String filePath) throws IOException { + //check out the class path first + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + URL sourceYangURL = classLoader.getResource(filePath); + + if (sourceYangURL != null) { + return sourceYangURL; + } + + String errorMessage = String.format( + "YANG file <%s> not found in classpath or on the file system." + ,filePath + ); + + //check the file system first + File sourceFile = new File(toAbsoluteFile(filePath)); + if (!sourceFile.exists()) { + throw new FileNotFoundException(errorMessage); + } + try { + sourceYangURL = sourceFile.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IOException(errorMessage,e); + } + return sourceYangURL; + } + + private void trace(String message) { getLog().info(message); } diff --git a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/JsonContextBuilderImpl.java b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/JsonContextBuilderImpl.java index 6c408dd1d..34471ff4e 100644 --- a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/JsonContextBuilderImpl.java +++ b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/JsonContextBuilderImpl.java @@ -24,14 +24,14 @@ package org.onap.appc.tools.generator.extensions; -import org.onap.appc.tools.generator.api.ContextBuilder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.onap.appc.tools.generator.api.ContextBuilder; -import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -39,15 +39,18 @@ import java.util.Properties; public class JsonContextBuilderImpl implements ContextBuilder { @Override - public Map buildContext(String sourceFile, String contextConf) throws IOException { + public Map buildContext(URL sourceURL, String contextConf) throws IOException { //read json file ObjectMapper mapper = new ObjectMapper(); - JsonNode model = mapper.readTree(new File(sourceFile)); + JsonNode model = mapper.readTree(sourceURL); //get context config file Properties properties = new Properties(); ClassLoader classloader = Thread.currentThread().getContextClassLoader(); InputStream inputStream = classloader.getResourceAsStream(contextConf); + if(inputStream == null){ + throw new IOException(String.format("The file [%s] cannot be found in the class path",contextConf)); + } properties.load(inputStream); //get context related properties diff --git a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/YangContextBuilderImpl.java b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/YangContextBuilderImpl.java index 67055c757..6307d1365 100644 --- a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/YangContextBuilderImpl.java +++ b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/extensions/YangContextBuilderImpl.java @@ -24,74 +24,190 @@ package org.onap.appc.tools.generator.extensions; -import org.onap.appc.tools.generator.api.ContextBuilder; import com.google.common.base.Optional; - +import org.onap.appc.tools.generator.api.ContextBuilder; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException; import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver; -import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.ModuleEffectiveStatementImpl; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; import java.net.URL; -import java.util.Map; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import java.util.Set; public class YangContextBuilderImpl implements ContextBuilder { @Override - public Map buildContext(String sourceFile, String contextConf) throws FileNotFoundException { - try ( InputStream source = new FileInputStream(sourceFile) ) {} - catch ( IOException ex) { - throw new FileNotFoundException("YANG file <" + sourceFile + ">not found"); - } + public Map buildContext(URL sourceYangURL, String contextConf) throws IOException { + Optional sc = null; try ( YangTextSchemaContextResolver yangContextResolver = YangTextSchemaContextResolver.create("yang-context-resolver")) { - yangContextResolver.registerSource(new URL("file:///" + sourceFile)); + yangContextResolver.registerSource(sourceYangURL); sc = yangContextResolver.getSchemaContext(); } catch (SchemaSourceException | IOException | YangSyntaxErrorException e) { - e.printStackTrace(); + throw new IOException(String.format("Exception occurred while processing sourceFileName %s ",sourceYangURL),e); } Map map = new HashMap<>(); if ( null != sc && sc.isPresent()) { Set modules = sc.get().getModules(); - for (Module module : modules) { - ModuleEffectiveStatementImpl impl = (ModuleEffectiveStatementImpl) module; - map.put("module", module); + for (final Module module : modules) { + + Module proxyModule = (new PackagePrivateReflectBug()).buildProxyObjects(module); + map.put("module", proxyModule); } } return map; } - // @Override - // public Map buildContext(String sourceFile, String - // contextConf) throws FileNotFoundException { - // InputStream source = new FileInputStream(sourceFile); - // if (source == null) { - // throw new FileNotFoundException("YANG file <" + sourceFile + ">not found"); - // } - // - // SchemaContext mSchema = parse(Collections.singletonList(source)); - // - // Map map = new HashMap<>(); - // map.put("module", mSchema.getModules().iterator().next()); - // return map; - // } - // - // private SchemaContext parse(List sources) { - // YangParserImpl parser = new YangParserImpl(); - // Set modules = parser.parseYangModelsFromStreams(sources); - // return parser.resolveSchemaContext(modules); - // } + /** + * The Wrap Proxy uses java Proxy to work around bug JDK-8193172. The Issue is that if a super class is package + * private its public methods cause IllegalAccessException when using java reflect to access those methods from a + * subclass which is package public. + *

+ * Example exception: + * Caused by: java.lang.IllegalAccessException: Class freemarker.ext.beans.BeansWrapper cannot access a + * member of class org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.AbstractEffectiveModule + * with modifiers "public final" + */ + private static class PackagePrivateReflectBug { + + /** + * Recursive method that traverses the yang model and wraps objects that extend package private classes + * with java proxy objects. + * @param candidateObj + * @param + * @return + */ + private T buildProxyObjects(Object candidateObj) { + + //can't proxy null so just return + if (candidateObj == null) { + return null; + } + + + Class candidateClass = candidateObj.getClass(); + + //prevent a recursive proxy + if (Proxy.isProxyClass(candidateClass)) { + return (T) candidateObj; + } + + //Can�t create a proxy an Array, so replace each + //element the array with a proxy if needed. + if (candidateClass.isArray()) { + Object[] sourceArray = (Object[]) candidateObj; + for (int i = 0; i < sourceArray.length; i++) { + sourceArray[i] = buildProxyObjects(sourceArray[i]); + } + return (T) candidateObj; + } + + //Evaluate if this class must be wrapped in a proxy or not. + if (!isCandidateForProxy(candidateClass)) { + return (T) candidateObj; + } + + //Collect the interfaces for the proxy. Proxy only work with classes + //that implement interfaces if there are none the obj cannot be wrapped + //with a proxy. + HashSet> interfaceSet = new HashSet<>(); + collectInterfaces(candidateClass, interfaceSet); + if (interfaceSet.isEmpty()) { + return (T) candidateObj; + } + Class[] interfaces = new Class[interfaceSet.size()]; + interfaceSet.toArray(interfaces); + + //wrap the Object in a proxy + return (T) Proxy.newProxyInstance( + candidateClass.getClassLoader(), + interfaces, + (proxy, method, args) -> { + Object returnObj = method.invoke(candidateObj, args); + returnObj = buildProxyObjects(returnObj); + return returnObj; + } + ); + } + + + /** + * This method determines if the specified class is a candidate for proxy. + * + * @param fromClass + * @return true - if the specifed class is a Candidate. + */ + private boolean isCandidateForProxy(Class fromClass) { + + //a + Class[] includeClasses = { + java.util.Collection.class, + java.util.Map.class, + org.opendaylight.yangtools.yang.model.api.Module.class + + }; + + for (Class toClass : includeClasses) { + if (toClass.isAssignableFrom(fromClass)) { + return true; + } + } + + if (isExtendsPackagePrivateSuperClass(fromClass)) { + return true; + } + + return false; + } + + /** + * Test if any of the super packages are package private. + * + * @param clazz + * @return + */ + private boolean isExtendsPackagePrivateSuperClass(Class clazz) { + if (Object.class.equals(clazz)) { + return false; + } + + int classModifiers = clazz.getModifiers(); + if (Modifier.isPublic(classModifiers)) { + return isExtendsPackagePrivateSuperClass(clazz.getSuperclass()); + } + + return true; + } + + /** + * Collect all of the interfaces this class can be cast too. Tavers the class hierarchy to include interfaces + * from the super classes. + * + * @param clazz + * @param classSet + */ + private void collectInterfaces(Class clazz, Set> classSet) { + Class[] interfaces = clazz.getInterfaces(); + classSet.addAll(Arrays.asList(interfaces)); + Class superClass = clazz.getSuperclass(); + + // + if (!Object.class.equals(superClass)) { + collectInterfaces(superClass, classSet); + } + } + } } diff --git a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/CodeGenWriter.java b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/CodeGenWriter.java index 347c24c06..30ed2cabf 100644 --- a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/CodeGenWriter.java +++ b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/CodeGenWriter.java @@ -24,7 +24,10 @@ package org.onap.appc.tools.generator.impl; -import java.io.*; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/ModelGenerator.java b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/ModelGenerator.java index 2275fdac7..e888aa804 100644 --- a/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/ModelGenerator.java +++ b/appc-client/code-generator/src/main/java/org/onap/appc/tools/generator/impl/ModelGenerator.java @@ -24,13 +24,14 @@ package org.onap.appc.tools.generator.impl; -import org.onap.appc.tools.generator.api.ContextBuilder; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; +import org.onap.appc.tools.generator.api.ContextBuilder; import java.io.IOException; import java.io.Writer; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -38,10 +39,10 @@ import java.util.Map; public class ModelGenerator { - public void execute(String sourceFile, String destinationFile, String templateFile, String builderName, String contextConfName) throws IOException, ReflectiveOperationException { + public void execute(URL sourceFileURL, String destinationFile, String templateFile, String builderName, String contextConfName) throws IOException, ReflectiveOperationException { ContextBuilder contextBuilder = (ContextBuilder) Class.forName(builderName).newInstance(); - Map context = contextBuilder.buildContext(sourceFile, contextConfName); + Map context = contextBuilder.buildContext(sourceFileURL, contextConfName); Path destinationPath = Paths.get(destinationFile); if (!Files.isDirectory(destinationPath)) @@ -68,4 +69,6 @@ public class ModelGenerator { } } + } + diff --git a/appc-client/code-generator/src/main/resources/configuration/client-kit.properties b/appc-client/code-generator/src/main/resources/configuration/client-kit.properties index a0097c9fa..a2f73508a 100644 --- a/appc-client/code-generator/src/main/resources/configuration/client-kit.properties +++ b/appc-client/code-generator/src/main/resources/configuration/client-kit.properties @@ -28,7 +28,7 @@ #-------------------------------------------------------------------------------------------- ctx.model.package=org.onap.appc.client.lcm.model ctx.api.package=org.onap.appc.client.lcm.api -ctx.utils.package=org.onap.appc.client.lcm.utils +ctx.utils.package=org.onap.appc ctx.exceptions.package=org.onap.appc.client.lcm.exceptions ctx.interface.classname=LifeCycleManagerStateful diff --git a/appc-client/code-generator/src/main/resources/templates/open-api/yang-to-open-api.ftl b/appc-client/code-generator/src/main/resources/templates/open-api/yang-to-open-api.ftl index 685efcb02..e92ee3b05 100644 --- a/appc-client/code-generator/src/main/resources/templates/open-api/yang-to-open-api.ftl +++ b/appc-client/code-generator/src/main/resources/templates/open-api/yang-to-open-api.ftl @@ -21,7 +21,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property. ============LICENSE_END========================================================= --> - +<#setting number_format="computer"> <#global _ = " "> <#global __ = _ + _> <#global ___ = __ + _> @@ -162,11 +162,11 @@ ${indent}] statement or locally (in-line withing a leaf node definition). --> <#macro constraints yangType indent = ""> -<#if yangType.patternConstraints?size != 0> +<#if yangType.patternConstraints?has_content> ${indent}, ${indent}"pattern" : "${yangType.patternConstraints?first.regularExpression?replace('\\\\', '\\\\\\\\', 'r')}" -<#if yangType.lengthConstraints?size != 0> +<#if yangType.lengthConstraints?has_content> ${indent}, ${indent}"minLength" : ${yangType.lengthConstraints?first.min}, ${indent}"maxLength" : ${yangType.lengthConstraints?first.max} @@ -234,9 +234,12 @@ ${_}"swagger": "2.0", ${_}"info": { ${__}"version": "${module.QNameModule.formattedRevision}" <@description obj = module indent = __ />, +<#if module.contact??> ${__}"contact": { ${_____}"name" : "${module.contact}" ${__}}, + + ${__}"title": "${moduleName}" ${_}}, ${_}"basePath": "/restconf", -- cgit 1.2.3-korg