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;
27 import com.google.gson.internal.LinkedTreeMap;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.List;
33 import org.onap.policy.apex.context.SchemaHelper;
34 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
35 import org.onap.policy.apex.model.basicmodel.service.ModelService;
36 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
37 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
38 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
39 import org.onap.policy.apex.service.engine.event.ApexEvent;
40 import org.onap.policy.apex.service.engine.event.ApexEventException;
41 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
42 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
43 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
44 import org.slf4j.ext.XLogger;
45 import org.slf4j.ext.XLoggerFactory;
48 * The Class Apex2JSONEventConverter converts {@link ApexEvent} instances to and from JSON string representations of
51 * @author Liam Fallon (liam.fallon@ericsson.com)
53 public class Apex2JsonEventConverter implements ApexEventProtocolConverter {
54 private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2JsonEventConverter.class);
56 // Recurring string constants
57 private static final String ERROR_PARSING = "error parsing ";
58 private static final String ERROR_CODING = "error coding ";
60 // The parameters for the JSON event protocol
61 private JsonEventProtocolParameters jsonPars;
66 * @see org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter#init(org.onap.policy.
67 * apex.service.parameters.eventprotocol.EventProtocolParameters)
70 public void init(final EventProtocolParameters parameters) {
71 // Check and get the JSON parameters
72 if (!(parameters instanceof JsonEventProtocolParameters)) {
73 final String errorMessage = "specified consumer properties are not applicable to the JSON event protocol";
74 LOGGER.warn(errorMessage);
75 throw new ApexEventRuntimeException(errorMessage);
78 jsonPars = (JsonEventProtocolParameters) parameters;
84 * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#toApexEvent(java.lang.String, java.lang.Object)
87 public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
88 // Check the event eventObject
89 if (eventObject == null) {
90 LOGGER.warn("event processing failed, event is null");
91 throw new ApexEventException("event processing failed, event is null");
94 // Cast the event to a string, if our conversion is correctly configured, this cast should
96 String jsonEventString = null;
98 jsonEventString = (String) eventObject;
99 } catch (final Exception e) {
100 final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
101 LOGGER.debug(errorMessage, e);
102 throw new ApexEventRuntimeException(errorMessage, e);
105 // The list of events we will return
106 final List<ApexEvent> eventList = new ArrayList<>();
109 // We may have a single JSON object with a single event or an array of JSON objects
110 final Object decodedJsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
113 // Check if we have a list of objects
114 if (decodedJsonObject instanceof List) {
115 eventList.addAll(decodeEventList(eventName, jsonEventString, decodedJsonObject));
117 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
119 } catch (final Exception e) {
120 final String errorString = "Failed to unmarshal JSON event: " + e.getMessage() + ", event="
122 LOGGER.warn(errorString, e);
123 throw new ApexEventException(errorString, e);
126 // Return the list of events we have unmarshalled
131 * Decode a list of Apex events.
133 * @param eventName the name of the incoming events
134 * @param jsonEventString the JSON representation of the event list
135 * @param decodedJsonObject The JSON list object
136 * @return a list of decoded Apex events
137 * @throws ApexEventException on event decoding errors
139 private Collection<? extends ApexEvent> decodeEventList(final String eventName, String jsonEventString,
140 final Object decodedJsonObject) throws ApexEventException {
142 final List<ApexEvent> eventList = new ArrayList<>();
144 // Check if it's a list of JSON objects or a list of strings
145 @SuppressWarnings("unchecked")
146 final List<Object> decodedJsonList = (List<Object>) decodedJsonObject;
148 // Decode each of the list elements in sequence
149 for (final Object jsonListObject : decodedJsonList) {
150 if (jsonListObject instanceof String) {
151 eventList.add(jsonStringApexEvent(eventName, (String) jsonListObject));
152 } else if (jsonListObject instanceof JsonObject) {
153 eventList.add(jsonObject2ApexEvent(eventName, (JsonObject) jsonListObject));
154 } else if (jsonListObject instanceof LinkedTreeMap) {
155 eventList.add(jsonObject2ApexEvent(eventName, new Gson().toJsonTree(jsonListObject).getAsJsonObject()));
157 throw new ApexEventException("incoming event (" + jsonEventString
158 + ") is a JSON object array containing an invalid object " + jsonListObject);
168 * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#fromApexEvent(org.onap.policy.
169 * apex.service.engine.event.ApexEvent)
172 public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
173 // Check the Apex event
174 if (apexEvent == null) {
175 LOGGER.warn("event processing failed, Apex event is null");
176 throw new ApexEventException("event processing failed, Apex event is null");
179 if (jsonPars.getPojoField() == null) {
180 return fromApexEventWithFields(apexEvent);
182 return fromApexEventPojo(apexEvent);
188 * Serialise an Apex event to a JSON string field by field.
190 * @param apexEvent the event to Serialise
191 * @return the Serialise event as JSON
192 * @throws ApexEventException exceptions on marshaling to JSON
194 private Object fromApexEventWithFields(final ApexEvent apexEvent) {
195 // Get the event definition for the event from the model service
196 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
197 apexEvent.getVersion());
199 // Use a GSON Json object to marshal the Apex event to JSON
200 final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
201 final JsonObject jsonObject = new JsonObject();
203 jsonObject.addProperty(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
204 jsonObject.addProperty(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
205 jsonObject.addProperty(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
206 jsonObject.addProperty(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
207 jsonObject.addProperty(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
209 if (apexEvent.getExceptionMessage() != null) {
210 jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
213 for (final AxField eventField : eventDefinition.getFields()) {
214 final String fieldName = eventField.getKey().getLocalName();
216 if (!apexEvent.containsKey(fieldName)) {
217 if (!eventField.getOptional()) {
218 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. "
219 + "Field \"" + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
220 LOGGER.debug(errorMessage);
221 throw new ApexEventRuntimeException(errorMessage);
226 final Object fieldValue = apexEvent.get(fieldName);
228 // Get the schema helper
229 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
230 eventField.getSchema());
231 jsonObject.add(fieldName, (JsonElement) fieldSchemaHelper.marshal2Object(fieldValue));
234 // Output JSON string in a pretty format
235 return gson.toJson(jsonObject);
239 * Serialise an Apex event to a JSON string as a single POJO.
241 * @param apexEvent the event to Serialise
242 * @return the Serialise event as JSON
243 * @throws ApexEventException exceptions on marshaling to JSON
245 private Object fromApexEventPojo(ApexEvent apexEvent) throws ApexEventException {
246 // Get the event definition for the event from the model service
247 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
248 apexEvent.getVersion());
250 if (eventDefinition.getFields().isEmpty()) {
251 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
252 + jsonPars.getPojoField() + " not found, no fields defined on event.";
253 LOGGER.debug(errorMessage);
254 throw new ApexEventException(errorMessage);
257 if (eventDefinition.getFields().size() != 1) {
258 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
259 + jsonPars.getPojoField() + ", "
260 + " one and only one field may be defined on a POJO event definition.";
261 LOGGER.debug(errorMessage);
262 throw new ApexEventException(errorMessage);
265 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
267 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
268 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. Field "
269 + jsonPars.getPojoField() + " not found on POJO event definition.";
270 LOGGER.debug(errorMessage);
271 throw new ApexEventException(errorMessage);
274 final Object fieldValue = apexEvent.get(jsonPars.getPojoField());
276 // Get the schema helper
277 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
278 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
280 return fieldSchemaHelper.marshal2String(fieldValue);
284 * This method converts a JSON object into an Apex event.
286 * @param eventName the name of the event
287 * @param jsonEventString the JSON string that holds the event
288 * @return the apex event that we have converted the JSON object into
289 * @throws ApexEventException thrown on unmarshaling exceptions
291 private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
292 throws ApexEventException {
293 // Use GSON to read the event string
294 final JsonObject jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
297 if (jsonObject == null || !jsonObject.isJsonObject()) {
298 throw new ApexEventException(
299 "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
302 return jsonObject2ApexEvent(eventName, jsonObject);
306 * This method converts a JSON object into an Apex event.
308 * @param eventName the name of the event
309 * @param jsonObject the JSON object that holds the event
310 * @return the apex event that we have converted the JSON object into
311 * @throws ApexEventException thrown on unmarshaling exceptions
313 private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
314 throws ApexEventException {
315 // Process the mandatory Apex header
316 final ApexEvent apexEvent = processApexEventHeader(eventName, jsonObject);
318 // Get the event definition for the event from the model service
319 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
320 apexEvent.getVersion());
322 if (jsonPars.getPojoField() == null) {
323 jsonObject2ApexEventWithFields(jsonObject, apexEvent, eventDefinition);
325 jsonObject2ApexEventPojo(jsonObject, apexEvent, eventDefinition);
332 * Decode an Apex event field by field.
334 * @param jsonObject the JSON representation of the event
335 * @param apexEvent the incoming event header
336 * @param eventDefinition the definition of the event from the model
337 * @throws ApexEventException on decode errors
339 private void jsonObject2ApexEventWithFields(final JsonObject jsonObject, final ApexEvent apexEvent,
340 final AxEvent eventDefinition) throws ApexEventException {
341 // Iterate over the input fields in the event
342 for (final AxField eventField : eventDefinition.getFields()) {
343 final String fieldName = eventField.getKey().getLocalName();
344 if (!hasJsonField(jsonObject, fieldName)) {
345 if (!eventField.getOptional()) {
346 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. "
347 + "Field \"" + fieldName + "\" is missing, but is mandatory.";
348 LOGGER.debug(errorMessage);
349 throw new ApexEventException(errorMessage);
354 final JsonElement fieldValue = getJsonField(jsonObject, fieldName, null, !eventField.getOptional());
356 if (fieldValue != null && !fieldValue.isJsonNull()) {
357 // Get the schema helper
358 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
359 eventField.getSchema());
360 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
362 apexEvent.put(fieldName, null);
368 * Decode an Apex event as a single POJO.
370 * @param jsonObject the JSON representation of the event
371 * @param apexEvent the incoming event header
372 * @param eventDefinition the definition of the event from the model
373 * @throws ApexEventException on decode errors
375 private void jsonObject2ApexEventPojo(JsonObject jsonObject, ApexEvent apexEvent, AxEvent eventDefinition)
376 throws ApexEventException {
378 if (eventDefinition.getFields().isEmpty()) {
379 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
380 + jsonPars.getPojoField() + " not found, no fields defined on event.";
381 LOGGER.debug(errorMessage);
382 throw new ApexEventException(errorMessage);
385 if (eventDefinition.getFields().size() != 1) {
386 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
387 + jsonPars.getPojoField()
388 + ", one and only one field may be defined on a POJO event definition.";
389 LOGGER.debug(errorMessage);
390 throw new ApexEventException(errorMessage);
393 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
395 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
396 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. Field "
397 + jsonPars.getPojoField() + " not found on POJO event definition.";
398 LOGGER.debug(errorMessage);
399 throw new ApexEventException(errorMessage);
402 // Get the schema helper
403 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
404 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
405 apexEvent.put(jsonPars.getPojoField(), fieldSchemaHelper.createNewInstance(jsonObject));
409 * This method processes the event header of an Apex event.
411 * @param parameterEventName the name of the event from the parameters
412 * @param jsonObject the JSON object containing the JSON representation of the incoming event
413 * @return an apex event constructed using the header fields of the event
414 * @throws ApexEventRuntimeException the apex event runtime exception
415 * @throws ApexEventException on invalid events with missing header fields
417 private ApexEvent processApexEventHeader(final String parameterEventName, final JsonObject jsonObject)
418 throws ApexEventException {
420 final String eventName = getHeaderName(jsonObject, parameterEventName);
422 String eventVersion = getHeaderVersion(jsonObject);
424 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(eventName, eventVersion);
426 if (eventDefinition == null) {
427 if (eventVersion == null) {
428 throw new ApexEventRuntimeException(
429 "an event definition for an event named \"" + eventName + "\" not found in Apex model");
431 throw new ApexEventRuntimeException("an event definition for an event named \"" + eventName
432 + "\" with version \"" + eventVersion + "\" not found in Apex model");
436 // Use the defined event version if no version is specified on the incoming fields
437 if (eventVersion == null) {
438 eventVersion = eventDefinition.getKey().getVersion();
441 final String eventNamespace = getHeaderNamespace(jsonObject, eventName, eventDefinition);
442 final String eventSource = getHeaderSource(jsonObject, eventDefinition);
443 final String eventTarget = getHeaderTarget(jsonObject, eventDefinition);
445 return new ApexEvent(eventName, eventVersion, eventNamespace, eventSource, eventTarget);
449 * Determine the name field of the event header.
451 * @param jsonObject the event in JSON format
452 * @param parameterEventName the configured event name from the parameters
453 * @return the event name to use on the event header
455 private String getHeaderName(final JsonObject jsonObject, final String parameterEventName) {
456 final String jsonEventName = getJsonStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD,
457 jsonPars.getNameAlias(), ApexEvent.NAME_REGEXP, false);
459 // Check that an event name has been specified
460 if (jsonEventName == null && parameterEventName == null) {
461 throw new ApexEventRuntimeException(
462 "event received without mandatory parameter \"name\" on configuration or on event");
465 // Check if an event name was specified on the event parameters
466 if (jsonEventName != null) {
467 if (parameterEventName != null && !parameterEventName.equals(jsonEventName)) {
468 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\","
469 + " using configured event name", jsonEventName, parameterEventName);
471 return jsonEventName;
473 return parameterEventName;
478 * Determine the version field of the event header.
480 * @param jsonObject the event in JSON format
481 * @return the event version
483 private String getHeaderVersion(final JsonObject jsonObject) {
484 // Find the event definition in the model service. If version is null, the newest event
485 // definition in the model service is used
486 return getJsonStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD, jsonPars.getVersionAlias(),
487 ApexEvent.VERSION_REGEXP, false);
491 * Determine the name space field of the event header.
493 * @param jsonObject the event in JSON format
494 * @param eventName the name of the event
495 * @param eventDefinition the definition of the event structure
496 * @return the event version
498 private String getHeaderNamespace(final JsonObject jsonObject, final String name, final AxEvent eventDefinition) {
499 // Check the name space is OK if it is defined, if not, use the name space from the model
500 String namespace = getJsonStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD,
501 jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
502 if (namespace != null) {
503 if (!namespace.equals(eventDefinition.getNameSpace())) {
504 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
505 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
506 + "\" for that event in the Apex model");
509 namespace = eventDefinition.getNameSpace();
515 * Determine the source field of the event header.
517 * @param jsonObject the event in JSON format
518 * @param eventDefinition the definition of the event structure
519 * @return the event version
521 private String getHeaderSource(final JsonObject jsonObject, final AxEvent eventDefinition) {
522 // For source, use the defined source only if the source is not found on the incoming event
523 String source = getJsonStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD, jsonPars.getSourceAlias(),
524 ApexEvent.SOURCE_REGEXP, false);
525 if (source == null) {
526 source = eventDefinition.getSource();
532 * Determine the target field of the event header.
534 * @param jsonObject the event in JSON format
535 * @param eventDefinition the definition of the event structure
536 * @return the event version
538 private String getHeaderTarget(final JsonObject jsonObject, final AxEvent eventDefinition) {
539 // For target, use the defined source only if the source is not found on the incoming event
540 String target = getJsonStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD, jsonPars.getTargetAlias(),
541 ApexEvent.TARGET_REGEXP, false);
542 if (target == null) {
543 target = eventDefinition.getTarget();
549 * This method gets an event string field from a JSON object.
551 * @param jsonObject the JSON object containing the JSON representation of the incoming event
552 * @param fieldName the field name to find in the event
553 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
554 * @param fieldRegexp the regular expression to check the field against for validity
555 * @param mandatory true if the field is mandatory
556 * @return the value of the field in the JSON object or null if the field is optional
557 * @throws ApexEventRuntimeException the apex event runtime exception
559 private String getJsonStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
560 final String fieldRegexp, final boolean mandatory) {
561 // Get the JSON field for the string field
562 final JsonElement jsonField = getJsonField(jsonObject, fieldName, fieldAlias, mandatory);
564 // Null strings are allowed
565 if (jsonField == null || jsonField.isJsonNull()) {
569 // Check if this is a string field
570 String fieldValueString = null;
572 fieldValueString = jsonField.getAsString();
573 } catch (final Exception e) {
574 // The element is not a string so throw an error
575 throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
576 + jsonField.getClass().getCanonicalName() + "\" is not a string value", e);
579 // Is regular expression checking required
580 if (fieldRegexp == null) {
581 return fieldValueString;
584 // Check the event field against its regular expression
585 if (!fieldValueString.matches(fieldRegexp)) {
586 throw new ApexEventRuntimeException(
587 "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
590 return fieldValueString;
594 * This method gets an event field from a JSON object.
596 * @param jsonObject the JSON object containing the JSON representation of the incoming event
597 * @param fieldName the field name to find in the event
598 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
599 * @param mandatory true if the field is mandatory
600 * @return the value of the field in the JSON object or null if the field is optional
601 * @throws ApexEventRuntimeException the apex event runtime exception
603 private JsonElement getJsonField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
604 final boolean mandatory) {
606 // Check if we should use the alias for this field
607 String fieldToFind = fieldName;
608 if (fieldAlias != null) {
609 fieldToFind = fieldAlias;
612 // Get the event field
613 final JsonElement eventElement = jsonObject.get(fieldToFind);
614 if (eventElement == null) {
618 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
626 * This method if a JSON object has a named field.
628 * @param jsonObject the JSON object containing the JSON representation of the incoming event
629 * @param fieldName the field name to find in the event
630 * @return true if the field is present
631 * @throws ApexEventRuntimeException the apex event runtime exception
633 private boolean hasJsonField(final JsonObject jsonObject, final String fieldName) {
634 // check for the field
635 return jsonObject.has(fieldName);