86a826bd1760f0d9631c65d8cd8157b9a67e3449
[policy/apex-pdp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2016-2018 Ericsson. All rights reserved.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.apex.tools.model.generator.model2event;
22
23 import java.util.HashSet;
24 import java.util.Properties;
25 import java.util.Set;
26
27 import org.apache.avro.Schema;
28 import org.apache.avro.Schema.Field;
29 import org.apache.avro.Schema.Type;
30 import org.apache.commons.lang3.Validate;
31 import org.onap.policy.apex.context.parameters.SchemaParameters;
32 import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
33 import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
34 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
35 import org.onap.policy.apex.model.modelapi.ApexApiResult;
36 import org.onap.policy.apex.model.modelapi.ApexModel;
37 import org.onap.policy.apex.model.modelapi.ApexModelFactory;
38 import org.onap.policy.apex.model.policymodel.concepts.AxPolicies;
39 import org.onap.policy.apex.model.policymodel.concepts.AxPolicy;
40 import org.onap.policy.apex.model.policymodel.concepts.AxPolicyModel;
41 import org.onap.policy.apex.model.policymodel.concepts.AxState;
42 import org.onap.policy.apex.model.policymodel.concepts.AxStateOutput;
43 import org.onap.policy.apex.plugins.context.schema.avro.AvroSchemaHelperParameters;
44 import org.onap.policy.apex.service.engine.event.ApexEventException;
45 import org.onap.policy.apex.tools.model.generator.SchemaUtils;
46 import org.slf4j.ext.XLogger;
47 import org.slf4j.ext.XLoggerFactory;
48 import org.stringtemplate.v4.ST;
49 import org.stringtemplate.v4.STGroup;
50 import org.stringtemplate.v4.STGroupFile;
51
52 /**
53  * Takes a model and generates the JSON event schemas.
54  *
55  * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
56  */
57 public class Model2JsonEventSchema {
58     // Logger for this class
59     private static final XLogger LOGGER = XLoggerFactory.getXLogger(Model2JsonEventSchema.class);
60
61     // Recurring string constants
62     private static final String TARGET = "target";
63     private static final String SOURCE = "source";
64     private static final String VERSION = "version";
65     private static final String NAME_SPACE = "nameSpace";
66
67     /** Application name, used as prompt. */
68     private final String appName;
69
70     /** The file name of the policy model. */
71     private final String modelFile;
72
73     /** The type of events to generate: stimuli, response, internal. */
74     private final String type;
75
76     /**
77      * Creates a new model to event schema generator.
78      *
79      * @param modelFile the model file to be used
80      * @param type the type of events to generate, one of: stimuli, response, internal
81      * @param appName application name for printouts
82      */
83     public Model2JsonEventSchema(final String modelFile, final String type, final String appName) {
84         Validate.notNull(modelFile, "Model2JsonEvent: given model file name was blank");
85         Validate.notNull(type, "Model2JsonEvent: given type was blank");
86         Validate.notNull(appName, "Model2JsonEvent: given application name was blank");
87
88         this.modelFile = modelFile;
89         this.type = type;
90         this.appName = appName;
91     }
92
93     /**
94      * Adds a type to a field for a given schema.
95      *
96      * @param schema the schema to add a type for
97      * @param stg the STG
98      * @return a template with the type
99      */
100     protected ST addFieldType(final Schema schema, final STGroup stg) {
101         ST ret = null;
102
103         if (isSimpleType(schema.getType())) {
104             ret = stg.getInstanceOf("fieldTypeAtomic");
105             ret.add("type", schema.getType());
106             return ret;
107         }
108         
109         switch (schema.getType()) {
110             case ARRAY:
111                 ret = stg.getInstanceOf("fieldTypeArray");
112                 ret.add("array", this.addFieldType(schema.getElementType(), stg));
113                 break;
114             case ENUM:
115                 ret = stg.getInstanceOf("fieldTypeEnum");
116                 ret.add("symbols", schema.getEnumSymbols());
117                 break;
118
119             case MAP:
120                 ret = stg.getInstanceOf("fieldTypeMap");
121                 ret.add("map", this.addFieldType(schema.getValueType(), stg));
122                 break;
123
124             case RECORD:
125                 ret = processRecord(schema, stg);
126                 break;
127
128             case NULL:
129                 break;
130             case UNION:
131                 break;
132             default:
133                 break;
134         }
135         return ret;
136     }
137
138     /**
139      * Check if a schema is a simple type.
140      * 
141      * @param schemaType the type of the schema
142      * @return true if the schema is a simple type
143      */
144     private boolean isSimpleType(Type schemaType) {
145         switch (schemaType) {
146             case BOOLEAN:
147             case BYTES:
148             case DOUBLE:
149             case FIXED:
150             case FLOAT:
151             case INT:
152             case LONG:
153             case STRING: {
154                 return true;
155             }
156             
157             default: {
158                 return false;
159             }
160         }
161     }
162
163     /**
164      * Process a record entry.
165      * @param schema the schema to add a type for
166      * @param stg the STG
167      * @return a template with the type
168      */
169     private ST processRecord(final Schema schema, final STGroup stg) {
170         ST ret;
171         ret = stg.getInstanceOf("fieldTypeRecord");
172         for (final Field field : schema.getFields()) {
173             final ST st = stg.getInstanceOf("field");
174             st.add("name", field.name());
175             st.add("type", this.addFieldType(field.schema(), stg));
176             ret.add("fields", st);
177         }
178         return ret;
179     }
180
181     /**
182      * Runs the application.
183      *
184      *
185      * @return status of the application execution, 0 for success, positive integer for exit condition (such as help or
186      *         version), negative integer for errors
187      * @throws ApexException if any problem occurred in the model
188      */
189     public int runApp() throws ApexException {
190         final STGroupFile stg = new STGroupFile("org/onap/policy/apex/tools/model/generator/event-json.stg");
191         final ST stEvents = stg.getInstanceOf("events");
192
193         final ApexModelFactory factory = new ApexModelFactory();
194         final ApexModel model = factory.createApexModel(new Properties(), true);
195
196         final ApexApiResult result = model.loadFromFile(modelFile);
197         if (result.isNok()) {
198             String message = appName + ": " + result.getMessage();
199             LOGGER.error(message);
200             return -1;
201         }
202
203         final AxPolicyModel policyModel = model.getPolicyModel();
204         policyModel.register();
205         new SchemaParameters().getSchemaHelperParameterMap().put("Avro", new AvroSchemaHelperParameters());
206
207         final Set<AxEvent> events = new HashSet<>();
208         final Set<AxArtifactKey> eventKeys = new HashSet<>();
209         final AxPolicies policies = policyModel.getPolicies();
210         switch (type) {
211             case "stimuli":
212                 processStimuli(eventKeys, policies);
213                 break;
214             case "response":
215                 processResponse(eventKeys, policies);
216                 break;
217             case "internal":
218                 processInternal(eventKeys, policies);
219                 break;
220             default:
221                 LOGGER.error("{}: unknown type <{}>, cannot proceed", appName, type);
222                 return -1;
223         }
224
225         for (final AxEvent event : policyModel.getEvents().getEventMap().values()) {
226             for (final AxArtifactKey key : eventKeys) {
227                 if (event.getKey().equals(key)) {
228                     events.add(event);
229                 }
230             }
231         }
232
233         String renderMessage = renderEvents(stg, stEvents, events);
234         LOGGER.error(renderMessage);
235         return 0;
236     }
237
238     /**
239      * Render the events.
240      * 
241      * @param stg the string template
242      * @param stEvents the event template
243      * @param events the events to render
244      * @return the rendered events
245      * @throws ApexEventException on rendering exceptions
246      */
247     private String renderEvents(final STGroupFile stg, final ST stEvents, final Set<AxEvent> events)
248                     throws ApexEventException {
249         for (final AxEvent event : events) {
250             final ST stEvent = stg.getInstanceOf("event");
251             stEvent.add("name", event.getKey().getName());
252             stEvent.add(NAME_SPACE, event.getNameSpace());
253             stEvent.add(VERSION, event.getKey().getVersion());
254             stEvent.add(SOURCE, event.getSource());
255             stEvent.add(TARGET, event.getTarget());
256
257             final Schema avro = SchemaUtils.getEventSchema(event);
258             for (final Field field : avro.getFields()) {
259                 // filter magic names
260                 switch (field.name()) {
261                     case "name":
262                     case NAME_SPACE:
263                     case VERSION:
264                     case SOURCE:
265                     case TARGET:
266                         break;
267                     default:
268                         stEvent.add("fields", this.setField(field, stg));
269                 }
270             }
271             stEvents.add("event", stEvent);
272         }
273         return stEvents.render();
274     }
275
276     /**
277      * Process the "stimuli" keyword.
278      * @param eventKeys the event keys
279      * @param policies the policies to process
280      */
281     private void processStimuli(final Set<AxArtifactKey> eventKeys, final AxPolicies policies) {
282         for (final AxPolicy policy : policies.getPolicyMap().values()) {
283             final String firsState = policy.getFirstState();
284             for (final AxState state : policy.getStateMap().values()) {
285                 if (state.getKey().getLocalName().equals(firsState)) {
286                     eventKeys.add(state.getTrigger());
287                 }
288             }
289         }
290     }
291
292     /**
293      * Process the "response" keyword.
294      * @param eventKeys the event keys
295      * @param policies the policies to process
296      */
297     private void processResponse(final Set<AxArtifactKey> eventKeys, final AxPolicies policies) {
298         for (final AxPolicy policy : policies.getPolicyMap().values()) {
299             processState(eventKeys, policy);
300         }
301     }
302
303     /**
304      * Process the state in the response.
305      * @param eventKeys the event keys
306      * @param policies the policies to process
307      */
308     private void processState(final Set<AxArtifactKey> eventKeys, final AxPolicy policy) {
309         for (final AxState state : policy.getStateMap().values()) {
310             if ("NULL".equals(state.getNextStateSet().iterator().next())) {
311                 for (final AxStateOutput output : state.getStateOutputs().values()) {
312                     eventKeys.add(output.getOutgingEvent());
313                 }
314             }
315         }
316     }
317
318     /**
319      * Process the "internal" keyword.
320      * @param eventKeys the event keys
321      * @param policies the policies to process
322      */
323     private void processInternal(final Set<AxArtifactKey> eventKeys, final AxPolicies policies) {
324         for (final AxPolicy policy : policies.getPolicyMap().values()) {
325             final String firsState = policy.getFirstState();
326             for (final AxState state : policy.getStateMap().values()) {
327                 processInternalState(eventKeys, firsState, state);
328             }
329         }
330     }
331
332     /**
333      * Process the internal state.
334      * @param eventKeys the event keys
335      * @param policies the policies to process
336      */
337     private void processInternalState(final Set<AxArtifactKey> eventKeys, final String firsState, final AxState state) {
338         if (state.getKey().getLocalName().equals(firsState)) {
339             return;
340         }
341         if ("NULL".equals(state.getNextStateSet().iterator().next())) {
342             return;
343         }
344         for (final AxStateOutput output : state.getStateOutputs().values()) {
345             eventKeys.add(output.getOutgingEvent());
346         }
347     }
348
349     /**
350      * Adds a field to the output.
351      *
352      * @param field the field from the event
353      * @param stg the STG
354      * @return a template for the field
355      */
356     protected ST setField(final Field field, final STGroup stg) {
357         final ST st = stg.getInstanceOf("field");
358         switch (field.name()) {
359             case "name":
360             case NAME_SPACE:
361             case VERSION:
362             case SOURCE:
363             case TARGET:
364                 break;
365             default:
366                 st.add("name", field.name());
367                 st.add("type", this.addFieldType(field.schema(), stg));
368         }
369         return st;
370     }
371
372 }