diff options
20 files changed, 1508 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b842f5a --- /dev/null +++ b/.gitignore @@ -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 @@ -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 index 0000000..55d73d9 --- /dev/null +++ b/mod/genprocessor/.dockerignore @@ -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 index 0000000..57b5cf9 --- /dev/null +++ b/mod/genprocessor/README.md @@ -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 index 0000000..47bdc14 --- /dev/null +++ b/mod/genprocessor/docker/README.md @@ -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 index 0000000..0cafbf4 --- /dev/null +++ b/mod/genprocessor/docker/http/Dockerfile @@ -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 index 0000000..bd53c07 --- /dev/null +++ b/mod/genprocessor/docker/http/nginx.conf @@ -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 index 0000000..45ff3e9 --- /dev/null +++ b/mod/genprocessor/docker/http/start.sh @@ -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 index 0000000..b70a06c --- /dev/null +++ b/mod/genprocessor/docker/job/Dockerfile @@ -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 index 0000000..b28c888 --- /dev/null +++ b/mod/genprocessor/pom.xml @@ -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 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 diff --git a/mod/genprocessor/src/test/java/sandbox/AppTest.java b/mod/genprocessor/src/test/java/sandbox/AppTest.java new file mode 100644 index 0000000..3ce8f1a --- /dev/null +++ b/mod/genprocessor/src/test/java/sandbox/AppTest.java @@ -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 ); + } +} |