2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * Modifications Copyright (C) 2019 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;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.List;
34 import org.onap.policy.apex.context.SchemaHelper;
35 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
36 import org.onap.policy.apex.model.basicmodel.service.ModelService;
37 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
38 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
39 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
40 import org.onap.policy.apex.service.engine.event.ApexEvent;
41 import org.onap.policy.apex.service.engine.event.ApexEventException;
42 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
43 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
44 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
45 import org.slf4j.ext.XLogger;
46 import org.slf4j.ext.XLoggerFactory;
49 * The Class Apex2JSONEventConverter converts {@link ApexEvent} instances to and from JSON string representations of
52 * @author Liam Fallon (liam.fallon@ericsson.com)
54 public class Apex2JsonEventConverter implements ApexEventProtocolConverter {
55 private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2JsonEventConverter.class);
57 // Recurring string constants
58 private static final String ERROR_PARSING = "error parsing ";
59 private static final String ERROR_CODING = "error coding ";
61 // The parameters for the JSON event protocol
62 private JsonEventProtocolParameters jsonPars;
68 public void init(final EventProtocolParameters parameters) {
69 // Check and get the JSON parameters
70 if (!(parameters instanceof JsonEventProtocolParameters)) {
71 final String errorMessage = "specified consumer properties are not applicable to the JSON event protocol";
72 LOGGER.warn(errorMessage);
73 throw new ApexEventRuntimeException(errorMessage);
76 jsonPars = (JsonEventProtocolParameters) parameters;
83 public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
84 // Check the event eventObject
85 if (eventObject == null) {
86 LOGGER.warn("event processing failed, event is null");
87 throw new ApexEventException("event processing failed, event is null");
90 // Cast the event to a string, if our conversion is correctly configured, this cast should
92 String jsonEventString = null;
94 jsonEventString = (String) eventObject;
95 } catch (final Exception e) {
96 final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
97 LOGGER.debug(errorMessage, e);
98 throw new ApexEventRuntimeException(errorMessage, e);
101 // The list of events we will return
102 final List<ApexEvent> eventList = new ArrayList<>();
105 // We may have a single JSON object with a single event or an array of JSON objects
106 final Object decodedJsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
109 // Check if we have a list of objects
110 if (decodedJsonObject instanceof List) {
111 eventList.addAll(decodeEventList(eventName, jsonEventString, decodedJsonObject));
113 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
115 } catch (final Exception e) {
116 final String errorString = "Failed to unmarshal JSON event: " + e.getMessage() + ", event="
118 LOGGER.warn(errorString, e);
119 throw new ApexEventException(errorString, e);
122 // Return the list of events we have unmarshalled
127 * Decode a list of Apex events.
129 * @param eventName the name of the incoming events
130 * @param jsonEventString the JSON representation of the event list
131 * @param decodedJsonObject The JSON list object
132 * @return a list of decoded Apex events
133 * @throws ApexEventException on event decoding errors
135 private Collection<? extends ApexEvent> decodeEventList(final String eventName, String jsonEventString,
136 final Object decodedJsonObject) throws ApexEventException {
138 final List<ApexEvent> eventList = new ArrayList<>();
140 // Check if it's a list of JSON objects or a list of strings
141 @SuppressWarnings("unchecked")
142 final List<Object> decodedJsonList = (List<Object>) decodedJsonObject;
144 // Decode each of the list elements in sequence
145 for (final Object jsonListObject : decodedJsonList) {
146 if (jsonListObject instanceof String) {
147 eventList.add(jsonStringApexEvent(eventName, (String) jsonListObject));
148 } else if (jsonListObject instanceof JsonObject) {
149 eventList.add(jsonObject2ApexEvent(eventName, (JsonObject) jsonListObject));
150 } else if (jsonListObject instanceof LinkedTreeMap) {
151 eventList.add(jsonObject2ApexEvent(eventName, new Gson().toJsonTree(jsonListObject).getAsJsonObject()));
153 throw new ApexEventException("incoming event (" + jsonEventString
154 + ") is a JSON object array containing an invalid object " + jsonListObject);
165 public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
166 // Check the Apex event
167 if (apexEvent == null) {
168 LOGGER.warn("event processing failed, Apex event is null");
169 throw new ApexEventException("event processing failed, Apex event is null");
172 if (jsonPars.getPojoField() == null) {
173 return fromApexEventWithFields(apexEvent);
175 return fromApexEventPojo(apexEvent);
180 * Serialise an Apex event to a JSON string field by field.
182 * @param apexEvent the event to Serialise
183 * @return the Serialise event as JSON
184 * @throws ApexEventException exceptions on marshaling to JSON
186 private Object fromApexEventWithFields(final ApexEvent apexEvent) {
187 // Get the event definition for the event from the model service
188 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
189 apexEvent.getVersion());
191 // Use a GSON Json object to marshal the Apex event to JSON
192 final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
193 final JsonObject jsonObject = new JsonObject();
195 jsonObject.addProperty(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
196 jsonObject.addProperty(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
197 jsonObject.addProperty(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
198 jsonObject.addProperty(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
199 jsonObject.addProperty(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
201 if (apexEvent.getExceptionMessage() != null) {
202 jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
205 for (final AxField eventField : eventDefinition.getFields()) {
206 final String fieldName = eventField.getKey().getLocalName();
208 if (!apexEvent.containsKey(fieldName)) {
209 if (!eventField.getOptional()) {
210 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. " + "Field \""
211 + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
212 LOGGER.debug(errorMessage);
213 throw new ApexEventRuntimeException(errorMessage);
218 final Object fieldValue = apexEvent.get(fieldName);
220 // Get the schema helper
221 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
222 eventField.getSchema());
223 jsonObject.add(fieldName, (JsonElement) fieldSchemaHelper.marshal2Object(fieldValue));
226 // Output JSON string in a pretty format
227 return gson.toJson(jsonObject);
231 * Serialise an Apex event to a JSON string as a single POJO.
233 * @param apexEvent the event to Serialise
234 * @return the Serialise event as JSON
235 * @throws ApexEventException exceptions on marshaling to JSON
237 private Object fromApexEventPojo(ApexEvent apexEvent) throws ApexEventException {
238 // Get the event definition for the event from the model service
239 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
240 apexEvent.getVersion());
242 if (eventDefinition.getFields().isEmpty()) {
243 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
244 + jsonPars.getPojoField() + " not found, no fields defined on event.";
245 LOGGER.debug(errorMessage);
246 throw new ApexEventException(errorMessage);
249 if (eventDefinition.getFields().size() != 1) {
250 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
251 + jsonPars.getPojoField() + ", "
252 + " one and only one field may be defined on a POJO event definition.";
253 LOGGER.debug(errorMessage);
254 throw new ApexEventException(errorMessage);
257 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
259 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
260 final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. Field "
261 + jsonPars.getPojoField() + " not found on POJO event definition.";
262 LOGGER.debug(errorMessage);
263 throw new ApexEventException(errorMessage);
266 final Object fieldValue = apexEvent.get(jsonPars.getPojoField());
268 // Get the schema helper
269 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
270 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
272 return fieldSchemaHelper.marshal2String(fieldValue);
276 * This method converts a JSON object into an Apex event.
278 * @param eventName the name of the event
279 * @param jsonEventString the JSON string that holds the event
280 * @return the apex event that we have converted the JSON object into
281 * @throws ApexEventException thrown on unmarshaling exceptions
283 private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
284 throws ApexEventException {
285 // Use GSON to read the event string
286 final JsonObject jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
289 if (jsonObject == null || !jsonObject.isJsonObject()) {
290 throw new ApexEventException(
291 "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
294 return jsonObject2ApexEvent(eventName, jsonObject);
298 * This method converts a JSON object into an Apex event.
300 * @param eventName the name of the event
301 * @param jsonObject the JSON object that holds the event
302 * @return the apex event that we have converted the JSON object into
303 * @throws ApexEventException thrown on unmarshaling exceptions
305 private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
306 throws ApexEventException {
307 // Process the mandatory Apex header
308 final ApexEvent apexEvent = processApexEventHeader(eventName, jsonObject);
310 // Get the event definition for the event from the model service
311 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
312 apexEvent.getVersion());
314 if (jsonPars.getPojoField() == null) {
315 jsonObject2ApexEventWithFields(jsonObject, apexEvent, eventDefinition);
317 jsonObject2ApexEventPojo(jsonObject, apexEvent, eventDefinition);
324 * Decode an Apex event field by field.
326 * @param jsonObject the JSON representation of the event
327 * @param apexEvent the incoming event header
328 * @param eventDefinition the definition of the event from the model
329 * @throws ApexEventException on decode errors
331 private void jsonObject2ApexEventWithFields(final JsonObject jsonObject, final ApexEvent apexEvent,
332 final AxEvent eventDefinition) throws ApexEventException {
333 // Iterate over the input fields in the event
334 for (final AxField eventField : eventDefinition.getFields()) {
335 final String fieldName = eventField.getKey().getLocalName();
336 if (!hasJsonField(jsonObject, fieldName)) {
337 if (!eventField.getOptional()) {
338 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. "
339 + "Field \"" + fieldName + "\" is missing, but is mandatory.";
340 LOGGER.debug(errorMessage);
341 throw new ApexEventException(errorMessage);
346 final JsonElement fieldValue = getJsonField(jsonObject, fieldName, null, !eventField.getOptional());
348 if (fieldValue != null && !fieldValue.isJsonNull()) {
349 // Get the schema helper
350 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
351 eventField.getSchema());
352 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
354 apexEvent.put(fieldName, null);
360 * Decode an Apex event as a single POJO.
362 * @param jsonObject the JSON representation of the event
363 * @param apexEvent the incoming event header
364 * @param eventDefinition the definition of the event from the model
365 * @throws ApexEventException on decode errors
367 private void jsonObject2ApexEventPojo(JsonObject jsonObject, ApexEvent apexEvent, AxEvent eventDefinition)
368 throws ApexEventException {
370 if (eventDefinition.getFields().isEmpty()) {
371 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
372 + jsonPars.getPojoField() + " not found, no fields defined on event.";
373 LOGGER.debug(errorMessage);
374 throw new ApexEventException(errorMessage);
377 if (eventDefinition.getFields().size() != 1) {
378 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
379 + jsonPars.getPojoField()
380 + ", one and only one field may be defined on a POJO event definition.";
381 LOGGER.debug(errorMessage);
382 throw new ApexEventException(errorMessage);
385 AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
387 if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
388 final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. Field "
389 + jsonPars.getPojoField() + " not found on POJO event definition.";
390 LOGGER.debug(errorMessage);
391 throw new ApexEventException(errorMessage);
394 // Get the schema helper
395 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory()
396 .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
397 apexEvent.put(jsonPars.getPojoField(), fieldSchemaHelper.createNewInstance(jsonObject));
401 * This method processes the event header of an Apex event.
403 * @param parameterEventName the name of the event from the parameters
404 * @param jsonObject the JSON object containing the JSON representation of the incoming event
405 * @return an apex event constructed using the header fields of the event
406 * @throws ApexEventRuntimeException the apex event runtime exception
407 * @throws ApexEventException on invalid events with missing header fields
409 private ApexEvent processApexEventHeader(final String parameterEventName, final JsonObject jsonObject)
410 throws ApexEventException {
412 final String eventName = getHeaderName(jsonObject, parameterEventName);
414 String eventVersion = getHeaderVersion(jsonObject);
416 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(eventName, eventVersion);
418 if (eventDefinition == null) {
419 if (eventVersion == null) {
420 throw new ApexEventRuntimeException(
421 "an event definition for an event named \"" + eventName + "\" not found in Apex model");
423 throw new ApexEventRuntimeException("an event definition for an event named \"" + eventName
424 + "\" with version \"" + eventVersion + "\" not found in Apex model");
428 // Use the defined event version if no version is specified on the incoming fields
429 if (eventVersion == null) {
430 eventVersion = eventDefinition.getKey().getVersion();
433 final String eventNamespace = getHeaderNamespace(jsonObject, eventName, eventDefinition);
434 final String eventSource = getHeaderSource(jsonObject, eventDefinition);
435 final String eventTarget = getHeaderTarget(jsonObject, eventDefinition);
437 return new ApexEvent(eventName, eventVersion, eventNamespace, eventSource, eventTarget);
441 * Determine the name field of the event header.
443 * @param jsonObject the event in JSON format
444 * @param parameterEventName the configured event name from the parameters
445 * @return the event name to use on the event header
447 private String getHeaderName(final JsonObject jsonObject, final String parameterEventName) {
448 final String jsonEventName = getJsonStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD,
449 jsonPars.getNameAlias(), ApexEvent.NAME_REGEXP, false);
451 // Check that an event name has been specified
452 if (jsonEventName == null && parameterEventName == null) {
453 throw new ApexEventRuntimeException(
454 "event received without mandatory parameter \"name\" on configuration or on event");
457 // Check if an event name was specified on the event parameters
458 if (jsonEventName != null) {
459 if (parameterEventName != null && !parameterEventName.equals(jsonEventName)) {
460 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\","
461 + " using configured event name", jsonEventName, parameterEventName);
463 return jsonEventName;
465 return parameterEventName;
470 * Determine the version field of the event header.
472 * @param jsonObject the event in JSON format
473 * @return the event version
475 private String getHeaderVersion(final JsonObject jsonObject) {
476 // Find the event definition in the model service. If version is null, the newest event
477 // definition in the model service is used
478 return getJsonStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD, jsonPars.getVersionAlias(),
479 ApexEvent.VERSION_REGEXP, false);
483 * Determine the name space field of the event header.
485 * @param jsonObject the event in JSON format
486 * @param eventName the name of the event
487 * @param eventDefinition the definition of the event structure
488 * @return the event version
490 private String getHeaderNamespace(final JsonObject jsonObject, final String name, final AxEvent eventDefinition) {
491 // Check the name space is OK if it is defined, if not, use the name space from the model
492 String namespace = getJsonStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD,
493 jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
494 if (namespace != null) {
495 if (!namespace.equals(eventDefinition.getNameSpace())) {
496 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
497 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
498 + "\" for that event in the Apex model");
501 namespace = eventDefinition.getNameSpace();
507 * Determine the source field of the event header.
509 * @param jsonObject the event in JSON format
510 * @param eventDefinition the definition of the event structure
511 * @return the event version
513 private String getHeaderSource(final JsonObject jsonObject, final AxEvent eventDefinition) {
514 // For source, use the defined source only if the source is not found on the incoming event
515 String source = getJsonStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD, jsonPars.getSourceAlias(),
516 ApexEvent.SOURCE_REGEXP, false);
517 if (source == null) {
518 source = eventDefinition.getSource();
524 * Determine the target field of the event header.
526 * @param jsonObject the event in JSON format
527 * @param eventDefinition the definition of the event structure
528 * @return the event version
530 private String getHeaderTarget(final JsonObject jsonObject, final AxEvent eventDefinition) {
531 // For target, use the defined source only if the source is not found on the incoming event
532 String target = getJsonStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD, jsonPars.getTargetAlias(),
533 ApexEvent.TARGET_REGEXP, false);
534 if (target == null) {
535 target = eventDefinition.getTarget();
541 * This method gets an event string field from a JSON object.
543 * @param jsonObject the JSON object containing the JSON representation of the incoming event
544 * @param fieldName the field name to find in the event
545 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
546 * @param fieldRegexp the regular expression to check the field against for validity
547 * @param mandatory true if the field is mandatory
548 * @return the value of the field in the JSON object or null if the field is optional
549 * @throws ApexEventRuntimeException the apex event runtime exception
551 private String getJsonStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
552 final String fieldRegexp, final boolean mandatory) {
553 // Get the JSON field for the string field
554 final JsonElement jsonField = getJsonField(jsonObject, fieldName, fieldAlias, mandatory);
556 // Null strings are allowed
557 if (jsonField == null || jsonField.isJsonNull()) {
561 // Check if this is a string field
562 String fieldValueString = null;
564 fieldValueString = jsonField.getAsString();
565 } catch (final Exception e) {
566 // The element is not a string so throw an error
567 throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
568 + jsonField.getClass().getName() + "\" is not a string value", e);
571 // Is regular expression checking required
572 if (fieldRegexp == null) {
573 return fieldValueString;
576 // Check the event field against its regular expression
577 if (!fieldValueString.matches(fieldRegexp)) {
578 throw new ApexEventRuntimeException(
579 "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
582 return fieldValueString;
586 * This method gets an event field from a JSON object.
588 * @param jsonObject the JSON object containing the JSON representation of the incoming event
589 * @param fieldName the field name to find in the event
590 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
591 * @param mandatory true if the field is mandatory
592 * @return the value of the field in the JSON object or null if the field is optional
593 * @throws ApexEventRuntimeException the apex event runtime exception
595 private JsonElement getJsonField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
596 final boolean mandatory) {
598 // Check if we should use the alias for this field
599 String fieldToFind = fieldName;
600 if (fieldAlias != null) {
601 fieldToFind = fieldAlias;
604 // Get the event field
605 final JsonElement eventElement = jsonObject.get(fieldToFind);
606 if (eventElement == null) {
610 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
618 * This method if a JSON object has a named field.
620 * @param jsonObject the JSON object containing the JSON representation of the incoming event
621 * @param fieldName the field name to find in the event
622 * @return true if the field is present
623 * @throws ApexEventRuntimeException the apex event runtime exception
625 private boolean hasJsonField(final JsonObject jsonObject, final String fieldName) {
626 // check for the field
627 return jsonObject.has(fieldName);