2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ============LICENSE_END=========================================================
18 package org.onap.dcae.genprocessor;
20 import javassist.CannotCompileException;
21 import javassist.ClassPool;
22 import javassist.CtClass;
25 import java.io.FilenameFilter;
26 import java.io.InputStream;
27 import java.io.IOException;
28 import java.net.MalformedURLException;
30 import java.net.URISyntaxException;
32 import java.net.URLClassLoader;
33 import java.nio.charset.StandardCharsets;
34 import java.nio.file.FileAlreadyExistsException;
35 import java.nio.file.Files;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.util.Arrays;
39 import java.util.List;
41 import java.util.ServiceLoader;
42 import java.util.jar.Manifest;
43 import java.util.stream.Collectors;
45 import com.fasterxml.jackson.core.JsonFactory;
46 import com.fasterxml.jackson.databind.ObjectMapper;
48 import java.util.jar.Attributes;
50 import org.apache.nifi.annotation.documentation.CapabilityDescription;
51 import org.apache.nifi.annotation.documentation.Tags;
52 import org.apache.nifi.processor.Processor;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
62 static final Logger LOG = LoggerFactory.getLogger(App.class);
64 // NOTE: For each new processor, need to: change jar command, change meta-inf
65 private static String createClassName(CompSpec compSpec) {
66 return String.format("org.onap.dcae.%s", compSpec.nameJavaClass);
70 * Does a series of DCAEProcessor specific verification checks on the generated
74 * @return true if verification is successful
76 private static boolean verifyGen(CtClass cc) {
77 DCAEProcessor processor;
79 processor = (DCAEProcessor) cc.toClass().newInstance();
80 } catch (InstantiationException | IllegalAccessException | CannotCompileException e) {
81 LOG.error(e.toString(), e);
84 java.lang.annotation.Annotation[] anns = processor.getClass().getAnnotations();
85 LOG.info(String.format("#Annotations on class: %d", anns.length));
87 for (java.lang.annotation.Annotation ann : anns) {
88 if (ann.annotationType().getName().contains("Description")) {
89 LOG.info(String.format("CapabilityDescription: %s", ((CapabilityDescription) ann).value()));
90 } else if (ann.annotationType().getName().contains("Tags")) {
91 LOG.info(String.format("Tags: %s", String.join(", ", ((Tags) ann).value())));
95 LOG.info(String.format("Processor getters:\nname=%s\nversion=%s\nid=%s\nselfUrl=%s", processor.getName(),
96 processor.getVersion(), processor.getComponentId(), processor.getComponentUrl()));
98 LOG.info(String.format("#Property descriptors: %d", processor.getPropertyDescriptors().size()));
100 if (processor.getPropertyDescriptors().size() > 0) {
101 LOG.info(processor.getPropertyDescriptors().get(0).toString());
104 LOG.info(String.format("#Relationships: %d", processor.getRelationships().size()));
106 // Actually do checks
111 * Generates a new DCAEProcessor class given a Component object and writes the
112 * class file to a specified directory.
114 * @param directoryName where generated class files will get written
115 * @param comp Component object to generate new DCAEProcessor classes
117 public static void generateClassFile(String directoryName, Comp comp) {
118 LOG.info("Generating classes");
121 ClassPool pool = ClassPool.getDefault();
122 CtClass base = pool.get(DCAEProcessor.class.getName());
124 CtClass cc = pool.makeClass(createClassName(comp.compSpec));
125 cc.setSuperclass(base);
127 String[] tags = ProcessorBuilder.createTags(comp.compSpec);
129 ProcessorBuilder.addAnnotationsProcessor(cc, comp.compSpec.description, tags);
130 ProcessorBuilder.setComponentPropertyGetters(cc, comp);
131 ProcessorBuilder.setProcessorPropertyDescriptors(cc, comp.compSpec);
132 ProcessorBuilder.setProcessorRelationships(cc, comp.compSpec);
135 cc.writeFile(directoryName);
137 } catch (Exception e) {
138 LOG.error("Uhoh", e);
142 private static List<String> generateManifestMF(CompSpec compSpec) {
143 return Arrays.asList("Manifest-Version: 1.0", String.format("Id: %s", compSpec.name),
144 String.format("Version: %s", compSpec.version)
145 // TODO: Group is hardcoded here. Should it be?
146 , "Group: org.onap.dcae");
149 private static void writeManifestThing(File dirTarget, List<String> lines, String subDir, String fileName) {
150 File dirManifest = new File(dirTarget, subDir);
152 if (dirManifest.exists() || dirManifest.mkdirs()) {
153 Path path = Paths.get(dirManifest.getAbsolutePath(), fileName);
155 Files.write(path, lines, StandardCharsets.UTF_8);
156 } catch (IOException e) {
157 throw new RuntimeException(e);
160 throw new RuntimeException("Could not create Manifest directory");
164 private static boolean copyProcessorClassFile(String classResourceName, File dirBuild) {
165 File dirSandbox = new File(dirBuild, "org/onap/dcae/genprocessor");
167 if (dirSandbox.exists() || dirSandbox.mkdir()) {
168 try (InputStream asStream = App.class.getResourceAsStream(classResourceName)) {
169 File dest = new File(dirSandbox, classResourceName);
170 Files.copy(asStream, dest.toPath());
172 } catch (FileAlreadyExistsException e) {
173 // Do nothing, class file already exists
174 } catch (IOException e) {
175 throw new RuntimeException(e);
182 private static File getDirectoryForJars(File dirWorking) {
183 return new File(dirWorking, "nifi-jars");
186 private static boolean packageJar(File dirWorking, File dirBuild, String jarName) {
187 LOG.info("Package into jar");
190 File dirJars = getDirectoryForJars(dirWorking);
192 if (dirJars.exists() || dirJars.mkdir()) {
193 String argDashC = String.format("-C %s", dirBuild.getAbsolutePath());
194 String cmd = String.join(" ", new String[] {
196 , String.format("%s/%s.jar", dirJars.getAbsolutePath(), jarName)
197 , String.format("%s/META-INF/MANIFEST.MF", dirBuild.getAbsolutePath())
199 , argDashC, "org/onap/dcae/genprocessor"
200 , argDashC, "META-INF"
202 LOG.debug(String.format("Jar command: %s", cmd));
204 if (Runtime.getRuntime().exec(cmd).waitFor() == 0) {
208 } catch (InterruptedException e) {
209 throw new RuntimeException("Error while creating jar", e);
210 } catch (IOException e) {
211 throw new RuntimeException("Error while creating jar", e);
217 private static boolean doesJarExist(File dirWorking, String jarName) {
218 File dirJars = getDirectoryForJars(dirWorking);
219 String[] jars = dirJars.list(new FilenameFilter() {
221 public boolean accept(File dir, String name) {
222 return name.contains(jarName);
225 return jars.length > 0;
229 * Looks for the MANIFEST.MF and extracts-to-print expected values (group, id,
234 private static void checkManifest(ClassLoader classLoader) {
236 URL url = ((URLClassLoader) classLoader).findResource("META-INF/MANIFEST.MF");
237 Manifest manifest = new Manifest(url.openStream());
239 final Attributes attributes = manifest.getMainAttributes();
240 final String group = attributes.getValue("Group");
241 final String id = attributes.getValue("Id");
242 final String version = attributes.getValue("Version");
243 LOG.info(String.format("group=%s, id=%s, version=%s", group, id, version));
244 } catch (IOException e) {
245 throw new RuntimeException("Error while reading manifest", e);
250 * Given a URL to a index.json file, fetches the file and generates a list of
251 * URLs for DCAE jars that has Processors packaged.
253 * @param indexDCAEJars
256 public static List<URL> getDCAEJarsURLs(URI indexDCAEJars) {
257 JsonFactory jf = new JsonFactory();
258 ObjectMapper om = new ObjectMapper();
261 List<Object> urls = om.readValue(jf.createParser(indexDCAEJars.toURL()), List.class);
263 return urls.stream().map(u -> {
265 Map<String, Object> foo = (Map<String, Object>) u;
266 String name = (String) foo.get("name");
267 String url = String.format("%s/%s", indexDCAEJars.toString(), name);
269 } catch (MalformedURLException e) {
270 // Hopefully you never come here...
273 }).collect(Collectors.toList());
274 } catch (Exception e) {
275 throw new RuntimeException("Error while getting jar URIs", e);
280 * Loads all the Processor classes from the list of jar URLs and does a
281 * validation check that prints to screen.
285 public static void loadFromJars(URL[] jarURLs) {
286 URLClassLoader urlClassLoader = new URLClassLoader(jarURLs);
287 checkManifest(urlClassLoader);
289 final ServiceLoader<?> serviceLoader = ServiceLoader.load(Processor.class, urlClassLoader);
291 for (final Object o : serviceLoader) {
292 LOG.info(o.getClass().getName());
293 DCAEProcessor proc = ((DCAEProcessor) o);
295 LOG.info(String.format("%s: %s", proc.getName(), proc.getComponentUrl()));
298 // TODO: Can fetch the comp spec with the component url to do further
302 private static boolean init(File dirWorking) {
303 File dirJars = getDirectoryForJars(dirWorking);
305 if (dirJars.exists() || dirJars.mkdirs()) {
312 public static void main(String[] args) throws InterruptedException, URISyntaxException {
313 if (args.length == 0) {
314 args = new String[] { "gen" };
315 String sleepstr = System.getenv("GENPROC_SLEEP_SEC");
316 long sleepdur = (sleepstr != null)? 1000 * Long.parseLong(sleepstr): 0;
320 } catch (Exception e) {
321 LOG.error(e.toString(), e);
323 Thread.sleep(sleepdur);
324 } while (sleepdur > 0);
332 public static void main2(String[] args) throws URISyntaxException {
333 String argsStr = String.join(", ", args);
334 if (argsStr.contains("-h")) {
335 LOG.info("Here are the possible args:");
336 LOG.info("<gen> <load>");
340 boolean shouldGenerate = argsStr.contains("gen") ? true : false;
341 boolean shouldLoad = argsStr.contains("load") ? true : false;
342 boolean shouldPackage = argsStr.contains("package") ? true : false;
344 // Config from env variables
345 File dirWorking = new File(System.getenv("GENPROC_WORKING_DIR"));
346 String hostOnboardingAPI = System.getenv("GENPROC_ONBOARDING_API_HOST");
347 String urlToJarIndex = System.getenv("GENPROC_JAR_INDEX_URL");
349 String[] paramsToPrint = new String[] {
350 String.format("shouldGenerate=%b", shouldGenerate)
351 , String.format("shouldLoad=%b", shouldLoad)
352 , String.format("Working directory=%s", dirWorking.getName())
354 LOG.info(String.format("Genprocessor configuration: \n\t%s",
355 String.join("\n\t", paramsToPrint)));
359 // TODO: Need a way to load from file again
361 if (shouldGenerate) {
362 CompList compList = OnboardingAPIClient.getComponents(hostOnboardingAPI);
363 LOG.info(String.format("Components retrieved: %d", compList.components.size()));
365 for (CompList.CompShort cs : compList.components) {
366 Comp comp = OnboardingAPIClient.getComponent(cs.getComponentUrlAsURI());
367 LOG.info(String.format("Component spec: \n\t%s", comp.compSpec.toString("\n\t")));
369 String jarName = Utils.formatNameForJar(comp.compSpec);
371 if (doesJarExist(dirWorking, jarName)) {
372 LOG.info(String.format("Jar exists: %s.jar", jarName));
376 File dirBuild = new File(dirWorking, jarName);
378 if (dirBuild.exists() || dirBuild.mkdir()) {
379 generateClassFile(dirBuild.getAbsolutePath(), comp);
380 writeManifestThing(dirBuild, generateManifestMF(comp.compSpec), "META-INF", "MANIFEST.MF");
381 writeManifestThing(dirBuild, Arrays.asList(createClassName(comp.compSpec)), "META-INF/services",
382 "org.apache.nifi.processor.Processor");
383 copyProcessorClassFile("DCAEProcessor.class", dirBuild);
384 packageJar(dirWorking, dirBuild, jarName);
392 jarURLs = getDCAEJarsURLs(new URI(urlToJarIndex));
393 LOG.info(jarURLs.toString());
394 } catch (URISyntaxException e) {
395 throw new RuntimeException("URL to index.json is bad");
398 for (URL jarURL : jarURLs) {
399 loadFromJars(new URL[] {jarURL});