f3afdde32d769d6273274c06f74c98d6e6c7ce24
[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.plugins.event.protocol.yaml;
22
23 import java.util.ArrayList;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.onap.policy.apex.context.SchemaHelper;
29 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
30 import org.onap.policy.apex.model.basicmodel.service.ModelService;
31 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
32 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
33 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
34 import org.onap.policy.apex.service.engine.event.ApexEvent;
35 import org.onap.policy.apex.service.engine.event.ApexEventException;
36 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
37 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
38 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
39 import org.slf4j.ext.XLogger;
40 import org.slf4j.ext.XLoggerFactory;
41 import org.yaml.snakeyaml.DumperOptions.FlowStyle;
42 import org.yaml.snakeyaml.Yaml;
43
44 /**
45  * The Class Apex2YamlEventConverter converts {@link ApexEvent} instances to and from YAML string representations of
46  * Apex events.
47  *
48  * @author Liam Fallon (liam.fallon@ericsson.com)
49  */
50 public class Apex2YamlEventConverter implements ApexEventProtocolConverter {
51     private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2YamlEventConverter.class);
52
53     // The parameters for the YAML event protocol
54     private YamlEventProtocolParameters yamlPars;
55
56     /*
57      * (non-Javadoc)
58      * 
59      * @see org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter#init(org.onap.policy.
60      * apex.service.parameters.eventprotocol.EventProtocolParameters)
61      */
62     @Override
63     public void init(final EventProtocolParameters parameters) {
64         // Check and get the YAML parameters
65         if (!(parameters instanceof YamlEventProtocolParameters)) {
66             final String errorMessage = "specified consumer properties are not applicable to the YAML event protocol";
67             LOGGER.warn(errorMessage);
68             throw new ApexEventRuntimeException(errorMessage);
69         }
70
71         yamlPars = (YamlEventProtocolParameters) parameters;
72     }
73
74     /*
75      * (non-Javadoc)
76      *
77      * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#toApexEvent(java.lang.String, java.lang.Object)
78      */
79     @Override
80     public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
81         // Check the event eventObject
82         if (eventObject == null) {
83             LOGGER.warn("event processing failed, event is null");
84             throw new ApexEventException("event processing failed, event is null");
85         }
86
87         // Cast the event to a string, if our conversion is correctly configured, this cast should
88         // always work
89         if (!(eventObject instanceof String)) {
90             final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
91             LOGGER.debug(errorMessage);
92             throw new ApexEventException(errorMessage);
93         }
94
95         final String yamlEventString = (String) eventObject;
96
97         // The list of events we will return
98         final List<ApexEvent> eventList = new ArrayList<>();
99
100         // Convert the YAML document string into an object
101         Object yamlObject = new Yaml().load(yamlEventString);
102
103         // If the incoming YAML did not create a map it is a primitive type or a collection so we
104         // convert it into a map for processing
105         Map<?, ?> yamlMap;
106         if (yamlObject instanceof Map) {
107             // We already have a map so just cast the object
108             yamlMap = (Map<?, ?>) yamlObject;
109         } else {
110             // Create a single entry map, new map creation and assignment is to avoid a
111             // type checking warning
112             LinkedHashMap<String, Object> newYamlMap = new LinkedHashMap<>();
113             newYamlMap.put(yamlPars.getYamlFieldName(), yamlObject);
114             yamlMap = newYamlMap;
115         }
116
117         try {
118             eventList.add(yamlMap2ApexEvent(eventName, yamlMap));
119         } catch (final Exception e) {
120             final String errorString = "Failed to unmarshal YAML event: " + e.getMessage() + ", event="
121                             + yamlEventString;
122             LOGGER.warn(errorString, e);
123             throw new ApexEventException(errorString, e);
124         }
125
126         // Return the list of events we have unmarshalled
127         return eventList;
128     }
129
130     /*
131      * (non-Javadoc)
132      *
133      * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#fromApexEvent(org.onap.policy.
134      * apex.service.engine.event.ApexEvent)
135      */
136     @Override
137     public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
138         // Check the Apex event
139         if (apexEvent == null) {
140             LOGGER.warn("event processing failed, Apex event is null");
141             throw new ApexEventException("event processing failed, Apex event is null");
142         }
143
144         // Get the event definition for the event from the model service
145         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
146                         apexEvent.getVersion());
147
148         // Create a map for output of the APEX event to YAML
149         LinkedHashMap<String, Object> yamlMap = new LinkedHashMap<>();
150
151         yamlMap.put(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
152         yamlMap.put(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
153         yamlMap.put(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
154         yamlMap.put(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
155         yamlMap.put(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
156
157         if (apexEvent.getExceptionMessage() != null) {
158             yamlMap.put(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
159         }
160
161         for (final AxField eventField : eventDefinition.getFields()) {
162             final String fieldName = eventField.getKey().getLocalName();
163
164             if (!apexEvent.containsKey(fieldName)) {
165                 if (!eventField.getOptional()) {
166                     final String errorMessage = "error parsing " + eventDefinition.getId() + " event to Json. "
167                                     + "Field \"" + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
168                     LOGGER.debug(errorMessage);
169                     throw new ApexEventRuntimeException(errorMessage);
170                 }
171                 continue;
172             }
173
174             yamlMap.put(fieldName, apexEvent.get(fieldName));
175         }
176
177         // Use Snake YAML to convert the APEX event to YAML
178         Yaml yaml = new Yaml();
179         return yaml.dumpAs(yamlMap, null, FlowStyle.BLOCK);
180     }
181
182     /**
183      * This method converts a YAML map into an Apex event.
184      *
185      * @param eventName the name of the event
186      * @param yamlMap the YAML map that holds the event
187      * @return the apex event that we have converted the JSON object into
188      * @throws ApexEventException thrown on unmarshaling exceptions
189      */
190     private ApexEvent yamlMap2ApexEvent(final String eventName, final Map<?, ?> yamlMap) throws ApexEventException {
191         // Process the mandatory Apex header
192         final ApexEvent apexEvent = processApexEventHeader(eventName, yamlMap);
193
194         // Get the event definition for the event from the model service
195         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
196                         apexEvent.getVersion());
197
198         // Iterate over the input fields in the event
199         for (final AxField eventField : eventDefinition.getFields()) {
200             final String fieldName = eventField.getKey().getLocalName();
201             if (!yamlMap.containsKey(fieldName)) {
202                 if (!eventField.getOptional()) {
203                     final String errorMessage = "error parsing " + eventDefinition.getId() + " event from Json. "
204                                     + "Field \"" + fieldName + "\" is missing, but is mandatory.";
205                     LOGGER.debug(errorMessage);
206                     throw new ApexEventException(errorMessage);
207                 }
208                 continue;
209             }
210
211             final Object fieldValue = getYamlField(yamlMap, fieldName, null, !eventField.getOptional());
212
213             if (fieldValue != null) {
214                 // Get the schema helper
215                 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
216                                 eventField.getSchema());
217                 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
218             } else {
219                 apexEvent.put(fieldName, null);
220             }
221         }
222         return apexEvent;
223
224     }
225
226     /**
227      * This method processes the event header of an Apex event.
228      *
229      * @param eventName the name of the event
230      * @param yamlMap the YAML map that holds the event
231      * @return an apex event constructed using the header fields of the event
232      * @throws ApexEventRuntimeException the apex event runtime exception
233      * @throws ApexEventException on invalid events with missing header fields
234      */
235     private ApexEvent processApexEventHeader(final String eventName, final Map<?, ?> yamlMap)
236                     throws ApexEventException {
237         String name = getYamlStringField(yamlMap, ApexEvent.NAME_HEADER_FIELD, yamlPars.getNameAlias(),
238                         ApexEvent.NAME_REGEXP, false);
239
240         // Check that an event name has been specified
241         if (name == null && eventName == null) {
242             throw new ApexEventRuntimeException(
243                             "event received without mandatory parameter \"name\" on configuration or on event");
244         }
245
246         // Check if an event name was specified on the event parameters
247         if (eventName != null) {
248             if (name != null && !eventName.equals(name)) {
249                 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\", "
250                                 + "using configured event name", name, eventName);
251             }
252             name = eventName;
253         }
254
255         // Now, find the event definition in the model service. If version is null, the newest event
256         // definition in the model service is used
257         String version = getYamlStringField(yamlMap, ApexEvent.VERSION_HEADER_FIELD, yamlPars.getVersionAlias(),
258                         ApexEvent.VERSION_REGEXP, false);
259         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(name, version);
260         if (eventDefinition == null) {
261             throw new ApexEventRuntimeException("an event definition for an event named \"" + name
262                             + "\" with version \"" + version + "\" not found in Apex model");
263         }
264
265         // Use the defined event version if no version is specified on the incoming fields
266         if (version == null) {
267             version = eventDefinition.getKey().getVersion();
268         }
269
270         String namespace = getEventHeaderNamespace(yamlMap, name, eventDefinition);
271
272         String source = getEventHeaderSource(yamlMap, eventDefinition);
273
274         String target = getHeaderTarget(yamlMap, eventDefinition);
275
276         return new ApexEvent(name, version, namespace, source, target);
277     }
278
279     /**
280      * Get the event header name space.
281      * 
282      * @param yamlMap the YAML map to read from
283      * @param eventDefinition the event definition
284      * @return the event header name space
285      */
286     private String getEventHeaderNamespace(final Map<?, ?> yamlMap, String name, final AxEvent eventDefinition) {
287         // Check the name space is OK if it is defined, if not, use the name space from the model
288         String namespace = getYamlStringField(yamlMap, ApexEvent.NAMESPACE_HEADER_FIELD, yamlPars.getNameSpaceAlias(),
289                         ApexEvent.NAMESPACE_REGEXP, false);
290         if (namespace != null) {
291             if (!namespace.equals(eventDefinition.getNameSpace())) {
292                 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
293                                 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
294                                 + "\" for that event in the Apex model");
295             }
296         } else {
297             namespace = eventDefinition.getNameSpace();
298         }
299         return namespace;
300     }
301
302     /**
303      * Get the event header source.
304      * 
305      * @param yamlMap the YAML map to read from
306      * @param eventDefinition the event definition
307      * @return the event header source
308      */
309     private String getEventHeaderSource(final Map<?, ?> yamlMap, final AxEvent eventDefinition) {
310         // For source, use the defined source only if the source is not found on the incoming event
311         String source = getYamlStringField(yamlMap, ApexEvent.SOURCE_HEADER_FIELD, yamlPars.getSourceAlias(),
312                         ApexEvent.SOURCE_REGEXP, false);
313         if (source == null) {
314             source = eventDefinition.getSource();
315         }
316         return source;
317     }
318
319     /**
320      * Get the event header target.
321      * 
322      * @param yamlMap the YAML map to read from
323      * @param eventDefinition the event definition
324      * @return the event header target
325      */
326     private String getHeaderTarget(final Map<?, ?> yamlMap, final AxEvent eventDefinition) {
327         // For target, use the defined source only if the source is not found on the incoming event
328         String target = getYamlStringField(yamlMap, ApexEvent.TARGET_HEADER_FIELD, yamlPars.getTargetAlias(),
329                         ApexEvent.TARGET_REGEXP, false);
330         if (target == null) {
331             target = eventDefinition.getTarget();
332         }
333         return target;
334     }
335
336     /**
337      * This method gets an event string field from a JSON object.
338      *
339      * @param yamlMap the YAML containing the YAML representation of the incoming event
340      * @param fieldName the field name to find in the event
341      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
342      * @param fieldRegexp the regular expression to check the field against for validity
343      * @param mandatory true if the field is mandatory
344      * @return the value of the field in the JSON object or null if the field is optional
345      * @throws ApexEventRuntimeException the apex event runtime exception
346      */
347     private String getYamlStringField(final Map<?, ?> yamlMap, final String fieldName, final String fieldAlias,
348                     final String fieldRegexp, final boolean mandatory) {
349         // Get the YAML field for the string field
350         final Object yamlField = getYamlField(yamlMap, fieldName, fieldAlias, mandatory);
351
352         // Null strings are allowed
353         if (yamlField == null) {
354             return null;
355         }
356
357         if (!(yamlField instanceof String)) {
358             // The element is not a string so throw an error
359             throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
360                             + yamlField.getClass().getCanonicalName() + "\" is not a string value");
361         }
362
363         final String fieldValueString = (String) yamlField;
364
365         // Is regular expression checking required
366         if (fieldRegexp == null) {
367             return fieldValueString;
368         }
369
370         // Check the event field against its regular expression
371         if (!fieldValueString.matches(fieldRegexp)) {
372             throw new ApexEventRuntimeException(
373                             "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
374         }
375
376         return fieldValueString;
377     }
378
379     /**
380      * This method gets an event field from a YAML object.
381      *
382      * @param yamlMap the YAML containing the YAML representation of the incoming event
383      * @param fieldName the field name to find in the event
384      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
385      * @param mandatory true if the field is mandatory
386      * @return the value of the field in the YAML object or null if the field is optional
387      * @throws ApexEventRuntimeException the apex event runtime exception
388      */
389     private Object getYamlField(final Map<?, ?> yamlMap, final String fieldName, final String fieldAlias,
390                     final boolean mandatory) {
391
392         // Check if we should use the alias for this field
393         String fieldToFind = fieldName;
394         if (fieldAlias != null) {
395             fieldToFind = fieldAlias;
396         }
397
398         // Get the event field
399         final Object eventElement = yamlMap.get(fieldToFind);
400         if (eventElement == null) {
401             if (!mandatory) {
402                 return null;
403             } else {
404                 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
405             }
406         }
407
408         return eventElement;
409     }
410 }