2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * Modifications Copyright (C) 2019-2021 Nordix Foundation.
5 * Modifications Copyright (C) 2021-2022 Bell Canada. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.policy.apex.service.engine.event.impl.jsonprotocolplugin;
25 import com.google.gson.Gson;
26 import com.google.gson.GsonBuilder;
27 import com.google.gson.JsonElement;
28 import com.google.gson.JsonObject;
29 import com.google.gson.internal.LinkedTreeMap;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.List;
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 var errorMessage = "specified consumer properties are not applicable to the JSON event protocol";
70 throw new ApexEventRuntimeException(errorMessage);
73 jsonPars = (JsonEventProtocolParameters) parameters;
80 public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
81 // Check the event eventObject
82 if (eventObject == null) {
83 throw new ApexEventException("event processing failed, event is null");
86 // Cast the event to a string, if our conversion is correctly configured, this cast should
88 String jsonEventString = null;
90 jsonEventString = (String) eventObject;
91 } catch (final Exception e) {
92 final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
93 throw new ApexEventRuntimeException(errorMessage, e);
96 // The list of events we will return
97 final List<ApexEvent> eventList = new ArrayList<>();
100 // We may have a single JSON object with a single event or an array of JSON objects
101 final var decodedJsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
104 // Check if we have a list of objects
105 if (decodedJsonObject instanceof List) {
106 eventList.addAll(decodeEventList(eventName, jsonEventString, decodedJsonObject));
108 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
110 } catch (final Exception e) {
111 throw new ApexEventException("Failed to unmarshal JSON event, event=" + jsonEventString, e);
114 // Return the list of events we have unmarshalled
119 * Decode a list of Apex events.
121 * @param eventName the name of the incoming events
122 * @param jsonEventString the JSON representation of the event list
123 * @param decodedJsonObject The JSON list object
124 * @return a list of decoded Apex events
125 * @throws ApexEventException on event decoding errors
127 private Collection<? extends ApexEvent> decodeEventList(final String eventName, String jsonEventString,
128 final Object decodedJsonObject) throws ApexEventException {
130 final List<ApexEvent> eventList = new ArrayList<>();
132 // Check if it's a list of JSON objects or a list of strings
133 @SuppressWarnings("unchecked")
134 final List<Object> decodedJsonList = (List<Object>) decodedJsonObject;
136 // Decode each of the list elements in sequence
137 for (final Object jsonListObject : decodedJsonList) {
138 if (jsonListObject instanceof String) {
139 eventList.add(jsonStringApexEvent(eventName, (String) jsonListObject));
140 } else if (jsonListObject instanceof JsonObject) {
141 eventList.add(jsonObject2ApexEvent(eventName, (JsonObject) jsonListObject));
142 } else if (jsonListObject instanceof LinkedTreeMap) {
143 eventList.add(jsonObject2ApexEvent(eventName, new Gson().toJsonTree(jsonListObject).getAsJsonObject()));
145 throw new ApexEventException("incoming event (" + jsonEventString
146 + ") is a JSON object array containing an invalid object " + jsonListObject);
157 public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
158 // Check the Apex event
159 if (apexEvent == null) {
160 throw new ApexEventException("event processing failed, Apex event is null");
163 if (jsonPars.getPojoField() == null) {
164 return fromApexEventWithFields(apexEvent);
166 return fromApexEventPojo(apexEvent);
171 * Serialise an Apex event to a JSON string field by field.
173 * @param apexEvent the event to Serialise
174 * @return the Serialise event as JSON
175 * @throws ApexEventException exceptions on marshaling to JSON
177 private Object fromApexEventWithFields(final ApexEvent apexEvent) {
178 // Get the event definition for the event from the model service
179 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
180 apexEvent.getVersion());
182 // Use a GSON Json object to marshal the Apex event to JSON
183 final var gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
184 final var jsonObject = new JsonObject();
186 jsonObject.addProperty(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
187 jsonObject.addProperty(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
188 jsonObject.addProperty(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
189 jsonObject.addProperty(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
190 jsonObject.addProperty(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
191 jsonObject.addProperty(ApexEvent.TOSCA_POLICY_STATE_HEADER_FIELD, apexEvent.getToscaPolicyState());
193 if (apexEvent.getExceptionMessage() != null) {
194 jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
197 for (final AxField eventField : eventDefinition.getFields()) {
198 final String fieldName = eventField.getKey().getLocalName();
200 if (!apexEvent.containsKey(fieldName)) {
201 if (!eventField.getOptional()) {
202 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. " + "Field \""
203 + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
204 throw new ApexEventRuntimeException(errorMessage);
209 final Object fieldValue = apexEvent.get(fieldName);
211 // Get the schema helper
212 final var fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
213 eventField.getSchema());
214 jsonObject.add(fieldName, (JsonElement) fieldSchemaHelper.marshal2Object(fieldValue));
217 // Output JSON string in a pretty format
218 return gson.toJson(jsonObject);
222 * Serialise an Apex event to a JSON string as a single POJO.
224 * @param apexEvent the event to Serialise
225 * @return the Serialise event as JSON
226 * @throws ApexEventException exceptions on marshaling to JSON
228 private Object fromApexEventPojo(ApexEvent apexEvent) throws ApexEventException {
229 // Get the event definition for the event from the model service
230 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
231 apexEvent.getVersion());
233 if (eventDefinition.getFields().isEmpty()) {
234 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
235 + jsonPars.getPojoField() + " not found, no fields defined on event.";
236 throw new ApexEventException(errorMessage);
239 if (eventDefinition.getFields().size() != 1) {
240 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
241 + jsonPars.getPojoField() + ", "
242 + " one and only one field may be defined on a POJO event definition.";
243 throw new ApexEventException(errorMessage);
246 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
248 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
249 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. Field "
250 + jsonPars.getPojoField() + " not found on POJO event definition.";
251 throw new ApexEventException(errorMessage);
254 final Object fieldValue = apexEvent.get(jsonPars.getPojoField());
256 // Get the schema helper
257 final var fieldSchemaHelper = new SchemaHelperFactory()
258 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
260 return fieldSchemaHelper.marshal2String(fieldValue);
264 * This method converts a JSON object into an Apex event.
266 * @param eventName the name of the event
267 * @param jsonEventString the JSON string that holds the event
268 * @return the apex event that we have converted the JSON object into
269 * @throws ApexEventException thrown on unmarshaling exceptions
271 private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
272 throws ApexEventException {
273 // Use GSON to read the event string
274 final var jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
277 if (jsonObject == null || !jsonObject.isJsonObject()) {
278 throw new ApexEventException(
279 "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
282 return jsonObject2ApexEvent(eventName, jsonObject);
286 * This method converts a JSON object into an Apex event.
288 * @param eventName the name of the event
289 * @param jsonObject the JSON object that holds the event
290 * @return the apex event that we have converted the JSON object into
291 * @throws ApexEventException thrown on unmarshaling exceptions
293 private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
294 throws ApexEventException {
295 // Process the mandatory Apex header
296 final var apexEvent = processApexEventHeader(eventName, jsonObject);
298 // Get the event definition for the event from the model service
299 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
300 apexEvent.getVersion());
302 if (jsonPars.getPojoField() == null) {
303 jsonObject2ApexEventWithFields(jsonObject, apexEvent, eventDefinition);
305 jsonObject2ApexEventPojo(jsonObject, apexEvent, eventDefinition);
312 * Decode an Apex event field by field.
314 * @param jsonObject the JSON representation of the event
315 * @param apexEvent the incoming event header
316 * @param eventDefinition the definition of the event from the model
317 * @throws ApexEventException on decode errors
319 private void jsonObject2ApexEventWithFields(final JsonObject jsonObject, final ApexEvent apexEvent,
320 final AxEvent eventDefinition) throws ApexEventException {
321 // Iterate over the input fields in the event
322 for (final AxField eventField : eventDefinition.getFields()) {
323 final String fieldName = eventField.getKey().getLocalName();
324 if (!hasJsonField(jsonObject, fieldName)) {
325 if (!eventField.getOptional()) {
326 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. "
327 + "Field \"" + fieldName + "\" is missing, but is mandatory.";
328 throw new ApexEventException(errorMessage);
333 final JsonElement fieldValue = getJsonField(jsonObject, fieldName, null, !eventField.getOptional());
335 if (fieldValue != null && !fieldValue.isJsonNull()) {
336 // Get the schema helper
337 final var fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
338 eventField.getSchema());
339 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
341 apexEvent.put(fieldName, null);
347 * Decode an Apex event as a single POJO.
349 * @param jsonObject the JSON representation of the event
350 * @param apexEvent the incoming event header
351 * @param eventDefinition the definition of the event from the model
352 * @throws ApexEventException on decode errors
354 private void jsonObject2ApexEventPojo(JsonObject jsonObject, ApexEvent apexEvent, AxEvent eventDefinition)
355 throws ApexEventException {
357 if (eventDefinition.getFields().isEmpty()) {
358 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
359 + jsonPars.getPojoField() + " not found, no fields defined on event.";
360 throw new ApexEventException(errorMessage);
363 if (eventDefinition.getFields().size() != 1) {
364 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
365 + jsonPars.getPojoField()
366 + ", one and only one field may be defined on a POJO event definition.";
367 throw new ApexEventException(errorMessage);
370 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
372 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
373 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. Field "
374 + jsonPars.getPojoField() + " not found on POJO event definition.";
375 throw new ApexEventException(errorMessage);
378 // Get the schema helper
379 final var fieldSchemaHelper = new SchemaHelperFactory()
380 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
381 apexEvent.put(jsonPars.getPojoField(), fieldSchemaHelper.createNewInstance(jsonObject));
385 * This method processes the event header of an Apex event.
387 * @param parameterEventName the name of the event from the parameters
388 * @param jsonObject the JSON object containing the JSON representation of the incoming event
389 * @return an apex event constructed using the header fields of the event
390 * @throws ApexEventRuntimeException the apex event runtime exception
391 * @throws ApexEventException on invalid events with missing header fields
393 private ApexEvent processApexEventHeader(final String parameterEventName, final JsonObject jsonObject)
394 throws ApexEventException {
396 final String eventName = getHeaderName(jsonObject, parameterEventName);
398 String eventVersion = getHeaderVersion(jsonObject);
400 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(eventName, eventVersion);
402 if (eventDefinition == null) {
403 if (eventVersion == null) {
404 throw new ApexEventRuntimeException(
405 "an event definition for an event named \"" + eventName + "\" not found in Apex model");
407 throw new ApexEventRuntimeException("an event definition for an event named \"" + eventName
408 + "\" with version \"" + eventVersion + "\" not found in Apex model");
412 // Use the defined event version if no version is specified on the incoming fields
413 if (eventVersion == null) {
414 eventVersion = eventDefinition.getKey().getVersion();
417 final String eventNamespace = getHeaderNamespace(jsonObject, eventName, eventDefinition);
418 final String eventSource = getHeaderSource(jsonObject, eventDefinition);
419 final String eventTarget = getHeaderTarget(jsonObject, eventDefinition);
420 final String eventStatus = getHeaderToscaPolicyState(jsonObject, eventDefinition);
422 return new ApexEvent(eventName, eventVersion, eventNamespace, eventSource, eventTarget, eventStatus);
426 * Determine the name field of the event header.
428 * @param jsonObject the event in JSON format
429 * @param parameterEventName the configured event name from the parameters
430 * @return the event name to use on the event header
432 private String getHeaderName(final JsonObject jsonObject, final String parameterEventName) {
433 final var jsonEventName = getJsonStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD,
434 jsonPars.getNameAlias(), ApexEvent.NAME_REGEXP, false);
436 // Check that an event name has been specified
437 if (jsonEventName == null && parameterEventName == null) {
438 throw new ApexEventRuntimeException(
439 "event received without mandatory parameter \"name\" on configuration or on event");
442 // Check if an event name was specified on the event parameters
443 if (jsonEventName != null) {
444 if (parameterEventName != null && !parameterEventName.equals(jsonEventName)) {
445 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\","
446 + " using configured event name", jsonEventName, parameterEventName);
448 return jsonEventName;
450 return parameterEventName;
455 * Determine the version field of the event header.
457 * @param jsonObject the event in JSON format
458 * @return the event version
460 private String getHeaderVersion(final JsonObject jsonObject) {
461 // Find the event definition in the model service. If version is null, the newest event
462 // definition in the model service is used
463 return getJsonStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD, jsonPars.getVersionAlias(),
464 ApexEvent.VERSION_REGEXP, false);
468 * Determine the name space field of the event header.
470 * @param jsonObject the event in JSON format
471 * @param name the name of the event
472 * @param eventDefinition the definition of the event structure
473 * @return the event version
475 private String getHeaderNamespace(final JsonObject jsonObject, final String name, final AxEvent eventDefinition) {
476 // Check the name space is OK if it is defined, if not, use the name space from the model
477 var namespace = getJsonStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD,
478 jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
479 if (namespace != null) {
480 if (!namespace.equals(eventDefinition.getNameSpace())) {
481 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
482 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
483 + "\" for that event in the Apex model");
486 namespace = eventDefinition.getNameSpace();
492 * Determine the source field of the event header.
494 * @param jsonObject the event in JSON format
495 * @param eventDefinition the definition of the event structure
496 * @return the event version
498 private String getHeaderSource(final JsonObject jsonObject, final AxEvent eventDefinition) {
499 // For source, use the defined source only if the source is not found on the incoming event
500 var source = getJsonStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD, jsonPars.getSourceAlias(),
501 ApexEvent.SOURCE_REGEXP, false);
502 if (source == null) {
503 source = eventDefinition.getSource();
509 * Determine the target field of the event header.
511 * @param jsonObject the event in JSON format
512 * @param eventDefinition the definition of the event structure
513 * @return the event version
515 private String getHeaderTarget(final JsonObject jsonObject, final AxEvent eventDefinition) {
516 // For target, use the defined source only if the source is not found on the incoming event
517 var target = getJsonStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD, jsonPars.getTargetAlias(),
518 ApexEvent.TARGET_REGEXP, false);
519 if (target == null) {
520 target = eventDefinition.getTarget();
526 * Determine the status field of the event header.
528 * @param jsonObject the event in JSON format
529 * @param eventDefinition the definition of the event structure
530 * @return the event toscaPolicyState
532 private String getHeaderToscaPolicyState(final JsonObject jsonObject, final AxEvent eventDefinition) {
533 // For toscaPolicyState, use defined value from model only if value is not found on the incoming event
534 var toscaPolicyState = getJsonStringField(jsonObject, ApexEvent.TOSCA_POLICY_STATE_HEADER_FIELD,
535 jsonPars.getToscaPolicyStateAlias(), null, false);
536 if (toscaPolicyState == null) {
537 toscaPolicyState = eventDefinition.getToscaPolicyState();
539 return toscaPolicyState;
543 * This method gets an event string field from a JSON object.
545 * @param jsonObject the JSON object containing the JSON representation of the incoming event
546 * @param fieldName the field name to find in the event
547 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
548 * @param fieldRegexp the regular expression to check the field against for validity
549 * @param mandatory true if the field is mandatory
550 * @return the value of the field in the JSON object or null if the field is optional
551 * @throws ApexEventRuntimeException the apex event runtime exception
553 private String getJsonStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
554 final String fieldRegexp, final boolean mandatory) {
555 // Get the JSON field for the string field
556 final JsonElement jsonField = getJsonField(jsonObject, fieldName, fieldAlias, mandatory);
558 // Null strings are allowed
559 if (jsonField == null || jsonField.isJsonNull()) {
563 // Check if this is a string field
564 String fieldValueString = null;
566 fieldValueString = jsonField.getAsString();
567 } catch (final Exception e) {
568 // The element is not a string so throw an error
569 throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
570 + jsonField.getClass().getName() + "\" is not a string value", e);
573 // Is regular expression checking required
574 if (fieldRegexp == null) {
575 return fieldValueString;
578 // Check the event field against its regular expression
579 if (!fieldValueString.matches(fieldRegexp)) {
580 throw new ApexEventRuntimeException(
581 "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
584 return fieldValueString;
588 * This method gets an event field from a JSON object.
590 * @param jsonObject the JSON object containing the JSON representation of the incoming event
591 * @param fieldName the field name to find in the event
592 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
593 * @param mandatory true if the field is mandatory
594 * @return the value of the field in the JSON object or null if the field is optional
595 * @throws ApexEventRuntimeException the apex event runtime exception
597 private JsonElement getJsonField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
598 final boolean mandatory) {
600 // Check if we should use the alias for this field
601 String fieldToFind = fieldName;
602 if (fieldAlias != null) {
603 fieldToFind = fieldAlias;
606 // Get the event field
607 final JsonElement eventElement = jsonObject.get(fieldToFind);
608 if (eventElement == null) {
612 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
620 * This method if a JSON object has a named field.
622 * @param jsonObject the JSON object containing the JSON representation of the incoming event
623 * @param fieldName the field name to find in the event
624 * @return true if the field is present
625 * @throws ApexEventRuntimeException the apex event runtime exception
627 private boolean hasJsonField(final JsonObject jsonObject, final String fieldName) {
628 // check for the field
629 return jsonObject.has(fieldName);