CodeCoverage improvement for dcaegen2-platform-mod-genprocessor
[dcaegen2/platform.git] / mod / genprocessor / src / main / java / org / onap / dcae / genprocessor / ProcessorBuilder.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
4  * Copyright (C) 2022 Huawei. All rights reserved.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  * 
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  * 
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  * ============LICENSE_END=========================================================
18  */
19 package org.onap.dcae.genprocessor;
20
21 import javassist.CannotCompileException;
22 import javassist.CtClass;
23 import javassist.CtMethod;
24 import javassist.bytecode.AnnotationsAttribute;
25 import javassist.bytecode.ClassFile;
26 import javassist.bytecode.ConstPool;
27 import javassist.bytecode.annotation.Annotation;
28 import javassist.bytecode.annotation.ArrayMemberValue;
29 import javassist.bytecode.annotation.MemberValue;
30 import javassist.bytecode.annotation.StringMemberValue;
31
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.stream.Collectors;
35
36 import org.apache.commons.text.StringEscapeUtils;
37 import org.apache.nifi.annotation.documentation.CapabilityDescription;
38 import org.apache.nifi.annotation.documentation.Tags;
39
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 public class ProcessorBuilder {
44
45     static final Logger LOG = LoggerFactory.getLogger(ProcessBuilder.class);
46
47     public static class ProcessorBuilderError extends RuntimeException {
48         public ProcessorBuilderError(Throwable e) {
49             super("Error while generating DCAEProcessor", e);
50         }
51     }
52
53     private static Annotation createAnnotationDescription(String description, ConstPool constPool) {
54         // https://www.codota.com/code/java/packages/javassist.bytecode showed me that
55         // the constructor
56         // adds a UTF8 object thing so I'm guessing that the index value when doing
57         // addMemberValue
58         // should match that of the newly added object otherwise you get a nullpointer
59         Annotation annDescrip = new Annotation(CapabilityDescription.class.getName(), constPool);
60         // Tried to use the index version of addMemberValue with index of
61         // constPool.getSize()-1
62         // but didn't work
63         annDescrip.addMemberValue("value", new StringMemberValue(description, constPool));
64         return annDescrip;
65     }
66
67     private static Annotation createAnnotationTags(String[] tags, ConstPool constPool) {
68         Annotation annTags = new Annotation(Tags.class.getName(), constPool);
69         ArrayMemberValue mv = new ArrayMemberValue(constPool);
70
71         List<MemberValue> elements = new ArrayList<MemberValue>();
72         for (String tag : tags) {
73             elements.add(new StringMemberValue(tag, constPool));
74         }
75
76         mv.setValue(elements.toArray(new MemberValue[elements.size()]));
77         // Tried to use the index version of addMemberValue with index of
78         // constPool.getSize()-1
79         // but didn't work
80         annTags.addMemberValue("value", mv);
81         return annTags;
82     }
83
84     public static String[] createTags(CompSpec compSpec) {
85         List<String> tags = new ArrayList<>();
86         tags.add("DCAE");
87
88         // TODO: Need to source type from spec
89         if (compSpec.name.toLowerCase().contains("collector")) {
90             tags.add("collector");
91         }
92
93         if (!compSpec.getPublishes().isEmpty()) {
94             tags.add("publisher");
95         }
96
97         if (!compSpec.getSubscribes().isEmpty()) {
98             tags.add("subscriber");
99         }
100
101         String[] tagArray = new String[tags.size()];
102         return tags.toArray(tagArray);
103     }
104
105     public static void addAnnotationsProcessor(CtClass target, String description, String[] tags) {
106         ClassFile ccFile = target.getClassFile();
107         ConstPool constPool = ccFile.getConstPool();
108
109         AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
110         attr.addAnnotation(createAnnotationDescription(description, constPool));
111         attr.addAnnotation(createAnnotationTags(tags, constPool));
112
113         ccFile.addAttribute(attr);
114     }
115
116     public static void addMethod(CtClass target, String methodCode) {
117         try {
118             CtMethod method = CtMethod.make(methodCode, target);
119             target.addMethod(method);
120         } catch (CannotCompileException e) {
121             LOG.error(String.format("Issue with this code:\n%s", methodCode));
122             LOG.error(e.toString(), e);
123             throw new ProcessorBuilderError(e);
124         }
125     }
126
127     private static String createCodeGetter(String methodName, String returnValue) {
128         return String.format("public java.lang.String get%s() { return \"%s\"; }", methodName, returnValue);
129     }
130
131     public static void setComponentPropertyGetters(CtClass target, Comp comp) {
132         addMethod(target, createCodeGetter("Name", comp.compSpec.name));
133         addMethod(target, createCodeGetter("Version", comp.compSpec.version));
134         addMethod(target, createCodeGetter("ComponentId", comp.id));
135         addMethod(target, createCodeGetter("ComponentUrl", comp.selfUrl));
136     }
137
138     private static String convertParameterToCode(CompSpec.Parameter param) {
139         StringBuilder sb = new StringBuilder("props.add(new org.apache.nifi.components.PropertyDescriptor.Builder()");
140         sb.append(String.format(".name(\"%s\")", param.name));
141         sb.append(String.format(".displayName(\"%s\")", param.name));
142         sb.append(String.format(".description(\"%s\")", StringEscapeUtils.escapeJava(param.description)));
143         sb.append(String.format(".defaultValue(\"%s\")", StringEscapeUtils.escapeJava(param.value)));
144         sb.append(".build());");
145         return sb.toString();
146     }
147
148     private static String createCodePropertyDescriptors(CompSpec compSpec) {
149         List<String> linesParams = compSpec.parameters.stream().map(p -> convertParameterToCode(p)).collect(Collectors.toList());
150
151         // NOTE: Generics are only partially supported https://www.javassist.org/tutorial/tutorial3.html#generics
152         String[] lines = new String[] {"protected java.util.List buildSupportedPropertyDescriptors() {"
153             , "java.util.List props = new java.util.LinkedList();"
154             , String.join("\n", linesParams.toArray(new String[linesParams.size()]))
155             , "return props; }"
156         };
157
158         return String.join("\n", lines);
159     }
160
161     public static void setProcessorPropertyDescriptors(CtClass target, CompSpec compSpec) {
162         addMethod(target, createCodePropertyDescriptors(compSpec));
163     }
164
165     private static String createRelationshipName(CompSpec.Connection connection, String direction) {
166         // TODO: Revisit this name thing ugh
167         return String.format("%s:%s:%s:%s:%s",
168             direction, connection.format.toLowerCase(), connection.version, connection.type, connection.configKey);
169     }
170
171     private static String convertConnectionToCode(CompSpec.Connection connection, String direction) {
172         StringBuilder sb = new StringBuilder("rels.add(new org.apache.nifi.processor.Relationship.Builder()");
173         sb.append(String.format(".name(\"%s\")", createRelationshipName(connection, direction)));
174         sb.append(".build());");
175         return sb.toString();
176     }
177
178     private static String createCodeRelationships(CompSpec compSpec) {
179         List<String> linesPubs = compSpec.getPublishes().stream().map(c -> convertConnectionToCode(c, "publishes")).collect(Collectors.toList());
180         List<String> linesSubs = compSpec.getSubscribes().stream().map(c -> convertConnectionToCode(c, "subscribes")).collect(Collectors.toList());
181
182         String [] lines = new String[] {"protected java.util.Set buildRelationships() {"
183             , "java.util.Set rels = new java.util.HashSet();"
184             , String.join("\n", linesPubs.toArray(new String[linesPubs.size()]))
185             , String.join("\n", linesSubs.toArray(new String[linesSubs.size()]))
186             , "return rels; }"
187         };
188
189         return String.join("\n", lines);
190     }
191
192     public static void setProcessorRelationships(CtClass target, CompSpec compSpec) {
193         addMethod(target, createCodeRelationships(compSpec));
194     }
195
196 }
197