Merge "Add mod/runtimeapi"
authorVijay Venkatesh Kumar <vv770d@att.com>
Wed, 11 Dec 2019 20:54:46 +0000 (20:54 +0000)
committerGerrit Code Review <gerrit@onap.org>
Wed, 11 Dec 2019 20:54:46 +0000 (20:54 +0000)
20 files changed:
.gitignore [new file with mode: 0644]
INFO.yaml
mod/genprocessor/.dockerignore [new file with mode: 0644]
mod/genprocessor/README.md [new file with mode: 0644]
mod/genprocessor/docker/README.md [new file with mode: 0644]
mod/genprocessor/docker/http/Dockerfile [new file with mode: 0644]
mod/genprocessor/docker/http/nginx.conf [new file with mode: 0644]
mod/genprocessor/docker/http/start.sh [new file with mode: 0755]
mod/genprocessor/docker/job/Dockerfile [new file with mode: 0644]
mod/genprocessor/pom.xml [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/App.java [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Comp.java [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompList.java [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/CompSpec.java [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/DCAEProcessor.java [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/OnboardingAPIClient.java [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/ProcessorBuilder.java [new file with mode: 0644]
mod/genprocessor/src/main/java/org/onap/dcae/genprocessor/Utils.java [new file with mode: 0644]
mod/genprocessor/src/main/resources/logback.xml [new file with mode: 0644]
mod/genprocessor/src/test/java/sandbox/AppTest.java [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b842f5a
--- /dev/null
@@ -0,0 +1,34 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+target
+.classpath
+.project
+.settings
+.vscode
+
+.env
+
+.idea
+*.iml
index 6051910..8bfe0e3 100644 (file)
--- a/INFO.yaml
+++ b/INFO.yaml
@@ -28,32 +28,7 @@ meetings:
       repeats: 'weekly'
       time: '13:00 UTC (DST), 15:30 UTC (post DST)'
 repositories:
-    - 'dcaegen2'
-    - 'dcaegen2-analytics'
-    - 'dcaegen2-analytics-flink'
-    - 'dcaegen2-analytics-pnda'
-    - 'dcaegen2-analytics-tca'
-    - 'dcaegen2-analytics-tca-gen2'
-    - 'dcaegen2-collectors'
-    - 'dcaegen2-collectors-datafile'
-    - 'dcaegen2-collectors-hv-ves'
-    - 'dcaegen2-collectors-snmptrap'
-    - 'dcaegen2-collectors-ves'
-    - 'dcaegen2-deployments'
-    - 'dcaegen2-platform-blueprints'
-    - 'dcaegen2-platform-cdapbroker'
-    - 'dcaegen2-platform-cli'
-    - 'dcaegen2-platform-configbinding'
-    - 'dcaegen2-platform-deployment-handler'
-    - 'dcaegen2-platform-inventory-api'
-    - 'dcaegen2-platform-plugins'
-    - 'dcaegen2-platform-policy-handler'
-    - 'dcaegen2-platform-registrator'
-    - 'dcaegen2-platform-servicechange-handler'
-    - 'dcaegen2-services-heartbeat'
-    - 'dcaegen2-services-mapper'
-    - 'dcaegen2-services-pm-mapper'
-    - 'dcaegen2-services-prh'
+    - 'dcaegen2/platform'
 committers:
     - <<: *onap_dcaegen2_ptl
     - name: 'Lusheng Ji'
@@ -86,5 +61,14 @@ committers:
       company: 'ATT'
       id: 'jflucas'
       timezone: 'America/New_York'
+    - name: 'Joseph O Leary'
+      email: 'joseph.o.leary@est.tech'
+      company: 'EST'
+      id: 'JoeOLeary'
+      timezone: 'Ireland/UTC'
 tsc:
     approval: 'https://lists.onap.org/pipermail/onap-tsc'
+    changes:
+        - type: 'Addition'
+          name: 'Joseph O Leary'
+          link: 'https://lists.onap.org/g/onap-tsc/message/5715'
diff --git a/mod/genprocessor/.dockerignore b/mod/genprocessor/.dockerignore
new file mode 100644 (file)
index 0000000..55d73d9
--- /dev/null
@@ -0,0 +1,5 @@
+target
+.git
+Dockerfile
+docker
+*.md
diff --git a/mod/genprocessor/README.md b/mod/genprocessor/README.md
new file mode 100644 (file)
index 0000000..57b5cf9
--- /dev/null
@@ -0,0 +1,84 @@
+# Genprocessor
+
+This project is a tool to experiment with generating a Nifi Processor POJO from a DCAE component spec.
+
+Environment variables needed to run the app:
+
+For generating -
+
+`GENPROC_WORKING_DIR` - Full file path to the directory where you will generate class files to and ultimately build the jar to distribute
+`GENPROC_ONBOARDING_API_HOST` - Onboarding API host URL
+`GENPROC_PROCESSOR_CLASSFILE_PATH` - Path to the DCAEProcessor class file
+
+For loading -
+
+`GENPROC_JAR_INDEX_URL` - URL to the index.json for DCAE processor jars
+
+## Build
+
+NOTE: You need a specific version of the `nifi-api` jar that contains the class `BaseDCAEProcessor`.
+
+Command to build and to copy dependencies into `target/dependency` directory:
+
+```
+mvn clean package dependency:copy-dependencies
+```
+
+## Run - Generate jars
+
+This will pull all component specs from onboarding API and for each component:
+
+* A class file is generated for a new DCAEProcessor class
+* Write metadata into META-INF directory
+* Copy a copy of the DCAEProcessor class file
+* Package up into a jar 
+
+Command to run:
+
+```
+java -cp "target/genprocessor-1.0.1.jar:target/dependency/*" org.onap.dcae.genprocessor.App gen
+```
+
+### More about what goes into META-INF
+
+#### Processor manifest
+
+Note the META-INF directory which contains:
+
+```
+$ tree META-INF/
+META-INF/
+└── services
+    └── org.apache.nifi.processor.Processor
+```
+
+If you don't have the above in your `GENPROC_TARGET_DIR`, then:
+
+```
+$ mkdir -p META-INF/services
+$ touch META-INF/services/org.apache.nifi.processor.Processor
+```
+
+Open `META-INF/services/org.apache.nifi.processor.Processor` and write the full class name for each generated processor on a separate line.
+
+#### MANIFEST.MF
+
+Write the `MANIFEST.MF` in a file that's arbitrarily named (mymanifest for example).  The content should look like:
+
+```
+$ cat mymanifest 
+Manifest-Version: 1.0
+Id: dcae-ves-collector
+Version: 1.5.0
+Group: org.onap.dcae
+```
+
+## Run - Load jars
+
+This will load all jars listed on an index page and for each jar will do a class load and quick test.
+
+Command to run:
+
+```
+java -cp "target/genprocessor-1.0.1.jar:target/dependency/*" org.onap.dcae.genprocessor.App load
+```
\ No newline at end of file
diff --git a/mod/genprocessor/docker/README.md b/mod/genprocessor/docker/README.md
new file mode 100644 (file)
index 0000000..47bdc14
--- /dev/null
@@ -0,0 +1,51 @@
+# Genprocessor: Docker
+
+`http` - http server that serves up the DCAE Nifi jars as files under the path `/nifi-jars`
+`job` - background job that continuously polls the onboarding API for components and generates jars from components
+
+The usage here will assume the use of a docker volume to persist data.
+
+Create a volume:
+
+```
+docker volume create genprocessor
+```
+
+## job
+
+Build:
+
+```
+$ cd ../
+$ docker build -t genprocessor-job -f docker/job/Dockerfile .
+```
+
+Run:
+
+```
+docker run -v genprocessor:/work -e GENPROC_ONBOARDING_API_HOST=http://some-hostname/onboarding -d genprocessor-job
+```
+
+NOTE: Above onboarding API is to the one running in iLab.
+
+Run as part of the stack:
+
+```
+docker run -v genprocessor:/work --link onboarding-api:onboarding-api -d genprocessor-job
+```
+
+## http
+
+Build:
+
+```
+$ cd http
+$ docker build -t genprocessor-http .
+```
+
+Run:
+
+```
+$ docker run -p 8080:80 -d -v genprocessor:/www/data:ro genprocessor-http
+```
+
diff --git a/mod/genprocessor/docker/http/Dockerfile b/mod/genprocessor/docker/http/Dockerfile
new file mode 100644 (file)
index 0000000..0cafbf4
--- /dev/null
@@ -0,0 +1,6 @@
+FROM nginx:latest
+
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+COPY start.sh /code/start.sh
+
+CMD /code/start.sh
diff --git a/mod/genprocessor/docker/http/nginx.conf b/mod/genprocessor/docker/http/nginx.conf
new file mode 100644 (file)
index 0000000..bd53c07
--- /dev/null
@@ -0,0 +1,22 @@
+server {
+    listen       80;
+    server_name  localhost;
+
+    location / {
+        root   /usr/share/nginx/html;
+        index  index.html index.htm;
+    }
+
+    # redirect server error pages to the static page /50x.html
+    #
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+        root   /usr/share/nginx/html;
+    }
+
+    location /nifi-jars {
+        root /www/data;
+        autoindex on;
+        autoindex_format json;
+    }
+}
diff --git a/mod/genprocessor/docker/http/start.sh b/mod/genprocessor/docker/http/start.sh
new file mode 100755 (executable)
index 0000000..45ff3e9
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+# ============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=========================================================
+
+if [ -d "/www/data/nifi-jars" ]; then
+    nginx -g "daemon off;"
+else
+    echo "\"/www/data/nifi-jars\" directory missing"
+    echo "You must perform a volume mount to this directory in the container"
+    exit 1
+fi
diff --git a/mod/genprocessor/docker/job/Dockerfile b/mod/genprocessor/docker/job/Dockerfile
new file mode 100644 (file)
index 0000000..b70a06c
--- /dev/null
@@ -0,0 +1,15 @@
+FROM maven:3-jdk-8
+
+COPY . /code
+WORKDIR /code
+RUN mvn package dependency:copy-dependencies
+ENV GENPROC_WORKING_DIR=/work
+ENV GENPROC_ONBOARDING_API_HOST=http://onboarding-api/onboarding
+ENV GENPROC_PROCESSOR_CLASSFILE_PATH=/code/target/classes/sandbox/DCAEProcessor.class
+ENV GENPROC_SLEEP_SEC=10
+
+ENV _RUN_COMMAND="java -cp \"target/genprocessor-1.0.1.jar:target/dependency/*\" sandbox.App gen"
+RUN printf "#!/bin/bash\nwhile true\ndo\n\t$_RUN_COMMAND\n\tsleep $GENPROC_SLEEP_SEC\ndone" > /code/run.sh \
+    & chmod +x /code/run.sh
+
+CMD /code/run.sh
diff --git a/mod/genprocessor/pom.xml b/mod/genprocessor/pom.xml
new file mode 100644 (file)
index 0000000..b28c888
--- /dev/null
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.onap.dcaegen2.platform.mod</groupId>
+  <artifactId>genprocessor</artifactId>
+  <version>1.0.0</version>
+
+  <name>dcaegen2-platform-mod-genprocessor</name>
+  <!-- FIXME change it to the project's website -->
+  <url>http://www.example.com</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
+  </properties>
+
+  <dependencies>
+    <!--NOTE: Nifi jars used here are version 1.9.2 but dcae mod is on 1.9.3 because 1.9.3 is not in
+         Maven Central -->
+    <dependency>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-api</artifactId>
+        <version>1.9.2</version>
+    </dependency>
+    <dependency>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-record-serialization-service-api</artifactId>
+        <version>1.9.2</version>
+    </dependency>
+    <dependency>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-record</artifactId>
+        <version>1.9.2</version>
+    </dependency>
+    <dependency>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-processor-utils</artifactId>
+        <version>1.9.2</version>
+    </dependency>
+    <dependency>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-utils</artifactId>
+        <version>1.9.2</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.11</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.javassist</groupId>
+      <artifactId>javassist</artifactId>
+      <version>3.25.0-GA</version>
+    </dependency>
+    <dependency>
+        <groupId>ch.qos.logback</groupId>
+        <artifactId>logback-classic</artifactId>
+        <version>1.2.3</version>
+    </dependency>
+    <dependency>
+        <groupId>com.fasterxml.jackson.core</groupId>
+        <artifactId>jackson-core</artifactId>
+        <version>2.10.0.pr1</version>
+    </dependency>
+    <dependency>
+        <groupId>com.fasterxml.jackson.core</groupId>
+        <artifactId>jackson-databind</artifactId>
+        <version>2.10.0.pr1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-text</artifactId>
+      <version>1.7</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
+      <plugins>
+        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
+        <plugin>
+          <artifactId>maven-clean-plugin</artifactId>
+          <version>3.1.0</version>
+        </plugin>
+        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
+        <plugin>
+          <artifactId>maven-resources-plugin</artifactId>
+          <version>3.0.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.8.0</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.22.1</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>3.0.2</version>
+          <configuration>
+            <archive>
+              <manifest>
+                <addClasspath>true</addClasspath>
+                <classpathPrefix>lib/</classpathPrefix>
+                <mainClass>org.onap.dcae.genprocessor.App</mainClass>
+              </manifest>
+            </archive>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-install-plugin</artifactId>
+          <version>2.5.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-deploy-plugin</artifactId>
+          <version>2.8.2</version>
+          <configuration>
+            <skip>true</skip>
+          </configuration>
+        </plugin>
+        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
+        <plugin>
+          <artifactId>maven-site-plugin</artifactId>
+          <version>3.7.1</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-project-info-reports-plugin</artifactId>
+          <version>3.0.0</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-assembly-plugin</artifactId>
+          <executions>
+            <execution>
+              <phase>package</phase>
+              <goals>
+                <goal>single</goal>
+              </goals>
+            </execution>
+          </executions>
+          <configuration>
+            <descriptorRefs>
+              <descriptorRef>jar-with-dependencies</descriptorRef>
+            </descriptorRefs>
+            <archive>
+              <manifest>
+                <addClasspath>true</addClasspath>
+                <classpathPrefix>lib/</classpathPrefix>
+                <mainClass>org.onap.dcae.genprocessor.App</mainClass>
+              </manifest>
+            </archive>
+          </configuration>
+        </plugin>
+        <!-- THIS DOES NOT RUN-->
+        <plugin>
+          <artifactId>maven-dependency-plugin</artifactId>
+          <executions>
+            <execution>
+              <phase>package</phase>
+              <goals>
+                <goal>copy-dependencies</goal>
+              </goals>
+              <configuration>
+                <outputDirectory>${project.build.directory}/lib</outputDirectory>
+              </configuration>
+            </execution>
+          </executions>
+         </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+</project>
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 (file)
index 0000000..9996b71
--- /dev/null
@@ -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 (file)
index 0000000..69d8776
--- /dev/null
@@ -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 (file)
index 0000000..fcf25b4
--- /dev/null
@@ -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 (file)
index 0000000..34b7398
--- /dev/null
@@ -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 (file)
index 0000000..b920b90
--- /dev/null
@@ -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 (file)
index 0000000..12c3726
--- /dev/null
@@ -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 (file)
index 0000000..ca87bda
--- /dev/null
@@ -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 (file)
index 0000000..9a7c50a
--- /dev/null
@@ -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 (file)
index 0000000..fdd8cf0
--- /dev/null
@@ -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
diff --git a/mod/genprocessor/src/test/java/sandbox/AppTest.java b/mod/genprocessor/src/test/java/sandbox/AppTest.java
new file mode 100644 (file)
index 0000000..3ce8f1a
--- /dev/null
@@ -0,0 +1,37 @@
+/*-
+ * ============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 sandbox;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest 
+{
+    /**
+     * Rigorous Test :-)
+     */
+    @Test
+    public void shouldAnswerWithTrue()
+    {
+        assertTrue( true );
+    }
+}