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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.apex.service.engine.event.impl.jsonprotocolplugin;
23 import com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
25 import com.google.gson.JsonElement;
26 import com.google.gson.JsonObject;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 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;
65 * @see org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter#init(org.onap.policy.
66 * apex.service.parameters.eventprotocol.EventProtocolParameters)
69 public void init(final EventProtocolParameters parameters) {
70 // Check and get the JSON parameters
71 if (!(parameters instanceof JsonEventProtocolParameters)) {
72 final String errorMessage = "specified consumer properties are not applicable to the JSON event protocol";
73 LOGGER.warn(errorMessage);
74 throw new ApexEventRuntimeException(errorMessage);
77 jsonPars = (JsonEventProtocolParameters) parameters;
83 * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#toApexEvent(java.lang.String, java.lang.Object)
86 public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
87 // Check the event eventObject
88 if (eventObject == null) {
89 LOGGER.warn("event processing failed, event is null");
90 throw new ApexEventException("event processing failed, event is null");
93 // Cast the event to a string, if our conversion is correctly configured, this cast should
95 String jsonEventString = null;
97 jsonEventString = (String) eventObject;
98 } catch (final Exception e) {
99 final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
100 LOGGER.debug(errorMessage, e);
101 throw new ApexEventRuntimeException(errorMessage, e);
104 // The list of events we will return
105 final List<ApexEvent> eventList = new ArrayList<>();
108 // We may have a single JSON object with a single event or an array of JSON objects
109 final Object decodedJsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
112 // Check if we have a list of objects
113 if (decodedJsonObject instanceof List) {
114 eventList.addAll(decodeEventList(eventName, jsonEventString, decodedJsonObject));
116 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
118 } catch (final Exception e) {
119 final String errorString = "Failed to unmarshal JSON event: " + e.getMessage() + ", event="
121 LOGGER.warn(errorString, e);
122 throw new ApexEventException(errorString, e);
125 // Return the list of events we have unmarshalled
130 * Decode a list of Apex events.
132 * @param eventName the name of the incoming events
133 * @param jsonEventString the JSON representation of the event list
134 * @param decodedJsonObject The JSON list object
135 * @return a list of decoded Apex events
136 * @throws ApexEventException on event decoding errors
138 private Collection<? extends ApexEvent> decodeEventList(final String eventName, String jsonEventString,
139 final Object decodedJsonObject) throws ApexEventException {
141 final List<ApexEvent> eventList = new ArrayList<>();
143 // Check if it's a list of JSON objects or a list of strings
144 @SuppressWarnings("unchecked")
145 final List<Object> decodedJsonList = (List<Object>) decodedJsonObject;
147 // Decode each of the list elements in sequence
148 for (final Object jsonListObject : decodedJsonList) {
149 if (jsonListObject instanceof String) {
150 eventList.add(jsonStringApexEvent(eventName, (String) jsonListObject));
151 } else if (jsonListObject instanceof JsonObject) {
152 eventList.add(jsonObject2ApexEvent(eventName, (JsonObject) jsonListObject));
154 throw new ApexEventException("incoming event (" + jsonEventString
155 + ") is a JSON object array containing an invalid object " + jsonListObject);
165 * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#fromApexEvent(org.onap.policy.
166 * apex.service.engine.event.ApexEvent)
169 public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
170 // Check the Apex event
171 if (apexEvent == null) {
172 LOGGER.warn("event processing failed, Apex event is null");
173 throw new ApexEventException("event processing failed, Apex event is null");
176 if (jsonPars.getPojoField() == null) {
177 return fromApexEventWithFields(apexEvent);
179 return fromApexEventPojo(apexEvent);
185 * Serialise an Apex event to a JSON string field by field.
187 * @param apexEvent the event to Serialise
188 * @return the Serialise event as JSON
189 * @throws ApexEventException exceptions on marshaling to JSON
191 private Object fromApexEventWithFields(final ApexEvent apexEvent) {
192 // Get the event definition for the event from the model service
193 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
194 apexEvent.getVersion());
196 // Use a GSON Json object to marshal the Apex event to JSON
197 final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
198 final JsonObject jsonObject = new JsonObject();
200 jsonObject.addProperty(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
201 jsonObject.addProperty(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
202 jsonObject.addProperty(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
203 jsonObject.addProperty(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
204 jsonObject.addProperty(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
206 if (apexEvent.getExceptionMessage() != null) {
207 jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
210 for (final AxField eventField : eventDefinition.getFields()) {
211 final String fieldName = eventField.getKey().getLocalName();
213 if (!apexEvent.containsKey(fieldName)) {
214 if (!eventField.getOptional()) {
215 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. "
216 + "Field \"" + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
217 LOGGER.debug(errorMessage);
218 throw new ApexEventRuntimeException(errorMessage);
223 final Object fieldValue = apexEvent.get(fieldName);
225 // Get the schema helper
226 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
227 eventField.getSchema());
228 jsonObject.add(fieldName, (JsonElement) fieldSchemaHelper.marshal2Object(fieldValue));
231 // Output JSON string in a pretty format
232 return gson.toJson(jsonObject);
236 * Serialise an Apex event to a JSON string as a single POJO.
238 * @param apexEvent the event to Serialise
239 * @return the Serialise event as JSON
240 * @throws ApexEventException exceptions on marshaling to JSON
242 private Object fromApexEventPojo(ApexEvent apexEvent) throws ApexEventException {
243 // Get the event definition for the event from the model service
244 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
245 apexEvent.getVersion());
247 if (eventDefinition.getFields().isEmpty()) {
248 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
249 + jsonPars.getPojoField() + " not found, no fields defined on event.";
250 LOGGER.debug(errorMessage);
251 throw new ApexEventException(errorMessage);
254 if (eventDefinition.getFields().size() != 1) {
255 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
256 + jsonPars.getPojoField() + ", "
257 + " one and only one field may be defined on a POJO event definition.";
258 LOGGER.debug(errorMessage);
259 throw new ApexEventException(errorMessage);
262 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
264 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
265 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. Field "
266 + jsonPars.getPojoField() + " not found on POJO event definition.";
267 LOGGER.debug(errorMessage);
268 throw new ApexEventException(errorMessage);
271 final Object fieldValue = apexEvent.get(jsonPars.getPojoField());
273 // Get the schema helper
274 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
275 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
277 return fieldSchemaHelper.marshal2String(fieldValue);
281 * This method converts a JSON object into an Apex event.
283 * @param eventName the name of the event
284 * @param jsonEventString the JSON string that holds the event
285 * @return the apex event that we have converted the JSON object into
286 * @throws ApexEventException thrown on unmarshaling exceptions
288 private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
289 throws ApexEventException {
290 // Use GSON to read the event string
291 final JsonObject jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
294 if (jsonObject == null || !jsonObject.isJsonObject()) {
295 throw new ApexEventException(
296 "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
299 return jsonObject2ApexEvent(eventName, jsonObject);
303 * This method converts a JSON object into an Apex event.
305 * @param eventName the name of the event
306 * @param jsonObject the JSON object that holds the event
307 * @return the apex event that we have converted the JSON object into
308 * @throws ApexEventException thrown on unmarshaling exceptions
310 private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
311 throws ApexEventException {
312 // Process the mandatory Apex header
313 final ApexEvent apexEvent = processApexEventHeader(eventName, jsonObject);
315 // Get the event definition for the event from the model service
316 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
317 apexEvent.getVersion());
319 if (jsonPars.getPojoField() == null) {
320 jsonObject2ApexEventWithFields(jsonObject, apexEvent, eventDefinition);
322 jsonObject2ApexEventPojo(jsonObject, apexEvent, eventDefinition);
329 * Decode an Apex event field by field.
331 * @param jsonObject the JSON representation of the event
332 * @param apexEvent the incoming event header
333 * @param eventDefinition the definition of the event from the model
334 * @throws ApexEventException on decode errors
336 private void jsonObject2ApexEventWithFields(final JsonObject jsonObject, final ApexEvent apexEvent,
337 final AxEvent eventDefinition) throws ApexEventException {
338 // Iterate over the input fields in the event
339 for (final AxField eventField : eventDefinition.getFields()) {
340 final String fieldName = eventField.getKey().getLocalName();
341 if (!hasJsonField(jsonObject, fieldName)) {
342 if (!eventField.getOptional()) {
343 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. "
344 + "Field \"" + fieldName + "\" is missing, but is mandatory.";
345 LOGGER.debug(errorMessage);
346 throw new ApexEventException(errorMessage);
351 final JsonElement fieldValue = getJsonField(jsonObject, fieldName, null, !eventField.getOptional());
353 if (fieldValue != null && !fieldValue.isJsonNull()) {
354 // Get the schema helper
355 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
356 eventField.getSchema());
357 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
359 apexEvent.put(fieldName, null);
365 * Decode an Apex event as a single POJO.
367 * @param jsonObject the JSON representation of the event
368 * @param apexEvent the incoming event header
369 * @param eventDefinition the definition of the event from the model
370 * @throws ApexEventException on decode errors
372 private void jsonObject2ApexEventPojo(JsonObject jsonObject, ApexEvent apexEvent, AxEvent eventDefinition)
373 throws ApexEventException {
375 if (eventDefinition.getFields().isEmpty()) {
376 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
377 + jsonPars.getPojoField() + " not found, no fields defined on event.";
378 LOGGER.debug(errorMessage);
379 throw new ApexEventException(errorMessage);
382 if (eventDefinition.getFields().size() != 1) {
383 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
384 + jsonPars.getPojoField()
385 + ", one and only one field may be defined on a POJO event definition.";
386 LOGGER.debug(errorMessage);
387 throw new ApexEventException(errorMessage);
390 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
392 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
393 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. Field "
394 + jsonPars.getPojoField() + " not found on POJO event definition.";
395 LOGGER.debug(errorMessage);
396 throw new ApexEventException(errorMessage);
399 // Get the schema helper
400 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
401 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
402 apexEvent.put(jsonPars.getPojoField(), fieldSchemaHelper.createNewInstance(jsonObject));
406 * This method processes the event header of an Apex event.
408 * @param parameterEventName the name of the event from the parameters
409 * @param jsonObject the JSON object containing the JSON representation of the incoming event
410 * @return an apex event constructed using the header fields of the event
411 * @throws ApexEventRuntimeException the apex event runtime exception
412 * @throws ApexEventException on invalid events with missing header fields
414 private ApexEvent processApexEventHeader(final String parameterEventName, final JsonObject jsonObject)
415 throws ApexEventException {
417 final String eventName = getHeaderName(jsonObject, parameterEventName);
419 String eventVersion = getHeaderVersion(jsonObject);
421 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(eventName, eventVersion);
423 if (eventDefinition == null) {
424 if (eventVersion == null) {
425 throw new ApexEventRuntimeException(
426 "an event definition for an event named \"" + eventName + "\" not found in Apex model");
428 throw new ApexEventRuntimeException("an event definition for an event named \"" + eventName
429 + "\" with version \"" + eventVersion + "\" not found in Apex model");
433 // Use the defined event version if no version is specified on the incoming fields
434 if (eventVersion == null) {
435 eventVersion = eventDefinition.getKey().getVersion();
438 final String eventNamespace = getHeaderNamespace(jsonObject, eventName, eventDefinition);
439 final String eventSource = getHeaderSource(jsonObject, eventDefinition);
440 final String eventTarget = getHeaderTarget(jsonObject, eventDefinition);
442 return new ApexEvent(eventName, eventVersion, eventNamespace, eventSource, eventTarget);
446 * Determine the name field of the event header.
448 * @param jsonObject the event in JSON format
449 * @param parameterEventName the configured event name from the parameters
450 * @return the event name to use on the event header
452 private String getHeaderName(final JsonObject jsonObject, final String parameterEventName) {
453 final String jsonEventName = getJsonStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD,
454 jsonPars.getNameAlias(), ApexEvent.NAME_REGEXP, false);
456 // Check that an event name has been specified
457 if (jsonEventName == null && parameterEventName == null) {
458 throw new ApexEventRuntimeException(
459 "event received without mandatory parameter \"name\" on configuration or on event");
462 // Check if an event name was specified on the event parameters
463 if (jsonEventName != null) {
464 if (parameterEventName != null && !parameterEventName.equals(jsonEventName)) {
465 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\","
466 + " using configured event name", jsonEventName, parameterEventName);
468 return jsonEventName;
470 return parameterEventName;
475 * Determine the version field of the event header.
477 * @param jsonObject the event in JSON format
478 * @return the event version
480 private String getHeaderVersion(final JsonObject jsonObject) {
481 // Find the event definition in the model service. If version is null, the newest event
482 // definition in the model service is used
483 return getJsonStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD, jsonPars.getVersionAlias(),
484 ApexEvent.VERSION_REGEXP, false);
488 * Determine the name space field of the event header.
490 * @param jsonObject the event in JSON format
491 * @param eventName the name of the event
492 * @param eventDefinition the definition of the event structure
493 * @return the event version
495 private String getHeaderNamespace(final JsonObject jsonObject, final String name, final AxEvent eventDefinition) {
496 // Check the name space is OK if it is defined, if not, use the name space from the model
497 String namespace = getJsonStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD,
498 jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
499 if (namespace != null) {
500 if (!namespace.equals(eventDefinition.getNameSpace())) {
501 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
502 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
503 + "\" for that event in the Apex model");
506 namespace = eventDefinition.getNameSpace();
512 * Determine the source field of the event header.
514 * @param jsonObject the event in JSON format
515 * @param eventDefinition the definition of the event structure
516 * @return the event version
518 private String getHeaderSource(final JsonObject jsonObject, final AxEvent eventDefinition) {
519 // For source, use the defined source only if the source is not found on the incoming event
520 String source = getJsonStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD, jsonPars.getSourceAlias(),
521 ApexEvent.SOURCE_REGEXP, false);
522 if (source == null) {
523 source = eventDefinition.getSource();
529 * Determine the target field of the event header.
531 * @param jsonObject the event in JSON format
532 * @param eventDefinition the definition of the event structure
533 * @return the event version
535 private String getHeaderTarget(final JsonObject jsonObject, final AxEvent eventDefinition) {
536 // For target, use the defined source only if the source is not found on the incoming event
537 String target = getJsonStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD, jsonPars.getTargetAlias(),
538 ApexEvent.TARGET_REGEXP, false);
539 if (target == null) {
540 target = eventDefinition.getTarget();
546 * This method gets an event string field from a JSON object.
548 * @param jsonObject the JSON object containing the JSON representation of the incoming event
549 * @param fieldName the field name to find in the event
550 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
551 * @param fieldRegexp the regular expression to check the field against for validity
552 * @param mandatory true if the field is mandatory
553 * @return the value of the field in the JSON object or null if the field is optional
554 * @throws ApexEventRuntimeException the apex event runtime exception
556 private String getJsonStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
557 final String fieldRegexp, final boolean mandatory) {
558 // Get the JSON field for the string field
559 final JsonElement jsonField = getJsonField(jsonObject, fieldName, fieldAlias, mandatory);
561 // Null strings are allowed
562 if (jsonField == null || jsonField.isJsonNull()) {
566 // Check if this is a string field
567 String fieldValueString = null;
569 fieldValueString = jsonField.getAsString();
570 } catch (final Exception e) {
571 // The element is not a string so throw an error
572 throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
573 + jsonField.getClass().getCanonicalName() + "\" is not a string value", e);
576 // Is regular expression checking required
577 if (fieldRegexp == null) {
578 return fieldValueString;
581 // Check the event field against its regular expression
582 if (!fieldValueString.matches(fieldRegexp)) {
583 throw new ApexEventRuntimeException(
584 "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
587 return fieldValueString;
591 * This method gets an event field from a JSON object.
593 * @param jsonObject the JSON object containing the JSON representation of the incoming event
594 * @param fieldName the field name to find in the event
595 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
596 * @param mandatory true if the field is mandatory
597 * @return the value of the field in the JSON object or null if the field is optional
598 * @throws ApexEventRuntimeException the apex event runtime exception
600 private JsonElement getJsonField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
601 final boolean mandatory) {
603 // Check if we should use the alias for this field
604 String fieldToFind = fieldName;
605 if (fieldAlias != null) {
606 fieldToFind = fieldAlias;
609 // Get the event field
610 final JsonElement eventElement = jsonObject.get(fieldToFind);
611 if (eventElement == null) {
615 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
623 * This method if a JSON object has a named field.
625 * @param jsonObject the JSON object containing the JSON representation of the incoming event
626 * @param fieldName the field name to find in the event
627 * @return true if the field is present
628 * @throws ApexEventRuntimeException the apex event runtime exception
630 private boolean hasJsonField(final JsonObject jsonObject, final String fieldName) {
631 // check for the field
632 return jsonObject.has(fieldName);