2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * Modifications Copyright (C) 2019-2020 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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.apex.service.engine.event.impl.jsonprotocolplugin;
24 import com.google.gson.Gson;
25 import com.google.gson.GsonBuilder;
26 import com.google.gson.JsonElement;
27 import com.google.gson.JsonObject;
28 import com.google.gson.internal.LinkedTreeMap;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.List;
32 import org.onap.policy.apex.context.SchemaHelper;
33 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
34 import org.onap.policy.apex.model.basicmodel.service.ModelService;
35 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
36 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
37 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
38 import org.onap.policy.apex.service.engine.event.ApexEvent;
39 import org.onap.policy.apex.service.engine.event.ApexEventException;
40 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
41 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
42 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
43 import org.slf4j.ext.XLogger;
44 import org.slf4j.ext.XLoggerFactory;
47 * The Class Apex2JSONEventConverter converts {@link ApexEvent} instances to and from JSON string representations of
50 * @author Liam Fallon (liam.fallon@ericsson.com)
52 public class Apex2JsonEventConverter implements ApexEventProtocolConverter {
53 private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2JsonEventConverter.class);
55 // Recurring string constants
56 private static final String ERROR_PARSING = "error parsing ";
57 private static final String ERROR_CODING = "error coding ";
59 // The parameters for the JSON event protocol
60 private JsonEventProtocolParameters jsonPars;
66 public void init(final EventProtocolParameters parameters) {
67 // Check and get the JSON parameters
68 if (!(parameters instanceof JsonEventProtocolParameters)) {
69 final String errorMessage = "specified consumer properties are not applicable to the JSON event protocol";
70 LOGGER.warn(errorMessage);
71 throw new ApexEventRuntimeException(errorMessage);
74 jsonPars = (JsonEventProtocolParameters) parameters;
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");
88 // Cast the event to a string, if our conversion is correctly configured, this cast should
90 String jsonEventString = null;
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);
99 // The list of events we will return
100 final List<ApexEvent> eventList = new ArrayList<>();
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,
107 // Check if we have a list of objects
108 if (decodedJsonObject instanceof List) {
109 eventList.addAll(decodeEventList(eventName, jsonEventString, decodedJsonObject));
111 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
113 } catch (final Exception e) {
114 throw new ApexEventException("Failed to unmarshal JSON event, event=" + jsonEventString, e);
117 // Return the list of events we have unmarshalled
122 * Decode a list of Apex events.
124 * @param eventName the name of the incoming events
125 * @param jsonEventString the JSON representation of the event list
126 * @param decodedJsonObject The JSON list object
127 * @return a list of decoded Apex events
128 * @throws ApexEventException on event decoding errors
130 private Collection<? extends ApexEvent> decodeEventList(final String eventName, String jsonEventString,
131 final Object decodedJsonObject) throws ApexEventException {
133 final List<ApexEvent> eventList = new ArrayList<>();
135 // Check if it's a list of JSON objects or a list of strings
136 @SuppressWarnings("unchecked")
137 final List<Object> decodedJsonList = (List<Object>) decodedJsonObject;
139 // Decode each of the list elements in sequence
140 for (final Object jsonListObject : decodedJsonList) {
141 if (jsonListObject instanceof String) {
142 eventList.add(jsonStringApexEvent(eventName, (String) jsonListObject));
143 } else if (jsonListObject instanceof JsonObject) {
144 eventList.add(jsonObject2ApexEvent(eventName, (JsonObject) jsonListObject));
145 } else if (jsonListObject instanceof LinkedTreeMap) {
146 eventList.add(jsonObject2ApexEvent(eventName, new Gson().toJsonTree(jsonListObject).getAsJsonObject()));
148 throw new ApexEventException("incoming event (" + jsonEventString
149 + ") is a JSON object array containing an invalid object " + jsonListObject);
160 public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
161 // Check the Apex event
162 if (apexEvent == null) {
163 LOGGER.warn("event processing failed, Apex event is null");
164 throw new ApexEventException("event processing failed, Apex event is null");
167 if (jsonPars.getPojoField() == null) {
168 return fromApexEventWithFields(apexEvent);
170 return fromApexEventPojo(apexEvent);
175 * Serialise an Apex event to a JSON string field by field.
177 * @param apexEvent the event to Serialise
178 * @return the Serialise event as JSON
179 * @throws ApexEventException exceptions on marshaling to JSON
181 private Object fromApexEventWithFields(final ApexEvent apexEvent) {
182 // Get the event definition for the event from the model service
183 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
184 apexEvent.getVersion());
186 // Use a GSON Json object to marshal the Apex event to JSON
187 final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
188 final JsonObject jsonObject = new JsonObject();
190 jsonObject.addProperty(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
191 jsonObject.addProperty(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
192 jsonObject.addProperty(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
193 jsonObject.addProperty(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
194 jsonObject.addProperty(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
196 if (apexEvent.getExceptionMessage() != null) {
197 jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
200 for (final AxField eventField : eventDefinition.getFields()) {
201 final String fieldName = eventField.getKey().getLocalName();
203 if (!apexEvent.containsKey(fieldName)) {
204 if (!eventField.getOptional()) {
205 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. " + "Field \""
206 + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
207 LOGGER.debug(errorMessage);
208 throw new ApexEventRuntimeException(errorMessage);
213 final Object fieldValue = apexEvent.get(fieldName);
215 // Get the schema helper
216 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
217 eventField.getSchema());
218 jsonObject.add(fieldName, (JsonElement) fieldSchemaHelper.marshal2Object(fieldValue));
221 // Output JSON string in a pretty format
222 return gson.toJson(jsonObject);
226 * Serialise an Apex event to a JSON string as a single POJO.
228 * @param apexEvent the event to Serialise
229 * @return the Serialise event as JSON
230 * @throws ApexEventException exceptions on marshaling to JSON
232 private Object fromApexEventPojo(ApexEvent apexEvent) throws ApexEventException {
233 // Get the event definition for the event from the model service
234 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
235 apexEvent.getVersion());
237 if (eventDefinition.getFields().isEmpty()) {
238 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
239 + jsonPars.getPojoField() + " not found, no fields defined on event.";
240 LOGGER.debug(errorMessage);
241 throw new ApexEventException(errorMessage);
244 if (eventDefinition.getFields().size() != 1) {
245 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
246 + jsonPars.getPojoField() + ", "
247 + " one and only one field may be defined on a POJO event definition.";
248 LOGGER.debug(errorMessage);
249 throw new ApexEventException(errorMessage);
252 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
254 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
255 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. Field "
256 + jsonPars.getPojoField() + " not found on POJO event definition.";
257 LOGGER.debug(errorMessage);
258 throw new ApexEventException(errorMessage);
261 final Object fieldValue = apexEvent.get(jsonPars.getPojoField());
263 // Get the schema helper
264 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
265 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
267 return fieldSchemaHelper.marshal2String(fieldValue);
271 * This method converts a JSON object into an Apex event.
273 * @param eventName the name of the event
274 * @param jsonEventString the JSON string that holds the event
275 * @return the apex event that we have converted the JSON object into
276 * @throws ApexEventException thrown on unmarshaling exceptions
278 private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
279 throws ApexEventException {
280 // Use GSON to read the event string
281 final JsonObject jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
284 if (jsonObject == null || !jsonObject.isJsonObject()) {
285 throw new ApexEventException(
286 "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
289 return jsonObject2ApexEvent(eventName, jsonObject);
293 * This method converts a JSON object into an Apex event.
295 * @param eventName the name of the event
296 * @param jsonObject the JSON object that holds the event
297 * @return the apex event that we have converted the JSON object into
298 * @throws ApexEventException thrown on unmarshaling exceptions
300 private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
301 throws ApexEventException {
302 // Process the mandatory Apex header
303 final ApexEvent apexEvent = processApexEventHeader(eventName, jsonObject);
305 // Get the event definition for the event from the model service
306 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
307 apexEvent.getVersion());
309 if (jsonPars.getPojoField() == null) {
310 jsonObject2ApexEventWithFields(jsonObject, apexEvent, eventDefinition);
312 jsonObject2ApexEventPojo(jsonObject, apexEvent, eventDefinition);
319 * Decode an Apex event field by field.
321 * @param jsonObject the JSON representation of the event
322 * @param apexEvent the incoming event header
323 * @param eventDefinition the definition of the event from the model
324 * @throws ApexEventException on decode errors
326 private void jsonObject2ApexEventWithFields(final JsonObject jsonObject, final ApexEvent apexEvent,
327 final AxEvent eventDefinition) throws ApexEventException {
328 // Iterate over the input fields in the event
329 for (final AxField eventField : eventDefinition.getFields()) {
330 final String fieldName = eventField.getKey().getLocalName();
331 if (!hasJsonField(jsonObject, fieldName)) {
332 if (!eventField.getOptional()) {
333 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. "
334 + "Field \"" + fieldName + "\" is missing, but is mandatory.";
335 LOGGER.debug(errorMessage);
336 throw new ApexEventException(errorMessage);
341 final JsonElement fieldValue = getJsonField(jsonObject, fieldName, null, !eventField.getOptional());
343 if (fieldValue != null && !fieldValue.isJsonNull()) {
344 // Get the schema helper
345 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
346 eventField.getSchema());
347 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
349 apexEvent.put(fieldName, null);
355 * Decode an Apex event as a single POJO.
357 * @param jsonObject the JSON representation of the event
358 * @param apexEvent the incoming event header
359 * @param eventDefinition the definition of the event from the model
360 * @throws ApexEventException on decode errors
362 private void jsonObject2ApexEventPojo(JsonObject jsonObject, ApexEvent apexEvent, AxEvent eventDefinition)
363 throws ApexEventException {
365 if (eventDefinition.getFields().isEmpty()) {
366 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
367 + jsonPars.getPojoField() + " not found, no fields defined on event.";
368 LOGGER.debug(errorMessage);
369 throw new ApexEventException(errorMessage);
372 if (eventDefinition.getFields().size() != 1) {
373 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
374 + jsonPars.getPojoField()
375 + ", one and only one field may be defined on a POJO event definition.";
376 LOGGER.debug(errorMessage);
377 throw new ApexEventException(errorMessage);
380 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
382 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
383 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. Field "
384 + jsonPars.getPojoField() + " not found on POJO event definition.";
385 LOGGER.debug(errorMessage);
386 throw new ApexEventException(errorMessage);
389 // Get the schema helper
390 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
391 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
392 apexEvent.put(jsonPars.getPojoField(), fieldSchemaHelper.createNewInstance(jsonObject));
396 * This method processes the event header of an Apex event.
398 * @param parameterEventName the name of the event from the parameters
399 * @param jsonObject the JSON object containing the JSON representation of the incoming event
400 * @return an apex event constructed using the header fields of the event
401 * @throws ApexEventRuntimeException the apex event runtime exception
402 * @throws ApexEventException on invalid events with missing header fields
404 private ApexEvent processApexEventHeader(final String parameterEventName, final JsonObject jsonObject)
405 throws ApexEventException {
407 final String eventName = getHeaderName(jsonObject, parameterEventName);
409 String eventVersion = getHeaderVersion(jsonObject);
411 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(eventName, eventVersion);
413 if (eventDefinition == null) {
414 if (eventVersion == null) {
415 throw new ApexEventRuntimeException(
416 "an event definition for an event named \"" + eventName + "\" not found in Apex model");
418 throw new ApexEventRuntimeException("an event definition for an event named \"" + eventName
419 + "\" with version \"" + eventVersion + "\" not found in Apex model");
423 // Use the defined event version if no version is specified on the incoming fields
424 if (eventVersion == null) {
425 eventVersion = eventDefinition.getKey().getVersion();
428 final String eventNamespace = getHeaderNamespace(jsonObject, eventName, eventDefinition);
429 final String eventSource = getHeaderSource(jsonObject, eventDefinition);
430 final String eventTarget = getHeaderTarget(jsonObject, eventDefinition);
432 return new ApexEvent(eventName, eventVersion, eventNamespace, eventSource, eventTarget);
436 * Determine the name field of the event header.
438 * @param jsonObject the event in JSON format
439 * @param parameterEventName the configured event name from the parameters
440 * @return the event name to use on the event header
442 private String getHeaderName(final JsonObject jsonObject, final String parameterEventName) {
443 final String jsonEventName = getJsonStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD,
444 jsonPars.getNameAlias(), ApexEvent.NAME_REGEXP, false);
446 // Check that an event name has been specified
447 if (jsonEventName == null && parameterEventName == null) {
448 throw new ApexEventRuntimeException(
449 "event received without mandatory parameter \"name\" on configuration or on event");
452 // Check if an event name was specified on the event parameters
453 if (jsonEventName != null) {
454 if (parameterEventName != null && !parameterEventName.equals(jsonEventName)) {
455 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\","
456 + " using configured event name", jsonEventName, parameterEventName);
458 return jsonEventName;
460 return parameterEventName;
465 * Determine the version field of the event header.
467 * @param jsonObject the event in JSON format
468 * @return the event version
470 private String getHeaderVersion(final JsonObject jsonObject) {
471 // Find the event definition in the model service. If version is null, the newest event
472 // definition in the model service is used
473 return getJsonStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD, jsonPars.getVersionAlias(),
474 ApexEvent.VERSION_REGEXP, false);
478 * Determine the name space field of the event header.
480 * @param jsonObject the event in JSON format
481 * @param eventName the name of the event
482 * @param eventDefinition the definition of the event structure
483 * @return the event version
485 private String getHeaderNamespace(final JsonObject jsonObject, final String name, final AxEvent eventDefinition) {
486 // Check the name space is OK if it is defined, if not, use the name space from the model
487 String namespace = getJsonStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD,
488 jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
489 if (namespace != null) {
490 if (!namespace.equals(eventDefinition.getNameSpace())) {
491 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
492 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
493 + "\" for that event in the Apex model");
496 namespace = eventDefinition.getNameSpace();
502 * Determine the source field of the event header.
504 * @param jsonObject the event in JSON format
505 * @param eventDefinition the definition of the event structure
506 * @return the event version
508 private String getHeaderSource(final JsonObject jsonObject, final AxEvent eventDefinition) {
509 // For source, use the defined source only if the source is not found on the incoming event
510 String source = getJsonStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD, jsonPars.getSourceAlias(),
511 ApexEvent.SOURCE_REGEXP, false);
512 if (source == null) {
513 source = eventDefinition.getSource();
519 * Determine the target field of the event header.
521 * @param jsonObject the event in JSON format
522 * @param eventDefinition the definition of the event structure
523 * @return the event version
525 private String getHeaderTarget(final JsonObject jsonObject, final AxEvent eventDefinition) {
526 // For target, use the defined source only if the source is not found on the incoming event
527 String target = getJsonStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD, jsonPars.getTargetAlias(),
528 ApexEvent.TARGET_REGEXP, false);
529 if (target == null) {
530 target = eventDefinition.getTarget();
536 * This method gets an event string field from a JSON object.
538 * @param jsonObject the JSON object containing the JSON representation of the incoming event
539 * @param fieldName the field name to find in the event
540 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
541 * @param fieldRegexp the regular expression to check the field against for validity
542 * @param mandatory true if the field is mandatory
543 * @return the value of the field in the JSON object or null if the field is optional
544 * @throws ApexEventRuntimeException the apex event runtime exception
546 private String getJsonStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
547 final String fieldRegexp, final boolean mandatory) {
548 // Get the JSON field for the string field
549 final JsonElement jsonField = getJsonField(jsonObject, fieldName, fieldAlias, mandatory);
551 // Null strings are allowed
552 if (jsonField == null || jsonField.isJsonNull()) {
556 // Check if this is a string field
557 String fieldValueString = null;
559 fieldValueString = jsonField.getAsString();
560 } catch (final Exception e) {
561 // The element is not a string so throw an error
562 throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
563 + jsonField.getClass().getName() + "\" is not a string value", e);
566 // Is regular expression checking required
567 if (fieldRegexp == null) {
568 return fieldValueString;
571 // Check the event field against its regular expression
572 if (!fieldValueString.matches(fieldRegexp)) {
573 throw new ApexEventRuntimeException(
574 "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
577 return fieldValueString;
581 * This method gets an event field from a JSON object.
583 * @param jsonObject the JSON object containing the JSON representation of the incoming event
584 * @param fieldName the field name to find in the event
585 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
586 * @param mandatory true if the field is mandatory
587 * @return the value of the field in the JSON object or null if the field is optional
588 * @throws ApexEventRuntimeException the apex event runtime exception
590 private JsonElement getJsonField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
591 final boolean mandatory) {
593 // Check if we should use the alias for this field
594 String fieldToFind = fieldName;
595 if (fieldAlias != null) {
596 fieldToFind = fieldAlias;
599 // Get the event field
600 final JsonElement eventElement = jsonObject.get(fieldToFind);
601 if (eventElement == null) {
605 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
613 * This method if a JSON object has a named field.
615 * @param jsonObject the JSON object containing the JSON representation of the incoming event
616 * @param fieldName the field name to find in the event
617 * @return true if the field is present
618 * @throws ApexEventRuntimeException the apex event runtime exception
620 private boolean hasJsonField(final JsonObject jsonObject, final String fieldName) {
621 // check for the field
622 return jsonObject.has(fieldName);