2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
4 * Copyright (C) 2022 Huawei. All rights reserved.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 * ============LICENSE_END=========================================================
19 package org.onap.dcae.genprocessor;
21 import javassist.CannotCompileException;
22 import javassist.ClassPool;
23 import javassist.CtClass;
26 import java.io.FilenameFilter;
27 import java.io.InputStream;
28 import java.io.IOException;
29 import java.net.MalformedURLException;
31 import java.net.URISyntaxException;
33 import java.net.URLClassLoader;
34 import java.nio.charset.StandardCharsets;
35 import java.nio.file.FileAlreadyExistsException;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.util.Arrays;
40 import java.util.List;
42 import java.util.ServiceLoader;
43 import java.util.jar.Manifest;
44 import java.util.stream.Collectors;
46 import com.fasterxml.jackson.core.JsonFactory;
47 import com.fasterxml.jackson.databind.ObjectMapper;
49 import java.util.jar.Attributes;
51 import org.apache.nifi.annotation.documentation.CapabilityDescription;
52 import org.apache.nifi.annotation.documentation.Tags;
53 import org.apache.nifi.processor.Processor;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
63 static final Logger LOG = LoggerFactory.getLogger(App.class);
65 // NOTE: For each new processor, need to: change jar command, change meta-inf
66 private static String createClassName(CompSpec compSpec) {
67 return String.format("org.onap.dcae.%s", compSpec.nameJavaClass);
71 * Does a series of DCAEProcessor specific verification checks on the generated
75 * @return true if verification is successful
77 private static boolean verifyGen(CtClass cc) {
78 DCAEProcessor processor;
80 processor = (DCAEProcessor) cc.toClass().newInstance();
81 } catch (InstantiationException | IllegalAccessException | CannotCompileException e) {
82 LOG.error(e.toString(), e);
85 java.lang.annotation.Annotation[] anns = processor.getClass().getAnnotations();
86 LOG.info(String.format("#Annotations on class: %d", anns.length));
88 for (java.lang.annotation.Annotation ann : anns) {
89 if (ann.annotationType().getName().contains("Description")) {
90 LOG.info(String.format("CapabilityDescription: %s", ((CapabilityDescription) ann).value()));
91 } else if (ann.annotationType().getName().contains("Tags")) {
92 LOG.info(String.format("Tags: %s", String.join(", ", ((Tags) ann).value())));
96 LOG.info(String.format("Processor getters:\nname=%s\nversion=%s\nid=%s\nselfUrl=%s", processor.getName(),
97 processor.getVersion(), processor.getComponentId(), processor.getComponentUrl()));
99 LOG.info(String.format("#Property descriptors: %d", processor.getPropertyDescriptors().size()));
101 if (processor.getPropertyDescriptors().size() > 0) {
102 LOG.info(processor.getPropertyDescriptors().get(0).toString());
105 LOG.info(String.format("#Relationships: %d", processor.getRelationships().size()));
107 // Actually do checks
112 * Generates a new DCAEProcessor class given a Component object and writes the
113 * class file to a specified directory.
115 * @param directoryName where generated class files will get written
116 * @param comp Component object to generate new DCAEProcessor classes
118 public static void generateClassFile(String directoryName, Comp comp) {
119 LOG.info("Generating classes");
122 ClassPool pool = ClassPool.getDefault();
123 CtClass base = pool.get(DCAEProcessor.class.getName());
125 CtClass cc = pool.makeClass(createClassName(comp.compSpec));
126 cc.setSuperclass(base);
128 String[] tags = ProcessorBuilder.createTags(comp.compSpec);
130 ProcessorBuilder.addAnnotationsProcessor(cc, comp.compSpec.description, tags);
131 ProcessorBuilder.setComponentPropertyGetters(cc, comp);
132 ProcessorBuilder.setProcessorPropertyDescriptors(cc, comp.compSpec);
133 ProcessorBuilder.setProcessorRelationships(cc, comp.compSpec);
136 cc.writeFile(directoryName);
138 } catch (Exception e) {
139 LOG.error("Uhoh", e);
143 private static List<String> generateManifestMF(CompSpec compSpec) {
144 return Arrays.asList("Manifest-Version: 1.0", String.format("Id: %s", compSpec.name),
145 String.format("Version: %s", compSpec.version)
146 // TODO: Group is hardcoded here. Should it be?
147 , "Group: org.onap.dcae");
150 private static void writeManifestThing(File dirTarget, List<String> lines, String subDir, String fileName) {
151 File dirManifest = new File(dirTarget, subDir);
153 if (dirManifest.exists() || dirManifest.mkdirs()) {
154 Path path = Paths.get(dirManifest.getAbsolutePath(), fileName);
156 Files.write(path, lines, StandardCharsets.UTF_8);
157 } catch (IOException e) {
158 throw new RuntimeException(e);
161 throw new RuntimeException("Could not create Manifest directory");
165 private static boolean copyProcessorClassFile(String classResourceName, File dirBuild) {
166 File dirSandbox = new File(dirBuild, "org/onap/dcae/genprocessor");
168 if (dirSandbox.exists() || dirSandbox.mkdir()) {
169 try (InputStream asStream = App.class.getResourceAsStream(classResourceName)) {
170 File dest = new File(dirSandbox, classResourceName);
171 Files.copy(asStream, dest.toPath());
173 } catch (FileAlreadyExistsException e) {
174 // Do nothing, class file already exists
175 } catch (IOException e) {
176 throw new RuntimeException(e);
183 private static File getDirectoryForJars(File dirWorking) {
184 return new File(dirWorking, "nifi-jars");
187 private static boolean packageJar(File dirWorking, File dirBuild, String jarName) {
188 LOG.info("Package into jar");
191 File dirJars = getDirectoryForJars(dirWorking);
193 if (dirJars.exists() || dirJars.mkdir()) {
194 String argDashC = String.format("-C %s", dirBuild.getAbsolutePath());
195 String cmd = String.join(" ", new String[] {
197 , String.format("%s/%s.jar", dirJars.getAbsolutePath(), jarName)
198 , String.format("%s/META-INF/MANIFEST.MF", dirBuild.getAbsolutePath())
200 , argDashC, "org/onap/dcae/genprocessor"
201 , argDashC, "META-INF"
203 LOG.debug(String.format("Jar command: %s", cmd));
205 if (Runtime.getRuntime().exec(cmd).waitFor() == 0) {
209 } catch (InterruptedException | 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, URISyntaxException {
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) throws URISyntaxException {
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 String urlToJarIndex = System.getenv("GENPROC_JAR_INDEX_URL");
348 String[] paramsToPrint = new String[] {
349 String.format("shouldGenerate=%b", shouldGenerate)
350 , String.format("shouldLoad=%b", shouldLoad)
351 , String.format("Working directory=%s", dirWorking.getName())
353 LOG.info(String.format("Genprocessor configuration: \n\t%s",
354 String.join("\n\t", paramsToPrint)));
358 // TODO: Need a way to load from file again
360 if (shouldGenerate) {
361 CompList compList = OnboardingAPIClient.getComponents(hostOnboardingAPI);
362 LOG.info(String.format("Components retrieved: %d", compList.components.size()));
364 for (CompList.CompShort cs : compList.components) {
365 Comp comp = OnboardingAPIClient.getComponent(cs.getComponentUrlAsURI());
366 LOG.info(String.format("Component spec: \n\t%s", comp.compSpec.toString("\n\t")));
368 String jarName = Utils.formatNameForJar(comp.compSpec);
370 if (doesJarExist(dirWorking, jarName)) {
371 LOG.info(String.format("Jar exists: %s.jar", jarName));
375 File dirBuild = new File(dirWorking, jarName);
377 if (dirBuild.exists() || dirBuild.mkdir()) {
378 generateClassFile(dirBuild.getAbsolutePath(), comp);
379 writeManifestThing(dirBuild, generateManifestMF(comp.compSpec), "META-INF", "MANIFEST.MF");
380 writeManifestThing(dirBuild, Arrays.asList(createClassName(comp.compSpec)), "META-INF/services",
381 "org.apache.nifi.processor.Processor");
382 copyProcessorClassFile("DCAEProcessor.class", dirBuild);
383 packageJar(dirWorking, dirBuild, jarName);
391 jarURLs = getDCAEJarsURLs(new URI(urlToJarIndex));
392 LOG.info(jarURLs.toString());
393 } catch (URISyntaxException e) {
394 throw new RuntimeException("URL to index.json is bad");
397 for (URL jarURL : jarURLs) {
398 loadFromJars(new URL[] {jarURL});