d64d46a7c047c6754df64721c48f39ea8e657c45
[policy/apex-pdp.git] /
1 /*-
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 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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.apex.service.engine.event.impl.jsonprotocolplugin;
24
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;
45
46 /**
47  * The Class Apex2JSONEventConverter converts {@link ApexEvent} instances to and from JSON string representations of
48  * Apex events.
49  *
50  * @author Liam Fallon (liam.fallon@ericsson.com)
51  */
52 public class Apex2JsonEventConverter implements ApexEventProtocolConverter {
53     private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2JsonEventConverter.class);
54
55     // Recurring string constants
56     private static final String ERROR_PARSING = "error parsing ";
57     private static final String ERROR_CODING = "error coding ";
58
59     // The parameters for the JSON event protocol
60     private JsonEventProtocolParameters jsonPars;
61
62     /**
63      * {@inheritDoc}.
64      */
65     @Override
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);
71         }
72
73         jsonPars = (JsonEventProtocolParameters) parameters;
74     }
75
76     /**
77      * {@inheritDoc}.
78      */
79     @Override
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");
84         }
85
86         // Cast the event to a string, if our conversion is correctly configured, this cast should
87         // always work
88         String jsonEventString = null;
89         try {
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);
94         }
95
96         // The list of events we will return
97         final List<ApexEvent> eventList = new ArrayList<>();
98
99         try {
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,
102                             Object.class);
103
104             // Check if we have a list of objects
105             if (decodedJsonObject instanceof List) {
106                 eventList.addAll(decodeEventList(eventName, jsonEventString, decodedJsonObject));
107             } else {
108                 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
109             }
110         } catch (final Exception e) {
111             throw new ApexEventException("Failed to unmarshal JSON event, event=" + jsonEventString, e);
112         }
113
114         // Return the list of events we have unmarshalled
115         return eventList;
116     }
117
118     /**
119      * Decode a list of Apex events.
120      *
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
126      */
127     private Collection<? extends ApexEvent> decodeEventList(final String eventName, String jsonEventString,
128                     final Object decodedJsonObject) throws ApexEventException {
129
130         final List<ApexEvent> eventList = new ArrayList<>();
131
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;
135
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()));
144             } else {
145                 throw new ApexEventException("incoming event (" + jsonEventString
146                                 + ") is a JSON object array containing an invalid object " + jsonListObject);
147             }
148         }
149
150         return eventList;
151     }
152
153     /**
154      * {@inheritDoc}.
155      */
156     @Override
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");
161         }
162
163         if (jsonPars.getPojoField() == null) {
164             return fromApexEventWithFields(apexEvent);
165         } else {
166             return fromApexEventPojo(apexEvent);
167         }
168     }
169
170     /**
171      * Serialise an Apex event to a JSON string field by field.
172      *
173      * @param apexEvent the event to Serialise
174      * @return the Serialise event as JSON
175      * @throws ApexEventException exceptions on marshaling to JSON
176      */
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());
181
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();
185
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
192         if (apexEvent.getExceptionMessage() != null) {
193             jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
194         }
195
196         for (final AxField eventField : eventDefinition.getFields()) {
197             final String fieldName = eventField.getKey().getLocalName();
198
199             if (!apexEvent.containsKey(fieldName)) {
200                 if (!eventField.getOptional()) {
201                     final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. " + "Field \""
202                                     + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
203                     throw new ApexEventRuntimeException(errorMessage);
204                 }
205                 continue;
206             }
207
208             final Object fieldValue = apexEvent.get(fieldName);
209
210             // Get the schema helper
211             final var fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
212                             eventField.getSchema());
213             jsonObject.add(fieldName, (JsonElement) fieldSchemaHelper.marshal2Object(fieldValue));
214         }
215
216         // Output JSON string in a pretty format
217         return gson.toJson(jsonObject);
218     }
219
220     /**
221      * Serialise an Apex event to a JSON string as a single POJO.
222      *
223      * @param apexEvent the event to Serialise
224      * @return the Serialise event as JSON
225      * @throws ApexEventException exceptions on marshaling to JSON
226      */
227     private Object fromApexEventPojo(ApexEvent apexEvent) throws ApexEventException {
228         // Get the event definition for the event from the model service
229         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
230                         apexEvent.getVersion());
231
232         if (eventDefinition.getFields().isEmpty()) {
233             final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
234                             + jsonPars.getPojoField() + " not found, no fields defined on event.";
235             throw new ApexEventException(errorMessage);
236         }
237
238         if (eventDefinition.getFields().size() != 1) {
239             final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json, Field "
240                             + jsonPars.getPojoField() + ", "
241                             + " one and only one field may be defined on a POJO event definition.";
242             throw new ApexEventException(errorMessage);
243         }
244
245         AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
246
247         if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
248             final String errorMessage = ERROR_CODING + eventDefinition.getId() + " event to Json. Field "
249                             + jsonPars.getPojoField() + " not found on POJO event definition.";
250             throw new ApexEventException(errorMessage);
251         }
252
253         final Object fieldValue = apexEvent.get(jsonPars.getPojoField());
254
255         // Get the schema helper
256         final var fieldSchemaHelper = new SchemaHelperFactory()
257                         .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
258
259         return fieldSchemaHelper.marshal2String(fieldValue);
260     }
261
262     /**
263      * This method converts a JSON object into an Apex event.
264      *
265      * @param eventName the name of the event
266      * @param jsonEventString the JSON string that holds the event
267      * @return the apex event that we have converted the JSON object into
268      * @throws ApexEventException thrown on unmarshaling exceptions
269      */
270     private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
271                     throws ApexEventException {
272         // Use GSON to read the event string
273         final var jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
274                         JsonObject.class);
275
276         if (jsonObject == null || !jsonObject.isJsonObject()) {
277             throw new ApexEventException(
278                             "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
279         }
280
281         return jsonObject2ApexEvent(eventName, jsonObject);
282     }
283
284     /**
285      * This method converts a JSON object into an Apex event.
286      *
287      * @param eventName the name of the event
288      * @param jsonObject the JSON object that holds the event
289      * @return the apex event that we have converted the JSON object into
290      * @throws ApexEventException thrown on unmarshaling exceptions
291      */
292     private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
293                     throws ApexEventException {
294         // Process the mandatory Apex header
295         final var apexEvent = processApexEventHeader(eventName, jsonObject);
296
297         // Get the event definition for the event from the model service
298         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
299                         apexEvent.getVersion());
300
301         if (jsonPars.getPojoField() == null) {
302             jsonObject2ApexEventWithFields(jsonObject, apexEvent, eventDefinition);
303         } else {
304             jsonObject2ApexEventPojo(jsonObject, apexEvent, eventDefinition);
305         }
306
307         return apexEvent;
308     }
309
310     /**
311      * Decode an Apex event field by field.
312      *
313      * @param jsonObject the JSON representation of the event
314      * @param apexEvent the incoming event header
315      * @param eventDefinition the definition of the event from the model
316      * @throws ApexEventException on decode errors
317      */
318     private void jsonObject2ApexEventWithFields(final JsonObject jsonObject, final ApexEvent apexEvent,
319                     final AxEvent eventDefinition) throws ApexEventException {
320         // Iterate over the input fields in the event
321         for (final AxField eventField : eventDefinition.getFields()) {
322             final String fieldName = eventField.getKey().getLocalName();
323             if (!hasJsonField(jsonObject, fieldName)) {
324                 if (!eventField.getOptional()) {
325                     final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. "
326                                     + "Field \"" + fieldName + "\" is missing, but is mandatory.";
327                     throw new ApexEventException(errorMessage);
328                 }
329                 continue;
330             }
331
332             final JsonElement fieldValue = getJsonField(jsonObject, fieldName, null, !eventField.getOptional());
333
334             if (fieldValue != null && !fieldValue.isJsonNull()) {
335                 // Get the schema helper
336                 final var fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
337                                 eventField.getSchema());
338                 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
339             } else {
340                 apexEvent.put(fieldName, null);
341             }
342         }
343     }
344
345     /**
346      * Decode an Apex event as a single POJO.
347      *
348      * @param jsonObject the JSON representation of the event
349      * @param apexEvent the incoming event header
350      * @param eventDefinition the definition of the event from the model
351      * @throws ApexEventException on decode errors
352      */
353     private void jsonObject2ApexEventPojo(JsonObject jsonObject, ApexEvent apexEvent, AxEvent eventDefinition)
354                     throws ApexEventException {
355
356         if (eventDefinition.getFields().isEmpty()) {
357             final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
358                             + jsonPars.getPojoField() + " not found, no fields defined on event.";
359             throw new ApexEventException(errorMessage);
360         }
361
362         if (eventDefinition.getFields().size() != 1) {
363             final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json, Field "
364                             + jsonPars.getPojoField()
365                             + ", one and only one field may be defined on a POJO event definition.";
366             throw new ApexEventException(errorMessage);
367         }
368
369         AxField pojoFieldDefinition = eventDefinition.getFields().iterator().next();
370
371         if (!jsonPars.getPojoField().equals(pojoFieldDefinition.getKey().getLocalName())) {
372             final String errorMessage = ERROR_PARSING + eventDefinition.getId() + " event from Json. Field "
373                             + jsonPars.getPojoField() + " not found on POJO event definition.";
374             throw new ApexEventException(errorMessage);
375         }
376
377         // Get the schema helper
378         final var fieldSchemaHelper = new SchemaHelperFactory()
379                         .createSchemaHelper(pojoFieldDefinition.getKey(), pojoFieldDefinition.getSchema());
380         apexEvent.put(jsonPars.getPojoField(), fieldSchemaHelper.createNewInstance(jsonObject));
381     }
382
383     /**
384      * This method processes the event header of an Apex event.
385      *
386      * @param parameterEventName the name of the event from the parameters
387      * @param jsonObject the JSON object containing the JSON representation of the incoming event
388      * @return an apex event constructed using the header fields of the event
389      * @throws ApexEventRuntimeException the apex event runtime exception
390      * @throws ApexEventException on invalid events with missing header fields
391      */
392     private ApexEvent processApexEventHeader(final String parameterEventName, final JsonObject jsonObject)
393                     throws ApexEventException {
394
395         final String eventName = getHeaderName(jsonObject, parameterEventName);
396
397         String eventVersion = getHeaderVersion(jsonObject);
398
399         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(eventName, eventVersion);
400
401         if (eventDefinition == null) {
402             if (eventVersion == null) {
403                 throw new ApexEventRuntimeException(
404                                 "an event definition for an event named \"" + eventName + "\" not found in Apex model");
405             } else {
406                 throw new ApexEventRuntimeException("an event definition for an event named \"" + eventName
407                                 + "\" with version \"" + eventVersion + "\" not found in Apex model");
408             }
409         }
410
411         // Use the defined event version if no version is specified on the incoming fields
412         if (eventVersion == null) {
413             eventVersion = eventDefinition.getKey().getVersion();
414         }
415
416         final String eventNamespace = getHeaderNamespace(jsonObject, eventName, eventDefinition);
417         final String eventSource = getHeaderSource(jsonObject, eventDefinition);
418         final String eventTarget = getHeaderTarget(jsonObject, eventDefinition);
419
420         return new ApexEvent(eventName, eventVersion, eventNamespace, eventSource, eventTarget);
421     }
422
423     /**
424      * Determine the name field of the event header.
425      *
426      * @param jsonObject the event in JSON format
427      * @param parameterEventName the configured event name from the parameters
428      * @return the event name to use on the event header
429      */
430     private String getHeaderName(final JsonObject jsonObject, final String parameterEventName) {
431         final var jsonEventName = getJsonStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD,
432                         jsonPars.getNameAlias(), ApexEvent.NAME_REGEXP, false);
433
434         // Check that an event name has been specified
435         if (jsonEventName == null && parameterEventName == null) {
436             throw new ApexEventRuntimeException(
437                             "event received without mandatory parameter \"name\" on configuration or on event");
438         }
439
440         // Check if an event name was specified on the event parameters
441         if (jsonEventName != null) {
442             if (parameterEventName != null && !parameterEventName.equals(jsonEventName)) {
443                 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\","
444                                 + " using configured event name", jsonEventName, parameterEventName);
445             }
446             return jsonEventName;
447         } else {
448             return parameterEventName;
449         }
450     }
451
452     /**
453      * Determine the version field of the event header.
454      *
455      * @param jsonObject the event in JSON format
456      * @return the event version
457      */
458     private String getHeaderVersion(final JsonObject jsonObject) {
459         // Find the event definition in the model service. If version is null, the newest event
460         // definition in the model service is used
461         return getJsonStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD, jsonPars.getVersionAlias(),
462                         ApexEvent.VERSION_REGEXP, false);
463     }
464
465     /**
466      * Determine the name space field of the event header.
467      *
468      * @param jsonObject the event in JSON format
469      * @param eventName the name of the event
470      * @param eventDefinition the definition of the event structure
471      * @return the event version
472      */
473     private String getHeaderNamespace(final JsonObject jsonObject, final String name, final AxEvent eventDefinition) {
474         // Check the name space is OK if it is defined, if not, use the name space from the model
475         var namespace = getJsonStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD,
476                         jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
477         if (namespace != null) {
478             if (!namespace.equals(eventDefinition.getNameSpace())) {
479                 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
480                                 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
481                                 + "\" for that event in the Apex model");
482             }
483         } else {
484             namespace = eventDefinition.getNameSpace();
485         }
486         return namespace;
487     }
488
489     /**
490      * Determine the source field of the event header.
491      *
492      * @param jsonObject the event in JSON format
493      * @param eventDefinition the definition of the event structure
494      * @return the event version
495      */
496     private String getHeaderSource(final JsonObject jsonObject, final AxEvent eventDefinition) {
497         // For source, use the defined source only if the source is not found on the incoming event
498         var source = getJsonStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD, jsonPars.getSourceAlias(),
499                         ApexEvent.SOURCE_REGEXP, false);
500         if (source == null) {
501             source = eventDefinition.getSource();
502         }
503         return source;
504     }
505
506     /**
507      * Determine the target field of the event header.
508      *
509      * @param jsonObject the event in JSON format
510      * @param eventDefinition the definition of the event structure
511      * @return the event version
512      */
513     private String getHeaderTarget(final JsonObject jsonObject, final AxEvent eventDefinition) {
514         // For target, use the defined source only if the source is not found on the incoming event
515         var target = getJsonStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD, jsonPars.getTargetAlias(),
516                         ApexEvent.TARGET_REGEXP, false);
517         if (target == null) {
518             target = eventDefinition.getTarget();
519         }
520         return target;
521     }
522
523     /**
524      * This method gets an event string field from a JSON object.
525      *
526      * @param jsonObject the JSON object containing the JSON representation of the incoming event
527      * @param fieldName the field name to find in the event
528      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
529      * @param fieldRegexp the regular expression to check the field against for validity
530      * @param mandatory true if the field is mandatory
531      * @return the value of the field in the JSON object or null if the field is optional
532      * @throws ApexEventRuntimeException the apex event runtime exception
533      */
534     private String getJsonStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
535                     final String fieldRegexp, final boolean mandatory) {
536         // Get the JSON field for the string field
537         final JsonElement jsonField = getJsonField(jsonObject, fieldName, fieldAlias, mandatory);
538
539         // Null strings are allowed
540         if (jsonField == null || jsonField.isJsonNull()) {
541             return null;
542         }
543
544         // Check if this is a string field
545         String fieldValueString = null;
546         try {
547             fieldValueString = jsonField.getAsString();
548         } catch (final Exception e) {
549             // The element is not a string so throw an error
550             throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
551                             + jsonField.getClass().getName() + "\" is not a string value", e);
552         }
553
554         // Is regular expression checking required
555         if (fieldRegexp == null) {
556             return fieldValueString;
557         }
558
559         // Check the event field against its regular expression
560         if (!fieldValueString.matches(fieldRegexp)) {
561             throw new ApexEventRuntimeException(
562                             "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
563         }
564
565         return fieldValueString;
566     }
567
568     /**
569      * This method gets an event field from a JSON object.
570      *
571      * @param jsonObject the JSON object containing the JSON representation of the incoming event
572      * @param fieldName the field name to find in the event
573      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
574      * @param mandatory true if the field is mandatory
575      * @return the value of the field in the JSON object or null if the field is optional
576      * @throws ApexEventRuntimeException the apex event runtime exception
577      */
578     private JsonElement getJsonField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
579                     final boolean mandatory) {
580
581         // Check if we should use the alias for this field
582         String fieldToFind = fieldName;
583         if (fieldAlias != null) {
584             fieldToFind = fieldAlias;
585         }
586
587         // Get the event field
588         final JsonElement eventElement = jsonObject.get(fieldToFind);
589         if (eventElement == null) {
590             if (!mandatory) {
591                 return null;
592             } else {
593                 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
594             }
595         }
596
597         return eventElement;
598     }
599
600     /**
601      * This method if a JSON object has a named field.
602      *
603      * @param jsonObject the JSON object containing the JSON representation of the incoming event
604      * @param fieldName the field name to find in the event
605      * @return true if the field is present
606      * @throws ApexEventRuntimeException the apex event runtime exception
607      */
608     private boolean hasJsonField(final JsonObject jsonObject, final String fieldName) {
609         // check for the field
610         return jsonObject.has(fieldName);
611     }
612 }