runtimeapi & genprocessor pom.xml docker changes
[dcaegen2/platform.git] / mod / genprocessor / src / main / java / org / onap / dcae / genprocessor / App.java
1 /*-
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
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
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=========================================================
17  */
18 package org.onap.dcae.genprocessor;
19
20 import javassist.CannotCompileException;
21 import javassist.ClassPool;
22 import javassist.CtClass;
23
24 import java.io.File;
25 import java.io.FilenameFilter;
26 import java.io.IOException;
27 import java.net.MalformedURLException;
28 import java.net.URI;
29 import java.net.URISyntaxException;
30 import java.net.URL;
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;
39 import java.util.Map;
40 import java.util.ServiceLoader;
41 import java.util.jar.Manifest;
42 import java.util.stream.Collectors;
43
44 import com.fasterxml.jackson.core.JsonFactory;
45 import com.fasterxml.jackson.databind.ObjectMapper;
46
47 import java.util.jar.Attributes;
48
49 import org.apache.nifi.annotation.documentation.CapabilityDescription;
50 import org.apache.nifi.annotation.documentation.Tags;
51 import org.apache.nifi.processor.Processor;
52
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * Hello world!
58  *
59  */
60 public class App {
61     static final Logger LOG = LoggerFactory.getLogger(App.class);
62
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);
66     }
67
68     /**
69      * Does a series of DCAEProcessor specific verification checks on the generated
70      * class.
71      * 
72      * @param cc
73      * @return true if verification is successful
74      */
75     private static boolean verifyGen(CtClass cc) {
76         DCAEProcessor processor;
77         try {
78             processor = (DCAEProcessor) cc.toClass().newInstance();
79         } catch (InstantiationException | IllegalAccessException | CannotCompileException e) {
80             LOG.error(e.toString(), e);
81             return false;
82         }
83         java.lang.annotation.Annotation[] anns = processor.getClass().getAnnotations();
84         LOG.info(String.format("#Annotations on class: %d", anns.length));
85
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())));
91             }
92         }
93
94         LOG.info(String.format("Processor getters:\nname=%s\nversion=%s\nid=%s\nselfUrl=%s", processor.getName(),
95                 processor.getVersion(), processor.getComponentId(), processor.getComponentUrl()));
96
97         LOG.info(String.format("#Property descriptors: %d", processor.getPropertyDescriptors().size()));
98
99         if (processor.getPropertyDescriptors().size() > 0) {
100             LOG.info(processor.getPropertyDescriptors().get(0).toString());
101         }
102
103         LOG.info(String.format("#Relationships: %d", processor.getRelationships().size()));
104
105         // Actually do checks
106         return true;
107     }
108
109     /**
110      * Generates a new DCAEProcessor class given a Component object and writes the
111      * class file to a specified directory.
112      * 
113      * @param directoryName where generated class files will get written
114      * @param comp          Component object to generate new DCAEProcessor classes
115      */
116     public static void generateClassFile(String directoryName, Comp comp) {
117         LOG.info("Generating classes");
118
119         try {
120             ClassPool pool = ClassPool.getDefault();
121             CtClass base = pool.get(DCAEProcessor.class.getName());
122
123             CtClass cc = pool.makeClass(createClassName(comp.compSpec));
124             cc.setSuperclass(base);
125
126             String[] tags = ProcessorBuilder.createTags(comp.compSpec);
127
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);
132
133             if (verifyGen(cc)) {
134                 cc.writeFile(directoryName);
135             }
136         } catch (Exception e) {
137             LOG.error("Uhoh", e);
138         }
139     }
140
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");
146     }
147
148     private static void writeManifestThing(File dirTarget, List<String> lines, String subDir, String fileName) {
149         File dirManifest = new File(dirTarget, subDir);
150
151         if (dirManifest.exists() || dirManifest.mkdirs()) {
152             Path path = Paths.get(dirManifest.getAbsolutePath(), fileName);
153             try {
154                 Files.write(path, lines, StandardCharsets.UTF_8);
155             } catch (IOException e) {
156                 throw new RuntimeException(e);
157             }
158         } else {
159             throw new RuntimeException("Could not create Manifest directory");
160         }
161     }
162
163     private static boolean copyProcessorClassFile(File pathClassFile, File dirBuild) {
164         File dirSandbox = new File(dirBuild, "org/onap/dcae/genprocessor");
165
166         if (dirSandbox.exists() || dirSandbox.mkdir()) {
167             try {
168                 File dest = new File(dirSandbox, pathClassFile.getName());
169                 Files.copy(pathClassFile.toPath(), dest.toPath());
170                 return true;
171             } catch (FileAlreadyExistsException e) {
172                 // Do nothing, class file already exists
173             } catch (IOException e) {
174                 throw new RuntimeException(e);
175             } 
176         }
177
178         return false;
179     }
180
181     private static File getDirectoryForJars(File dirWorking) {
182         return new File(dirWorking, "nifi-jars");
183     }
184
185     private static boolean packageJar(File dirWorking, File dirBuild, String jarName) {
186         LOG.info("Package into jar");
187
188         try {
189             File dirJars = getDirectoryForJars(dirWorking);
190
191             if (dirJars.exists() || dirJars.mkdir()) {
192                 String argDashC = String.format("-C %s", dirBuild.getAbsolutePath());
193                 String cmd = String.join(" ", new String[] {
194                     "jar cvfm"
195                     , String.format("%s/%s.jar", dirJars.getAbsolutePath(), jarName)
196                     , String.format("%s/META-INF/MANIFEST.MF", dirBuild.getAbsolutePath())
197                     , argDashC, "org"
198                     , argDashC, "org/onap/dcae/genprocessor"
199                     , argDashC, "META-INF"
200                 });
201                 LOG.debug(String.format("Jar command: %s", cmd));
202
203                 if (Runtime.getRuntime().exec(cmd).waitFor() == 0) {
204                     return true;
205                 }
206             }
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);
211         }
212
213         return false;
214     }
215
216     private static boolean doesJarExist(File dirWorking, String jarName) {
217         File dirJars = getDirectoryForJars(dirWorking);
218         String[] jars = dirJars.list(new FilenameFilter() {
219             @Override
220             public boolean accept(File dir, String name) {
221                 return name.contains(jarName);
222             }
223         });
224         return jars.length > 0;
225     }
226
227     /**
228      * Looks for the MANIFEST.MF and extracts-to-print expected values (group, id,
229      * version)
230      * 
231      * @param classLoader
232      */
233     private static void checkManifest(ClassLoader classLoader) {
234         try {
235             URL url = ((URLClassLoader) classLoader).findResource("META-INF/MANIFEST.MF");
236             Manifest manifest = new Manifest(url.openStream());
237
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);
245         }
246     }
247
248     /**
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.
251      * 
252      * @param indexDCAEJars
253      * @return
254      */
255     public static List<URL> getDCAEJarsURLs(URI indexDCAEJars) {
256         JsonFactory jf = new JsonFactory();
257         ObjectMapper om = new ObjectMapper();
258
259         try {
260             List<Object> urls = om.readValue(jf.createParser(indexDCAEJars.toURL()), List.class);
261
262             return urls.stream().map(u -> {
263                 try {
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);
267                     return new URL(url);
268                 } catch (MalformedURLException e) {
269                     // Hopefully you never come here...
270                     return null;
271                 }
272             }).collect(Collectors.toList());
273         } catch (Exception e) {
274             throw new RuntimeException("Error while getting jar URIs", e);
275         }
276     }
277
278     /**
279      * Loads all the Processor classes from the list of jar URLs and does a
280      * validation check that prints to screen.
281      * 
282      * @param jarURLs
283      */
284     public static void loadFromJars(URL[] jarURLs) {
285         URLClassLoader urlClassLoader = new URLClassLoader(jarURLs);
286         checkManifest(urlClassLoader);
287
288         final ServiceLoader<?> serviceLoader = ServiceLoader.load(Processor.class, urlClassLoader);
289
290         for (final Object o : serviceLoader) {
291             LOG.info(o.getClass().getName());
292             DCAEProcessor proc = ((DCAEProcessor) o);
293             proc.ping();
294             LOG.info(String.format("%s: %s", proc.getName(), proc.getComponentUrl()));
295         }
296
297         // TODO: Can fetch the comp spec with the component url to do further
298         // validation..
299     }
300
301     private static boolean init(File dirWorking) {
302         File dirJars = getDirectoryForJars(dirWorking);
303
304         if (dirJars.exists() || dirJars.mkdirs()) {
305             return true;
306         }
307
308         return false;
309     }
310
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;
316             do {
317                 try {
318                         main2(args);
319                 } catch (Exception e) {
320                         LOG.error(e.toString(), e);
321                 }
322                 Thread.sleep(sleepdur);
323             } while (sleepdur > 0);
324             return;
325         } else {
326             main2(args);
327         }
328     }
329
330
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>");
336             return;
337         }
338
339         boolean shouldGenerate = argsStr.contains("gen") ? true : false;
340         boolean shouldLoad = argsStr.contains("load") ? true : false;
341         boolean shouldPackage = argsStr.contains("package") ? true : false;
342
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");
348
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())
353         };
354         LOG.info(String.format("Genprocessor configuration: \n\t%s",
355             String.join("\n\t", paramsToPrint)));
356
357         init(dirWorking);
358
359         // TODO: Need a way to load from file again
360
361         if (shouldGenerate) {
362             CompList compList = OnboardingAPIClient.getComponents(hostOnboardingAPI);
363             LOG.info(String.format("Components retrieved: %d", compList.components.size()));
364
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")));
368
369                 String jarName = Utils.formatNameForJar(comp.compSpec);
370
371                 if (doesJarExist(dirWorking, jarName)) {
372                     LOG.info(String.format("Jar exists: %s.jar", jarName));
373                     continue;
374                 }
375
376                 File dirBuild = new File(dirWorking, jarName);
377
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);
385                 }
386             }
387         }
388
389         if (shouldLoad) {
390             List<URL> jarURLs;
391             try {
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");
396             }
397
398             for (URL jarURL : jarURLs) {
399                 loadFromJars(new URL[] {jarURL});
400             }
401         }
402     }
403 }