summaryrefslogtreecommitdiffstats
path: root/mod/genprocessor/src/main
diff options
context:
space:
mode:
authorMichael Hwang <mhwang@research.att.com>2019-11-11 14:31:28 -0500
committerMichael Hwang <mhwang@research.att.com>2019-11-18 17:06:45 -0500
commit566a0d2c4e917fd03b0126b5cc610539f3db3cb1 (patch)
treee2abec4b0a9ea77c09e7de666c3b115ae55c0845 /mod/genprocessor/src/main
parent849da15d5b7ddc68e4c2b90b603fc8948d4b5e6d (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')
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java382
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Comp.java35
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompList.java70
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompSpec.java144
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/DCAEProcessor.java92
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/OnboardingAPIClient.java65
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/ProcessorBuilder.java195
-rw-r--r--mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Utils.java44
-rw-r--r--mod/genprocessor/src/main/resources/logback.xml16
9 files changed, 1043 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});
+ }
+ }
+ }
+}
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Comp.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Comp.java
new file mode 100644
index 0000000..69d8776
--- /dev/null
+++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Comp.java
@@ -0,0 +1,35 @@
+/*-
+ * ============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 com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Comp {
+
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("spec")
+ public CompSpec compSpec;
+
+ @JsonProperty("selfUrl")
+ public String selfUrl;
+
+} \ No newline at end of file
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompList.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompList.java
new file mode 100644
index 0000000..fcf25b4
--- /dev/null
+++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompList.java
@@ -0,0 +1,70 @@
+/*-
+ * ============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 java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CompList {
+
+ static final Logger LOG = LoggerFactory.getLogger(CompList.class);
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class CompShort {
+ @JsonProperty("id")
+ public String id;
+ @JsonProperty("name")
+ public String name;
+ @JsonProperty("version")
+ public String version;
+ @JsonProperty("description")
+ public String description;
+ @JsonProperty("componentType")
+ public String componentType;
+ @JsonProperty("owner")
+ public String owner;
+ @JsonProperty("componentUrl")
+ public String componentUrl;
+ @JsonProperty("whenAdded")
+ public String whenAdded;
+
+ public String getNameForJavaClass() {
+ return Utils.formatNameForJavaClass(this.name);
+ }
+
+ public URI getComponentUrlAsURI() {
+ try {
+ return new URI(this.componentUrl);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Component URL is bad");
+ }
+ }
+ }
+
+ @JsonProperty("components")
+ public List<CompShort> components;
+
+}
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompSpec.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompSpec.java
new file mode 100644
index 0000000..34b7398
--- /dev/null
+++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompSpec.java
@@ -0,0 +1,144 @@
+/*-
+ * ============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 java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CompSpec {
+
+ static final Logger LOG = LoggerFactory.getLogger(App.class);
+
+ public String name;
+ // Name of component to be transformed to be more Java style
+ public String nameJavaClass;
+ public String version;
+ public String description;
+
+ // https://stackoverflow.com/questions/37010891/how-to-map-a-nested-value-to-a-property-using-jackson-annotations
+ @JsonProperty("self")
+ public void unpackSelf(Map<String, String> self) {
+ this.name = self.get("name");
+ this.nameJavaClass = Utils.formatNameForJavaClass(self.get("name"));
+ this.version = self.get("version");
+ this.description = self.get("description");
+ }
+
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Parameter {
+ @JsonProperty("name")
+ public String name;
+ @JsonProperty("value")
+ public String value;
+ @JsonProperty("description")
+ public String description;
+ @JsonProperty("sourced_at_deployment")
+ public boolean sourcedAtDeployment;
+ @JsonProperty("policy_editable")
+ public boolean policyEditable;
+ @JsonProperty("designer_editable")
+ public boolean designerEditable;
+
+ public String toString() {
+ String[] params = new String[] {
+ String.format("name: \"%s\"", this.name)
+ , String.format("value: \"%s\"", this.value)
+ , String.format("description: \"%s\"", this.description)
+ };
+ return String.join(", ", params);
+ }
+ }
+
+ @JsonProperty("parameters")
+ public List<Parameter> parameters;
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Connection {
+ @JsonProperty("format")
+ public String format;
+ @JsonProperty("version")
+ public String version;
+ @JsonProperty("type")
+ public String type;
+ @JsonProperty("config_key")
+ public String configKey;
+ }
+
+ @JsonProperty("streams")
+ public Map<String, List<Connection>> streams;
+
+ public List<Connection> getPublishes() {
+ return streams.containsKey("publishes") ? streams.get("publishes") : null;
+ }
+
+ public List<Connection> getSubscribes() {
+ return streams.containsKey("subscribes") ? streams.get("subscribes") : null;
+ }
+
+ public String toString(String delimiter) {
+ List<String> items = new ArrayList();
+ items.add(String.format("name: %s", name));
+ items.add(String.format("version: %s", version));
+ items.add(String.format("description: %s", description));
+ items.add(String.format("parameters: %d", parameters.size()));
+
+ if (!parameters.isEmpty()) {
+ // Cap at MAX
+ int MAX=parameters.size() > 3 ? 3 : parameters.size();
+ for (int i=0; i<MAX; i++) {
+ items.add(String.format("\t%s", parameters.get(i).toString()));
+ }
+ }
+
+ items.add("\t..");
+
+ return String.join(delimiter, items.toArray(new String[items.size()]));
+ }
+
+ public static CompSpec loadComponentSpec(File compSpecFile) {
+ return loadComponentSpec(compSpecFile.toURI());
+ }
+
+ public static CompSpec loadComponentSpec(URI compSpecURI) {
+ JsonFactory jf = new JsonFactory();
+ ObjectMapper om = new ObjectMapper();
+
+ try {
+ return om.readValue(jf.createParser(compSpecURI.toURL()), CompSpec.class);
+ } catch (Exception e) {
+ LOG.error("Uhoh", e);
+ }
+
+ throw new RuntimeException("REPLACE ME");
+ }
+
+}
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/DCAEProcessor.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/DCAEProcessor.java
new file mode 100644
index 0000000..b920b90
--- /dev/null
+++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/DCAEProcessor.java
@@ -0,0 +1,92 @@
+/*-
+ * ============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 java.util.List;
+import java.util.Set;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class DCAEProcessor extends AbstractProcessor {
+
+ static final Logger LOG = LoggerFactory.getLogger(DCAEProcessor.class);
+
+ // These are properties of the DCAE component that may be useful in the future..
+ abstract public String getName();
+ abstract public String getVersion();
+ abstract public String getComponentId();
+ abstract public String getComponentUrl();
+
+ public void ping() {
+ LOG.info("pong");
+ }
+
+ @Override
+ public void onTrigger(ProcessContext arg0, ProcessSession arg1) throws ProcessException {
+ LOG.info("Bang you triggered DCAEProcessor!");
+ return;
+ }
+
+ /**
+ * This function gets implemented by the ProcessorBuilder magic to build a new custom list of
+ * PropertyDescriptor every time. This is to be used by getSupportedPropertyDescriptors() which
+ * *should* only call this method once to initially fill the cache.
+ *
+ * @return list of PropertyDescriptor
+ */
+ abstract protected List<PropertyDescriptor> buildSupportedPropertyDescriptors();
+
+ // Cache of PropertyDescriptors which should be static
+ private List<PropertyDescriptor> properties;
+
+ /**
+ * This is the critical Nifi function that is used to populate the configuration parameters
+ * in the UI and drive all the other configuration related functions in the ConfigurableComponent
+ * base class
+ */
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ if (this.properties == null) {
+ this.properties = buildSupportedPropertyDescriptors();
+ }
+ return this.properties;
+ }
+
+ abstract protected Set<Relationship> buildRelationships();
+
+ // Cache of Relationships which should be static
+ private Set<Relationship> relationships;
+
+ @Override
+ public Set<Relationship> getRelationships() {
+ if (this.relationships == null) {
+ this.relationships = buildRelationships();
+ }
+ return this.relationships;
+ }
+
+} \ No newline at end of file
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/OnboardingAPIClient.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/OnboardingAPIClient.java
new file mode 100644
index 0000000..12c3726
--- /dev/null
+++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/OnboardingAPIClient.java
@@ -0,0 +1,65 @@
+/*-
+ * ============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 java.net.URI;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OnboardingAPIClient {
+
+ static final Logger LOG = LoggerFactory.getLogger(OnboardingAPIClient.class);
+
+ public static CompList getComponents(String hostOnboardingAPI) {
+ JsonFactory jf = new JsonFactory();
+ ObjectMapper om = new ObjectMapper();
+
+ try {
+ URI uri = new URI(hostOnboardingAPI + "/components");
+ return om.readValue(jf.createParser(uri.toURL()), CompList.class);
+ } catch (Exception e) {
+ String message = "Error while pulling components from onboarding API";
+ LOG.error(message, e);
+ throw new OnboardingAPIClientError(message, e);
+ }
+ }
+
+ public static Comp getComponent(URI componentUri) {
+ JsonFactory jf = new JsonFactory();
+ ObjectMapper om = new ObjectMapper();
+
+ try {
+ return om.readValue(jf.createParser(componentUri.toURL()), Comp.class);
+ } catch (Exception e) {
+ String message = "Error while pulling component from onboarding API";
+ LOG.error(message, e);
+ throw new OnboardingAPIClientError(message, e);
+ }
+ }
+
+ public static class OnboardingAPIClientError extends RuntimeException {
+ public OnboardingAPIClientError(String message, Throwable exception) {
+ super(message, exception);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/ProcessorBuilder.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/ProcessorBuilder.java
new file mode 100644
index 0000000..ca87bda
--- /dev/null
+++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/ProcessorBuilder.java
@@ -0,0 +1,195 @@
+/*-
+ * ============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.CtClass;
+import javassist.CtMethod;
+import javassist.bytecode.AnnotationsAttribute;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.ConstPool;
+import javassist.bytecode.annotation.Annotation;
+import javassist.bytecode.annotation.ArrayMemberValue;
+import javassist.bytecode.annotation.MemberValue;
+import javassist.bytecode.annotation.StringMemberValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.text.StringEscapeUtils;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProcessorBuilder {
+
+ static final Logger LOG = LoggerFactory.getLogger(ProcessBuilder.class);
+
+ public static class ProcessorBuilderError extends RuntimeException {
+ public ProcessorBuilderError(Throwable e) {
+ super("Error while generating DCAEProcessor", e);
+ }
+ }
+
+ private static Annotation createAnnotationDescription(String description, ConstPool constPool) {
+ // https://www.codota.com/code/java/packages/javassist.bytecode showed me that
+ // the constructor
+ // adds a UTF8 object thing so I'm guessing that the index value when doing
+ // addMemberValue
+ // should match that of the newly added object otherwise you get a nullpointer
+ Annotation annDescrip = new Annotation(CapabilityDescription.class.getName(), constPool);
+ // Tried to use the index version of addMemberValue with index of
+ // constPool.getSize()-1
+ // but didn't work
+ annDescrip.addMemberValue("value", new StringMemberValue(description, constPool));
+ return annDescrip;
+ }
+
+ private static Annotation createAnnotationTags(String[] tags, ConstPool constPool) {
+ Annotation annTags = new Annotation(Tags.class.getName(), constPool);
+ ArrayMemberValue mv = new ArrayMemberValue(constPool);
+
+ List<MemberValue> elements = new ArrayList<MemberValue>();
+ for (String tag : tags) {
+ elements.add(new StringMemberValue(tag, constPool));
+ }
+
+ mv.setValue(elements.toArray(new MemberValue[elements.size()]));
+ // Tried to use the index version of addMemberValue with index of
+ // constPool.getSize()-1
+ // but didn't work
+ annTags.addMemberValue("value", mv);
+ return annTags;
+ }
+
+ public static String[] createTags(CompSpec compSpec) {
+ List<String> tags = new ArrayList<>();
+ tags.add("DCAE");
+
+ // TODO: Need to source type from spec
+ if (compSpec.name.toLowerCase().contains("collector")) {
+ tags.add("collector");
+ }
+
+ if (!compSpec.getPublishes().isEmpty()) {
+ tags.add("publisher");
+ }
+
+ if (!compSpec.getSubscribes().isEmpty()) {
+ tags.add("subscriber");
+ }
+
+ String[] tagArray = new String[tags.size()];
+ return tags.toArray(tagArray);
+ }
+
+ public static void addAnnotationsProcessor(CtClass target, String description, String[] tags) {
+ ClassFile ccFile = target.getClassFile();
+ ConstPool constPool = ccFile.getConstPool();
+
+ AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
+ attr.addAnnotation(createAnnotationDescription(description, constPool));
+ attr.addAnnotation(createAnnotationTags(tags, constPool));
+
+ ccFile.addAttribute(attr);
+ }
+
+ private static void addMethod(CtClass target, String methodCode) {
+ try {
+ CtMethod method = CtMethod.make(methodCode, target);
+ target.addMethod(method);
+ } catch (CannotCompileException e) {
+ LOG.error(String.format("Issue with this code:\n%s", methodCode));
+ LOG.error(e.toString(), e);
+ throw new ProcessorBuilderError(e);
+ }
+ }
+
+ private static String createCodeGetter(String methodName, String returnValue) {
+ return String.format("public java.lang.String get%s() { return \"%s\"; }", methodName, returnValue);
+ }
+
+ public static void setComponentPropertyGetters(CtClass target, Comp comp) {
+ addMethod(target, createCodeGetter("Name", comp.compSpec.name));
+ addMethod(target, createCodeGetter("Version", comp.compSpec.version));
+ addMethod(target, createCodeGetter("ComponentId", comp.id));
+ addMethod(target, createCodeGetter("ComponentUrl", comp.selfUrl));
+ }
+
+ private static String convertParameterToCode(CompSpec.Parameter param) {
+ StringBuilder sb = new StringBuilder("props.add(new org.apache.nifi.components.PropertyDescriptor.Builder()");
+ sb.append(String.format(".name(\"%s\")", param.name));
+ sb.append(String.format(".displayName(\"%s\")", param.name));
+ sb.append(String.format(".description(\"%s\")", StringEscapeUtils.escapeJava(param.description)));
+ sb.append(String.format(".defaultValue(\"%s\")", StringEscapeUtils.escapeJava(param.value)));
+ sb.append(".build());");
+ return sb.toString();
+ }
+
+ private static String createCodePropertyDescriptors(CompSpec compSpec) {
+ List<String> linesParams = compSpec.parameters.stream().map(p -> convertParameterToCode(p)).collect(Collectors.toList());
+
+ // NOTE: Generics are only partially supported https://www.javassist.org/tutorial/tutorial3.html#generics
+ String[] lines = new String[] {"protected java.util.List buildSupportedPropertyDescriptors() {"
+ , "java.util.List props = new java.util.LinkedList();"
+ , String.join("\n", linesParams.toArray(new String[linesParams.size()]))
+ , "return props; }"
+ };
+
+ return String.join("\n", lines);
+ }
+
+ public static void setProcessorPropertyDescriptors(CtClass target, CompSpec compSpec) {
+ addMethod(target, createCodePropertyDescriptors(compSpec));
+ }
+
+ private static String createRelationshipName(CompSpec.Connection connection, String direction) {
+ // TODO: Revisit this name thing ugh
+ return String.format("%s:%s:%s:%s:%s",
+ direction, connection.format.toLowerCase(), connection.version, connection.type, connection.configKey);
+ }
+
+ private static String convertConnectionToCode(CompSpec.Connection connection, String direction) {
+ StringBuilder sb = new StringBuilder("rels.add(new org.apache.nifi.processor.Relationship.Builder()");
+ sb.append(String.format(".name(\"%s\")", createRelationshipName(connection, direction)));
+ sb.append(".build());");
+ return sb.toString();
+ }
+
+ private static String createCodeRelationships(CompSpec compSpec) {
+ List<String> linesPubs = compSpec.getPublishes().stream().map(c -> convertConnectionToCode(c, "publishes")).collect(Collectors.toList());
+ List<String> linesSubs = compSpec.getSubscribes().stream().map(c -> convertConnectionToCode(c, "subscribes")).collect(Collectors.toList());
+
+ String [] lines = new String[] {"protected java.util.Set buildRelationships() {"
+ , "java.util.Set rels = new java.util.HashSet();"
+ , String.join("\n", linesPubs.toArray(new String[linesPubs.size()]))
+ , String.join("\n", linesSubs.toArray(new String[linesSubs.size()]))
+ , "return rels; }"
+ };
+
+ return String.join("\n", lines);
+ }
+
+ public static void setProcessorRelationships(CtClass target, CompSpec compSpec) {
+ addMethod(target, createCodeRelationships(compSpec));
+ }
+
+} \ No newline at end of file
diff --git a/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Utils.java b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Utils.java
new file mode 100644
index 0000000..9a7c50a
--- /dev/null
+++ b/mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Utils.java
@@ -0,0 +1,44 @@
+/*-
+ * ============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;
+
+public class Utils {
+
+ /**
+ * Make a name like this "dcae-ves-collector" to "DcaeVesCollector"
+ *
+ * @param name
+ * @return
+ */
+ public static String formatNameForJavaClass(String name) {
+ // From the sample of 134 specs, 79 had dashes and 102 had dots which means some
+ // had both
+ String[] segments = name.split("[\\-\\.]");
+
+ for (int i=0; i<segments.length; i++) {
+ segments[i] = segments[i].substring(0, 1).toUpperCase() + segments[i].substring(1);
+ }
+
+ return String.join("", segments);
+ }
+
+ public static String formatNameForJar(CompSpec compSpec) {
+ return String.format("%s-%s", compSpec.name, compSpec.version);
+ }
+
+} \ No newline at end of file
diff --git a/mod/genprocessor/src/main/resources/logback.xml b/mod/genprocessor/src/main/resources/logback.xml
new file mode 100644
index 0000000..fdd8cf0
--- /dev/null
+++ b/mod/genprocessor/src/main/resources/logback.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <layout class="ch.qos.logback.classic.PatternLayout">
+ <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
+ </layout>
+ </appender>
+
+ <logger name="sandbox" level="TRACE"/>
+
+
+ <root level="debug">
+ <appender-ref ref="STDOUT" />
+ </root>
+</configuration> \ No newline at end of file