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