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