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