diff options
author | Michael Hwang <mhwang@research.att.com> | 2019-11-11 14:31:28 -0500 |
---|---|---|
committer | Michael Hwang <mhwang@research.att.com> | 2019-11-18 17:06:45 -0500 |
commit | 566a0d2c4e917fd03b0126b5cc610539f3db3cb1 (patch) | |
tree | e2abec4b0a9ea77c09e7de666c3b115ae55c0845 /mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java | |
parent | 849da15d5b7ddc68e4c2b90b603fc8948d4b5e6d (diff) |
Add genprocessor project
Signed-off-by: Michael Hwang <mhwang@research.att.com>
Issue-ID: DCAEGEN2-1860
Change-Id: Ie22a30ed8c72327ff0a28afa64ae53d8d6458a0c
Diffstat (limited to 'mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java')
-rw-r--r-- | mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java new file mode 100644 index 0000000..9996b71 --- /dev/null +++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java @@ -0,0 +1,382 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2019 AT&T Intellectual Property. 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. + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.dcae.genprocessor; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.jar.Manifest; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.jar.Attributes; + +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.processor.Processor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Hello world! + * + */ +public class App { + static final Logger LOG = LoggerFactory.getLogger(App.class); + + // NOTE: For each new processor, need to: change jar command, change meta-inf + private static String createClassName(CompSpec compSpec) { + return String.format("org.onap.dcae.%s", compSpec.nameJavaClass); + } + + /** + * Does a series of DCAEProcessor specific verification checks on the generated + * class. + * + * @param cc + * @return true if verification is successful + */ + private static boolean verifyGen(CtClass cc) { + DCAEProcessor processor; + try { + processor = (DCAEProcessor) cc.toClass().newInstance(); + } catch (InstantiationException | IllegalAccessException | CannotCompileException e) { + LOG.error(e.toString(), e); + return false; + } + java.lang.annotation.Annotation[] anns = processor.getClass().getAnnotations(); + LOG.info(String.format("#Annotations on class: %d", anns.length)); + + for (java.lang.annotation.Annotation ann : anns) { + if (ann.annotationType().getName().contains("Description")) { + LOG.info(String.format("CapabilityDescription: %s", ((CapabilityDescription) ann).value())); + } else if (ann.annotationType().getName().contains("Tags")) { + LOG.info(String.format("Tags: %s", String.join(", ", ((Tags) ann).value()))); + } + } + + LOG.info(String.format("Processor getters:\nname=%s\nversion=%s\nid=%s\nselfUrl=%s", processor.getName(), + processor.getVersion(), processor.getComponentId(), processor.getComponentUrl())); + + LOG.info(String.format("#Property descriptors: %d", processor.getPropertyDescriptors().size())); + + if (processor.getPropertyDescriptors().size() > 0) { + LOG.info(processor.getPropertyDescriptors().get(0).toString()); + } + + LOG.info(String.format("#Relationships: %d", processor.getRelationships().size())); + + // Actually do checks + return true; + } + + /** + * Generates a new DCAEProcessor class given a Component object and writes the + * class file to a specified directory. + * + * @param directoryName where generated class files will get written + * @param comp Component object to generate new DCAEProcessor classes + */ + public static void generateClassFile(String directoryName, Comp comp) { + LOG.info("Generating classes"); + + try { + ClassPool pool = ClassPool.getDefault(); + CtClass base = pool.get(DCAEProcessor.class.getName()); + + CtClass cc = pool.makeClass(createClassName(comp.compSpec)); + cc.setSuperclass(base); + + String[] tags = ProcessorBuilder.createTags(comp.compSpec); + + ProcessorBuilder.addAnnotationsProcessor(cc, comp.compSpec.description, tags); + ProcessorBuilder.setComponentPropertyGetters(cc, comp); + ProcessorBuilder.setProcessorPropertyDescriptors(cc, comp.compSpec); + ProcessorBuilder.setProcessorRelationships(cc, comp.compSpec); + + if (verifyGen(cc)) { + cc.writeFile(directoryName); + } + } catch (Exception e) { + LOG.error("Uhoh", e); + } + } + + private static List<String> generateManifestMF(CompSpec compSpec) { + return Arrays.asList("Manifest-Version: 1.0", String.format("Id: %s", compSpec.name), + String.format("Version: %s", compSpec.version) + // TODO: Group is hardcoded here. Should it be? + , "Group: org.onap.dcae"); + } + + private static void writeManifestThing(File dirTarget, List<String> lines, String subDir, String fileName) { + File dirManifest = new File(dirTarget, subDir); + + if (dirManifest.exists() || dirManifest.mkdirs()) { + Path path = Paths.get(dirManifest.getAbsolutePath(), fileName); + try { + Files.write(path, lines, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + throw new RuntimeException("Could not create Manifest directory"); + } + } + + private static boolean copyProcessorClassFile(File pathClassFile, File dirBuild) { + File dirSandbox = new File(dirBuild, "org/onap/dcae/genprocessor"); + + if (dirSandbox.exists() || dirSandbox.mkdir()) { + try { + File dest = new File(dirSandbox, pathClassFile.getName()); + Files.copy(pathClassFile.toPath(), dest.toPath()); + return true; + } catch (FileAlreadyExistsException e) { + // Do nothing, class file already exists + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return false; + } + + private static File getDirectoryForJars(File dirWorking) { + return new File(dirWorking, "nifi-jars"); + } + + private static boolean packageJar(File dirWorking, File dirBuild, String jarName) { + LOG.info("Package into jar"); + + try { + File dirJars = getDirectoryForJars(dirWorking); + + if (dirJars.exists() || dirJars.mkdir()) { + String argDashC = String.format("-C %s", dirBuild.getAbsolutePath()); + String cmd = String.join(" ", new String[] { + "jar cvfm" + , String.format("%s/%s.jar", dirJars.getAbsolutePath(), jarName) + , String.format("%s/META-INF/MANIFEST.MF", dirBuild.getAbsolutePath()) + , argDashC, "org" + , argDashC, "org/onap/dcae/genprocessor" + , argDashC, "META-INF" + }); + LOG.debug(String.format("Jar command: %s", cmd)); + + if (Runtime.getRuntime().exec(cmd).waitFor() == 0) { + return true; + } + } + } catch (InterruptedException e) { + throw new RuntimeException("Error while creating jar", e); + } catch (IOException e) { + throw new RuntimeException("Error while creating jar", e); + } + + return false; + } + + private static boolean doesJarExist(File dirWorking, String jarName) { + File dirJars = getDirectoryForJars(dirWorking); + String[] jars = dirJars.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.contains(jarName); + } + }); + return jars.length > 0; + } + + /** + * Looks for the MANIFEST.MF and extracts-to-print expected values (group, id, + * version) + * + * @param classLoader + */ + private static void checkManifest(ClassLoader classLoader) { + try { + URL url = ((URLClassLoader) classLoader).findResource("META-INF/MANIFEST.MF"); + Manifest manifest = new Manifest(url.openStream()); + + final Attributes attributes = manifest.getMainAttributes(); + final String group = attributes.getValue("Group"); + final String id = attributes.getValue("Id"); + final String version = attributes.getValue("Version"); + LOG.info(String.format("group=%s, id=%s, version=%s", group, id, version)); + } catch (IOException e) { + throw new RuntimeException("Error while reading manifest", e); + } + } + + /** + * Given a URL to a index.json file, fetches the file and generates a list of + * URLs for DCAE jars that has Processors packaged. + * + * @param indexDCAEJars + * @return + */ + public static List<URL> getDCAEJarsURLs(URI indexDCAEJars) { + JsonFactory jf = new JsonFactory(); + ObjectMapper om = new ObjectMapper(); + + try { + List<Object> urls = om.readValue(jf.createParser(indexDCAEJars.toURL()), List.class); + + return urls.stream().map(u -> { + try { + Map<String, Object> foo = (Map<String, Object>) u; + String name = (String) foo.get("name"); + String url = String.format("%s/%s", indexDCAEJars.toString(), name); + return new URL(url); + } catch (MalformedURLException e) { + // Hopefully you never come here... + return null; + } + }).collect(Collectors.toList()); + } catch (Exception e) { + throw new RuntimeException("Error while getting jar URIs", e); + } + } + + /** + * Loads all the Processor classes from the list of jar URLs and does a + * validation check that prints to screen. + * + * @param jarURLs + */ + public static void loadFromJars(URL[] jarURLs) { + URLClassLoader urlClassLoader = new URLClassLoader(jarURLs); + checkManifest(urlClassLoader); + + final ServiceLoader<?> serviceLoader = ServiceLoader.load(Processor.class, urlClassLoader); + + for (final Object o : serviceLoader) { + LOG.info(o.getClass().getName()); + DCAEProcessor proc = ((DCAEProcessor) o); + proc.ping(); + LOG.info(String.format("%s: %s", proc.getName(), proc.getComponentUrl())); + } + + // TODO: Can fetch the comp spec with the component url to do further + // validation.. + } + + private static boolean init(File dirWorking) { + File dirJars = getDirectoryForJars(dirWorking); + + if (dirJars.exists() || dirJars.mkdirs()) { + return true; + } + + return false; + } + + public static void main(String[] args) { + if (args.length == 0) { + LOG.info("Here are the possible args:"); + LOG.info("<gen> <load>"); + } + + String argsStr = String.join(", ", args); + boolean shouldGenerate = argsStr.contains("gen") ? true : false; + boolean shouldLoad = argsStr.contains("load") ? true : false; + boolean shouldPackage = argsStr.contains("package") ? true : false; + + // Config from env variables + File dirWorking = new File(System.getenv("GENPROC_WORKING_DIR")); + String hostOnboardingAPI = System.getenv("GENPROC_ONBOARDING_API_HOST"); + File processorClassFile = new File(System.getenv("GENPROC_PROCESSOR_CLASSFILE_PATH")); + String urlToJarIndex = System.getenv("GENPROC_JAR_INDEX_URL"); + + String[] paramsToPrint = new String[] { + String.format("shouldGenerate=%b", shouldGenerate) + , String.format("shouldLoad=%b", shouldLoad) + , String.format("Working directory=%s", dirWorking.getName()) + }; + LOG.info(String.format("Genprocessor configuration: \n\t%s", + String.join("\n\t", paramsToPrint))); + + init(dirWorking); + + // TODO: Need a way to load from file again + + if (shouldGenerate) { + CompList compList = OnboardingAPIClient.getComponents(hostOnboardingAPI); + LOG.info(String.format("Components retrieved: %d", compList.components.size())); + + for (CompList.CompShort cs : compList.components) { + Comp comp = OnboardingAPIClient.getComponent(cs.getComponentUrlAsURI()); + LOG.info(String.format("Component spec: \n\t%s", comp.compSpec.toString("\n\t"))); + + String jarName = Utils.formatNameForJar(comp.compSpec); + + if (doesJarExist(dirWorking, jarName)) { + LOG.info(String.format("Jar exists: %s.jar", jarName)); + continue; + } + + File dirBuild = new File(dirWorking, jarName); + + if (dirBuild.exists() || dirBuild.mkdir()) { + generateClassFile(dirBuild.getAbsolutePath(), comp); + writeManifestThing(dirBuild, generateManifestMF(comp.compSpec), "META-INF", "MANIFEST.MF"); + writeManifestThing(dirBuild, Arrays.asList(createClassName(comp.compSpec)), "META-INF/services", + "org.apache.nifi.processor.Processor"); + copyProcessorClassFile(processorClassFile, dirBuild); + packageJar(dirWorking, dirBuild, jarName); + } + } + } + + if (shouldLoad) { + List<URL> jarURLs; + try { + jarURLs = getDCAEJarsURLs(new URI(urlToJarIndex)); + LOG.info(jarURLs.toString()); + } catch (URISyntaxException e) { + throw new RuntimeException("URL to index.json is bad"); + } + + for (URL jarURL : jarURLs) { + loadFromJars(new URL[] {jarURL}); + } + } + } +} |