ce511be7f562dea0da79065f2ee4a391a40c2387
[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.service.engine.event.impl.jsonprotocolplugin;
22
23 import com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
25 import com.google.gson.JsonElement;
26 import com.google.gson.JsonObject;
27
28 import java.util.ArrayList;
29 import java.util.List;
30
31 import org.onap.policy.apex.context.SchemaHelper;
32 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
33 import org.onap.policy.apex.model.basicmodel.service.ModelService;
34 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
35 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
36 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
37 import org.onap.policy.apex.service.engine.event.ApexEvent;
38 import org.onap.policy.apex.service.engine.event.ApexEventException;
39 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
40 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
41 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
42 import org.slf4j.ext.XLogger;
43 import org.slf4j.ext.XLoggerFactory;
44
45 /**
46  * The Class Apex2JSONEventConverter converts {@link ApexEvent} instances to and from JSON string representations of
47  * Apex events.
48  *
49  * @author Liam Fallon (liam.fallon@ericsson.com)
50  */
51 public class Apex2JsonEventConverter implements ApexEventProtocolConverter {
52     private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2JsonEventConverter.class);
53
54     // The parameters for the JSON event protocol
55     private JsonEventProtocolParameters jsonPars;
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 JSON parameters
66         if (!(parameters instanceof JsonEventProtocolParameters)) {
67             final String errorMessage = "specified consumer properties are not applicable to the JSON event protocol";
68             LOGGER.warn(errorMessage);
69             throw new ApexEventRuntimeException(errorMessage);
70         }
71
72         jsonPars = (JsonEventProtocolParameters) 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         String jsonEventString = null;
91         try {
92             jsonEventString = (String) eventObject;
93         } catch (final Exception e) {
94             final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
95             LOGGER.debug(errorMessage, e);
96             throw new ApexEventRuntimeException(errorMessage, e);
97         }
98
99         // The list of events we will return
100         final List<ApexEvent> eventList = new ArrayList<>();
101
102         try {
103             // We may have a single JSON object with a single event or an array of JSON objects
104             final Object decodedJsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
105                             Object.class);
106
107             // Check if we have a list of objects
108             if (decodedJsonObject instanceof List) {
109                 // Check if it's a list of JSON objects or a list of strings
110                 @SuppressWarnings("unchecked")
111                 final List<Object> decodedJsonList = (List<Object>) decodedJsonObject;
112
113                 // Decode each of the list elements in sequence
114                 for (final Object jsonListObject : decodedJsonList) {
115                     if (jsonListObject instanceof String) {
116                         eventList.add(jsonStringApexEvent(eventName, (String) jsonListObject));
117                     } else if (jsonListObject instanceof JsonObject) {
118                         eventList.add(jsonObject2ApexEvent(eventName, (JsonObject) jsonListObject));
119                     } else {
120                         throw new ApexEventException("incoming event (" + jsonEventString
121                                         + ") is a JSON object array containing an invalid object " + jsonListObject);
122                     }
123                 }
124             } else {
125                 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
126             }
127         } catch (final Exception e) {
128             final String errorString = "Failed to unmarshal JSON event: " + e.getMessage() + ", event="
129                             + jsonEventString;
130             LOGGER.warn(errorString, e);
131             throw new ApexEventException(errorString, e);
132         }
133
134         // Return the list of events we have unmarshalled
135         return eventList;
136     }
137
138     /*
139      * (non-Javadoc)
140      *
141      * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#fromApexEvent(org.onap.policy.
142      * apex.service.engine.event.ApexEvent)
143      */
144     @Override
145     public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
146         // Check the Apex event
147         if (apexEvent == null) {
148             LOGGER.warn("event processing failed, Apex event is null");
149             throw new ApexEventException("event processing failed, Apex event is null");
150         }
151
152         // Get the event definition for the event from the model service
153         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
154                         apexEvent.getVersion());
155
156         // Use a GSON Json object to marshal the Apex event to JSON
157         final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
158         final JsonObject jsonObject = new JsonObject();
159
160         jsonObject.addProperty(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
161         jsonObject.addProperty(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
162         jsonObject.addProperty(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
163         jsonObject.addProperty(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
164         jsonObject.addProperty(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
165
166         if (apexEvent.getExceptionMessage() != null) {
167             jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
168         }
169
170         for (final AxField eventField : eventDefinition.getFields()) {
171             final String fieldName = eventField.getKey().getLocalName();
172
173             if (!apexEvent.containsKey(fieldName)) {
174                 if (!eventField.getOptional()) {
175                     final String errorMessage = "error parsing " + eventDefinition.getId() + " event to Json. "
176                                     + "Field \"" + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
177                     LOGGER.debug(errorMessage);
178                     throw new ApexEventRuntimeException(errorMessage);
179                 }
180                 continue;
181             }
182
183             final Object fieldValue = apexEvent.get(fieldName);
184
185             // Get the schema helper
186             final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
187                             eventField.getSchema());
188             jsonObject.add(fieldName, (JsonElement) fieldSchemaHelper.marshal2Object(fieldValue));
189         }
190
191         // Output JSON string in a pretty format
192         return gson.toJson(jsonObject);
193     }
194
195     /**
196      * This method converts a JSON object into an Apex event.
197      *
198      * @param eventName the name of the event
199      * @param jsonEventString the JSON string that holds the event
200      * @return the apex event that we have converted the JSON object into
201      * @throws ApexEventException thrown on unmarshaling exceptions
202      */
203     private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
204                     throws ApexEventException {
205         // Use GSON to read the event string
206         final JsonObject jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
207                         JsonObject.class);
208
209         if (jsonObject == null || !jsonObject.isJsonObject()) {
210             throw new ApexEventException(
211                             "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
212         }
213
214         return jsonObject2ApexEvent(eventName, jsonObject);
215     }
216
217     /**
218      * This method converts a JSON object into an Apex event.
219      *
220      * @param eventName the name of the event
221      * @param jsonObject the JSON object that holds the event
222      * @return the apex event that we have converted the JSON object into
223      * @throws ApexEventException thrown on unmarshaling exceptions
224      */
225     private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
226                     throws ApexEventException {
227         // Process the mandatory Apex header
228         final ApexEvent apexEvent = processApexEventHeader(eventName, jsonObject);
229
230         // Get the event definition for the event from the model service
231         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
232                         apexEvent.getVersion());
233
234         // Iterate over the input fields in the event
235         for (final AxField eventField : eventDefinition.getFields()) {
236             final String fieldName = eventField.getKey().getLocalName();
237             if (!hasJsonField(jsonObject, fieldName)) {
238                 if (!eventField.getOptional()) {
239                     final String errorMessage = "error parsing " + eventDefinition.getId() + " event from Json. "
240                                     + "Field \"" + fieldName + "\" is missing, but is mandatory.";
241                     LOGGER.debug(errorMessage);
242                     throw new ApexEventException(errorMessage);
243                 }
244                 continue;
245             }
246
247             final JsonElement fieldValue = getJsonField(jsonObject, fieldName, null, !eventField.getOptional());
248
249             if (fieldValue != null && !fieldValue.isJsonNull()) {
250                 // Get the schema helper
251                 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
252                                 eventField.getSchema());
253                 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
254             } else {
255                 apexEvent.put(fieldName, null);
256             }
257         }
258         return apexEvent;
259
260     }
261
262     /**
263      * This method processes the event header of an Apex event.
264      *
265      * @param eventName the name of the event
266      * @param jsonObject the JSON object containing the JSON representation of the incoming event
267      * @return an apex event constructed using the header fields of the event
268      * @throws ApexEventRuntimeException the apex event runtime exception
269      * @throws ApexEventException on invalid events with missing header fields
270      */
271     private ApexEvent processApexEventHeader(final String eventName, final JsonObject jsonObject)
272                     throws ApexEventException {
273         String name = getJsonStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD, jsonPars.getNameAlias(),
274                         ApexEvent.NAME_REGEXP, false);
275
276         // Check that an event name has been specified
277         if (name == null && eventName == null) {
278             throw new ApexEventRuntimeException(
279                             "event received without mandatory parameter \"name\" on configuration or on event");
280         }
281
282         // Check if an event name was specified on the event parameters
283         if (eventName != null) {
284             if (name != null && !eventName.equals(name)) {
285                 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\","
286                                 + " using configured event name", name, eventName);
287             }
288             name = eventName;
289         }
290
291         // Now, find the event definition in the model service. If version is null, the newest event
292         // definition in the model service is used
293         String version = getJsonStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD, jsonPars.getVersionAlias(),
294                         ApexEvent.VERSION_REGEXP, false);
295         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(name, version);
296         if (eventDefinition == null) {
297             throwVersionException(name, version);
298         }
299
300         // Use the defined event version if no version is specified on the incoming fields
301         if (version == null) {
302             version = eventDefinition.getKey().getVersion();
303         }
304
305         // Check the name space is OK if it is defined, if not, use the name space from the model
306         String namespace = getJsonStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD,
307                         jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
308         if (namespace != null) {
309             if (!namespace.equals(eventDefinition.getNameSpace())) {
310                 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
311                                 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
312                                 + "\" for that event in the Apex model");
313             }
314         } else {
315             namespace = eventDefinition.getNameSpace();
316         }
317
318         // For source, use the defined source only if the source is not found on the incoming event
319         String source = getJsonStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD, jsonPars.getSourceAlias(),
320                         ApexEvent.SOURCE_REGEXP, false);
321         if (source == null) {
322             source = eventDefinition.getSource();
323         }
324
325         // For target, use the defined source only if the source is not found on the incoming event
326         String target = getJsonStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD, jsonPars.getTargetAlias(),
327                         ApexEvent.TARGET_REGEXP, false);
328         if (target == null) {
329             target = eventDefinition.getTarget();
330         }
331
332         return new ApexEvent(name, version, namespace, source, target);
333     }
334
335     /**
336      * Throw an exception on event name and/or version with the correct text.
337      * @param name The event name
338      * @param version The event version
339      */
340     private void throwVersionException(String name, String version) {
341         if (version == null) {
342             throw new ApexEventRuntimeException(
343                             "an event definition for an event named \"" + name + "\" not found in Apex model");
344         }
345         else {
346             throw new ApexEventRuntimeException("an event definition for an event named \"" + name
347                             + "\" with version \"" + version + "\" not found in Apex model");
348         }
349     }
350
351     /**
352      * This method gets an event string field from a JSON object.
353      *
354      * @param jsonObject the JSON object containing the JSON representation of the incoming event
355      * @param fieldName the field name to find in the event
356      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
357      * @param fieldRegexp the regular expression to check the field against for validity
358      * @param mandatory true if the field is mandatory
359      * @return the value of the field in the JSON object or null if the field is optional
360      * @throws ApexEventRuntimeException the apex event runtime exception
361      */
362     private String getJsonStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
363                     final String fieldRegexp, final boolean mandatory) {
364         // Get the JSON field for the string field
365         final JsonElement jsonField = getJsonField(jsonObject, fieldName, fieldAlias, mandatory);
366
367         // Null strings are allowed
368         if (jsonField == null || jsonField.isJsonNull()) {
369             return null;
370         }
371
372         // Check if this is a string field
373         String fieldValueString = null;
374         try {
375             fieldValueString = jsonField.getAsString();
376         } catch (final Exception e) {
377             // The element is not a string so throw an error
378             throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
379                             + jsonField.getClass().getCanonicalName() + "\" is not a string value");
380         }
381
382         // Is regular expression checking required
383         if (fieldRegexp == null) {
384             return fieldValueString;
385         }
386
387         // Check the event field against its regular expression
388         if (!fieldValueString.matches(fieldRegexp)) {
389             throw new ApexEventRuntimeException(
390                             "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
391         }
392
393         return fieldValueString;
394     }
395
396     /**
397      * This method gets an event field from a JSON object.
398      *
399      * @param jsonObject the JSON object containing the JSON representation of the incoming event
400      * @param fieldName the field name to find in the event
401      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
402      * @param mandatory true if the field is mandatory
403      * @return the value of the field in the JSON object or null if the field is optional
404      * @throws ApexEventRuntimeException the apex event runtime exception
405      */
406     private JsonElement getJsonField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
407                     final boolean mandatory) {
408
409         // Check if we should use the alias for this field
410         String fieldToFind = fieldName;
411         if (fieldAlias != null) {
412             fieldToFind = fieldAlias;
413         }
414
415         // Get the event field
416         final JsonElement eventElement = jsonObject.get(fieldToFind);
417         if (eventElement == null) {
418             if (!mandatory) {
419                 return null;
420             } else {
421                 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
422             }
423         }
424
425         return eventElement;
426     }
427
428     /**
429      * This method if a JSON object has a named field.
430      *
431      * @param jsonObject the JSON object containing the JSON representation of the incoming event
432      * @param fieldName the field name to find in the event
433      * @return true if the field is present
434      * @throws ApexEventRuntimeException the apex event runtime exception
435      */
436     private boolean hasJsonField(final JsonObject jsonObject, final String fieldName) {
437         // check for the field
438         return jsonObject.has(fieldName);
439     }
440 }