2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2019 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.IOException;
27 import java.net.MalformedURLException;
29 import java.net.URISyntaxException;
31 import java.net.URLClassLoader;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.FileAlreadyExistsException;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.util.Arrays;
38 import java.util.List;
40 import java.util.ServiceLoader;
41 import java.util.jar.Manifest;
42 import java.util.stream.Collectors;
44 import com.fasterxml.jackson.core.JsonFactory;
45 import com.fasterxml.jackson.databind.ObjectMapper;
47 import java.util.jar.Attributes;
49 import org.apache.nifi.annotation.documentation.CapabilityDescription;
50 import org.apache.nifi.annotation.documentation.Tags;
51 import org.apache.nifi.processor.Processor;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
61 static final Logger LOG = LoggerFactory.getLogger(App.class);
63 // NOTE: For each new processor, need to: change jar command, change meta-inf
64 private static String createClassName(CompSpec compSpec) {
65 return String.format("org.onap.dcae.%s", compSpec.nameJavaClass);
69 * Does a series of DCAEProcessor specific verification checks on the generated
73 * @return true if verification is successful
75 private static boolean verifyGen(CtClass cc) {
76 DCAEProcessor processor;
78 processor = (DCAEProcessor) cc.toClass().newInstance();
79 } catch (InstantiationException | IllegalAccessException | CannotCompileException e) {
80 LOG.error(e.toString(), e);
83 java.lang.annotation.Annotation[] anns = processor.getClass().getAnnotations();
84 LOG.info(String.format("#Annotations on class: %d", anns.length));
86 for (java.lang.annotation.Annotation ann : anns) {
87 if (ann.annotationType().getName().contains("Description")) {
88 LOG.info(String.format("CapabilityDescription: %s", ((CapabilityDescription) ann).value()));
89 } else if (ann.annotationType().getName().contains("Tags")) {
90 LOG.info(String.format("Tags: %s", String.join(", ", ((Tags) ann).value())));
94 LOG.info(String.format("Processor getters:\nname=%s\nversion=%s\nid=%s\nselfUrl=%s", processor.getName(),
95 processor.getVersion(), processor.getComponentId(), processor.getComponentUrl()));
97 LOG.info(String.format("#Property descriptors: %d", processor.getPropertyDescriptors().size()));
99 if (processor.getPropertyDescriptors().size() > 0) {
100 LOG.info(processor.getPropertyDescriptors().get(0).toString());
103 LOG.info(String.format("#Relationships: %d", processor.getRelationships().size()));
105 // Actually do checks
110 * Generates a new DCAEProcessor class given a Component object and writes the
111 * class file to a specified directory.
113 * @param directoryName where generated class files will get written
114 * @param comp Component object to generate new DCAEProcessor classes
116 public static void generateClassFile(String directoryName, Comp comp) {
117 LOG.info("Generating classes");
120 ClassPool pool = ClassPool.getDefault();
121 CtClass base = pool.get(DCAEProcessor.class.getName());
123 CtClass cc = pool.makeClass(createClassName(comp.compSpec));
124 cc.setSuperclass(base);
126 String[] tags = ProcessorBuilder.createTags(comp.compSpec);
128 ProcessorBuilder.addAnnotationsProcessor(cc, comp.compSpec.description, tags);
129 ProcessorBuilder.setComponentPropertyGetters(cc, comp);
130 ProcessorBuilder.setProcessorPropertyDescriptors(cc, comp.compSpec);
131 ProcessorBuilder.setProcessorRelationships(cc, comp.compSpec);
134 cc.writeFile(directoryName);
136 } catch (Exception e) {
137 LOG.error("Uhoh", e);
141 private static List<String> generateManifestMF(CompSpec compSpec) {
142 return Arrays.asList("Manifest-Version: 1.0", String.format("Id: %s", compSpec.name),
143 String.format("Version: %s", compSpec.version)
144 // TODO: Group is hardcoded here. Should it be?
145 , "Group: org.onap.dcae");
148 private static void writeManifestThing(File dirTarget, List<String> lines, String subDir, String fileName) {
149 File dirManifest = new File(dirTarget, subDir);
151 if (dirManifest.exists() || dirManifest.mkdirs()) {
152 Path path = Paths.get(dirManifest.getAbsolutePath(), fileName);
154 Files.write(path, lines, StandardCharsets.UTF_8);
155 } catch (IOException e) {
156 throw new RuntimeException(e);
159 throw new RuntimeException("Could not create Manifest directory");
163 private static boolean copyProcessorClassFile(File pathClassFile, File dirBuild) {
164 File dirSandbox = new File(dirBuild, "org/onap/dcae/genprocessor");
166 if (dirSandbox.exists() || dirSandbox.mkdir()) {
168 File dest = new File(dirSandbox, pathClassFile.getName());
169 Files.copy(pathClassFile.toPath(), dest.toPath());
171 } catch (FileAlreadyExistsException e) {
172 // Do nothing, class file already exists
173 } catch (IOException e) {
174 throw new RuntimeException(e);
181 private static File getDirectoryForJars(File dirWorking) {
182 return new File(dirWorking, "nifi-jars");
185 private static boolean packageJar(File dirWorking, File dirBuild, String jarName) {
186 LOG.info("Package into jar");
189 File dirJars = getDirectoryForJars(dirWorking);
191 if (dirJars.exists() || dirJars.mkdir()) {
192 String argDashC = String.format("-C %s", dirBuild.getAbsolutePath());
193 String cmd = String.join(" ", new String[] {
195 , String.format("%s/%s.jar", dirJars.getAbsolutePath(), jarName)
196 , String.format("%s/META-INF/MANIFEST.MF", dirBuild.getAbsolutePath())
198 , argDashC, "org/onap/dcae/genprocessor"
199 , argDashC, "META-INF"
201 LOG.debug(String.format("Jar command: %s", cmd));
203 if (Runtime.getRuntime().exec(cmd).waitFor() == 0) {
207 } catch (InterruptedException e) {
208 throw new RuntimeException("Error while creating jar", e);
209 } catch (IOException e) {
210 throw new RuntimeException("Error while creating jar", e);
216 private static boolean doesJarExist(File dirWorking, String jarName) {
217 File dirJars = getDirectoryForJars(dirWorking);
218 String[] jars = dirJars.list(new FilenameFilter() {
220 public boolean accept(File dir, String name) {
221 return name.contains(jarName);
224 return jars.length > 0;
228 * Looks for the MANIFEST.MF and extracts-to-print expected values (group, id,
233 private static void checkManifest(ClassLoader classLoader) {
235 URL url = ((URLClassLoader) classLoader).findResource("META-INF/MANIFEST.MF");
236 Manifest manifest = new Manifest(url.openStream());
238 final Attributes attributes = manifest.getMainAttributes();
239 final String group = attributes.getValue("Group");
240 final String id = attributes.getValue("Id");
241 final String version = attributes.getValue("Version");
242 LOG.info(String.format("group=%s, id=%s, version=%s", group, id, version));
243 } catch (IOException e) {
244 throw new RuntimeException("Error while reading manifest", e);
249 * Given a URL to a index.json file, fetches the file and generates a list of
250 * URLs for DCAE jars that has Processors packaged.
252 * @param indexDCAEJars
255 public static List<URL> getDCAEJarsURLs(URI indexDCAEJars) {
256 JsonFactory jf = new JsonFactory();
257 ObjectMapper om = new ObjectMapper();
260 List<Object> urls = om.readValue(jf.createParser(indexDCAEJars.toURL()), List.class);
262 return urls.stream().map(u -> {
264 Map<String, Object> foo = (Map<String, Object>) u;
265 String name = (String) foo.get("name");
266 String url = String.format("%s/%s", indexDCAEJars.toString(), name);
268 } catch (MalformedURLException e) {
269 // Hopefully you never come here...
272 }).collect(Collectors.toList());
273 } catch (Exception e) {
274 throw new RuntimeException("Error while getting jar URIs", e);
279 * Loads all the Processor classes from the list of jar URLs and does a
280 * validation check that prints to screen.
284 public static void loadFromJars(URL[] jarURLs) {
285 URLClassLoader urlClassLoader = new URLClassLoader(jarURLs);
286 checkManifest(urlClassLoader);
288 final ServiceLoader<?> serviceLoader = ServiceLoader.load(Processor.class, urlClassLoader);
290 for (final Object o : serviceLoader) {
291 LOG.info(o.getClass().getName());
292 DCAEProcessor proc = ((DCAEProcessor) o);
294 LOG.info(String.format("%s: %s", proc.getName(), proc.getComponentUrl()));
297 // TODO: Can fetch the comp spec with the component url to do further
301 private static boolean init(File dirWorking) {
302 File dirJars = getDirectoryForJars(dirWorking);
304 if (dirJars.exists() || dirJars.mkdirs()) {
311 public static void main(String[] args) throws InterruptedException {
312 if (args.length == 0) {
313 args = new String[] { "gen" };
314 String sleepstr = System.getenv("GENPROC_SLEEP_SEC");
315 long sleepdur = (sleepstr != null)? 1000 * Long.parseLong(sleepstr): 0;
319 } catch (Exception e) {
320 LOG.error(e.toString(), e);
322 Thread.sleep(sleepdur);
323 } while (sleepdur > 0);
331 public static void main2(String[] args) {
332 String argsStr = String.join(", ", args);
333 if (argsStr.contains("-h")) {
334 LOG.info("Here are the possible args:");
335 LOG.info("<gen> <load>");
339 boolean shouldGenerate = argsStr.contains("gen") ? true : false;
340 boolean shouldLoad = argsStr.contains("load") ? true : false;
341 boolean shouldPackage = argsStr.contains("package") ? true : false;
343 // Config from env variables
344 File dirWorking = new File(System.getenv("GENPROC_WORKING_DIR"));
345 String hostOnboardingAPI = System.getenv("GENPROC_ONBOARDING_API_HOST");
346 File processorClassFile = new File(System.getenv("GENPROC_PROCESSOR_CLASSFILE_PATH"));
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(processorClassFile, 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});