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.plugins.event.protocol.yaml;
23 import java.util.ArrayList;
24 import java.util.LinkedHashMap;
25 import java.util.List;
28 import org.onap.policy.apex.context.SchemaHelper;
29 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
30 import org.onap.policy.apex.model.basicmodel.service.ModelService;
31 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
32 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
33 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
34 import org.onap.policy.apex.service.engine.event.ApexEvent;
35 import org.onap.policy.apex.service.engine.event.ApexEventException;
36 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
37 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
38 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
39 import org.slf4j.ext.XLogger;
40 import org.slf4j.ext.XLoggerFactory;
41 import org.yaml.snakeyaml.DumperOptions.FlowStyle;
42 import org.yaml.snakeyaml.Yaml;
45 * The Class Apex2YamlEventConverter converts {@link ApexEvent} instances to and from YAML string representations of
48 * @author Liam Fallon (liam.fallon@ericsson.com)
50 public class Apex2YamlEventConverter implements ApexEventProtocolConverter {
51 private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2YamlEventConverter.class);
53 // The parameters for the YAML event protocol
54 private YamlEventProtocolParameters yamlPars;
59 * @see org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter#init(org.onap.policy.
60 * apex.service.parameters.eventprotocol.EventProtocolParameters)
63 public void init(final EventProtocolParameters parameters) {
64 // Check and get the YAML parameters
65 if (!(parameters instanceof YamlEventProtocolParameters)) {
66 final String errorMessage = "specified consumer properties are not applicable to the YAML event protocol";
67 LOGGER.warn(errorMessage);
68 throw new ApexEventRuntimeException(errorMessage);
71 yamlPars = (YamlEventProtocolParameters) parameters;
77 * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#toApexEvent(java.lang.String, java.lang.Object)
80 public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
81 // Check the event eventObject
82 if (eventObject == null) {
83 LOGGER.warn("event processing failed, event is null");
84 throw new ApexEventException("event processing failed, event is null");
87 // Cast the event to a string, if our conversion is correctly configured, this cast should
89 if (!(eventObject instanceof String)) {
90 final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
91 LOGGER.debug(errorMessage);
92 throw new ApexEventException(errorMessage);
95 final String yamlEventString = (String) eventObject;
97 // The list of events we will return
98 final List<ApexEvent> eventList = new ArrayList<>();
100 // Convert the YAML document string into an object
101 Object yamlObject = new Yaml().load(yamlEventString);
103 // If the incoming YAML did not create a map it is a primitive type or a collection so we
104 // convert it into a map for processing
106 if (yamlObject instanceof Map) {
107 // We already have a map so just cast the object
108 yamlMap = (Map<?, ?>) yamlObject;
110 // Create a single entry map, new map creation and assignment is to avoid a
111 // type checking warning
112 LinkedHashMap<String, Object> newYamlMap = new LinkedHashMap<>();
113 newYamlMap.put(yamlPars.getYamlFieldName(), yamlObject);
114 yamlMap = newYamlMap;
118 eventList.add(yamlMap2ApexEvent(eventName, yamlMap));
119 } catch (final Exception e) {
120 final String errorString = "Failed to unmarshal YAML event: " + e.getMessage() + ", event="
122 LOGGER.warn(errorString, e);
123 throw new ApexEventException(errorString, e);
126 // Return the list of events we have unmarshalled
133 * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#fromApexEvent(org.onap.policy.
134 * apex.service.engine.event.ApexEvent)
137 public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
138 // Check the Apex event
139 if (apexEvent == null) {
140 LOGGER.warn("event processing failed, Apex event is null");
141 throw new ApexEventException("event processing failed, Apex event is null");
144 // Get the event definition for the event from the model service
145 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
146 apexEvent.getVersion());
148 // Create a map for output of the APEX event to YAML
149 LinkedHashMap<String, Object> yamlMap = new LinkedHashMap<>();
151 yamlMap.put(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
152 yamlMap.put(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
153 yamlMap.put(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
154 yamlMap.put(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
155 yamlMap.put(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
157 if (apexEvent.getExceptionMessage() != null) {
158 yamlMap.put(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
161 for (final AxField eventField : eventDefinition.getFields()) {
162 final String fieldName = eventField.getKey().getLocalName();
164 if (!apexEvent.containsKey(fieldName)) {
165 if (!eventField.getOptional()) {
166 final String errorMessage = "error parsing " + eventDefinition.getId() + " event to Json. "
167 + "Field \"" + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
168 LOGGER.debug(errorMessage);
169 throw new ApexEventRuntimeException(errorMessage);
174 yamlMap.put(fieldName, apexEvent.get(fieldName));
177 // Use Snake YAML to convert the APEX event to YAML
178 Yaml yaml = new Yaml();
179 return yaml.dumpAs(yamlMap, null, FlowStyle.BLOCK);
183 * This method converts a YAML map into an Apex event.
185 * @param eventName the name of the event
186 * @param yamlMap the YAML map that holds the event
187 * @return the apex event that we have converted the JSON object into
188 * @throws ApexEventException thrown on unmarshaling exceptions
190 private ApexEvent yamlMap2ApexEvent(final String eventName, final Map<?, ?> yamlMap) throws ApexEventException {
191 // Process the mandatory Apex header
192 final ApexEvent apexEvent = processApexEventHeader(eventName, yamlMap);
194 // Get the event definition for the event from the model service
195 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
196 apexEvent.getVersion());
198 // Iterate over the input fields in the event
199 for (final AxField eventField : eventDefinition.getFields()) {
200 final String fieldName = eventField.getKey().getLocalName();
201 if (!yamlMap.containsKey(fieldName)) {
202 if (!eventField.getOptional()) {
203 final String errorMessage = "error parsing " + eventDefinition.getId() + " event from Json. "
204 + "Field \"" + fieldName + "\" is missing, but is mandatory.";
205 LOGGER.debug(errorMessage);
206 throw new ApexEventException(errorMessage);
211 final Object fieldValue = getYamlField(yamlMap, fieldName, null, !eventField.getOptional());
213 if (fieldValue != null) {
214 // Get the schema helper
215 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
216 eventField.getSchema());
217 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
219 apexEvent.put(fieldName, null);
227 * This method processes the event header of an Apex event.
229 * @param eventName the name of the event
230 * @param yamlMap the YAML map that holds the event
231 * @return an apex event constructed using the header fields of the event
232 * @throws ApexEventRuntimeException the apex event runtime exception
233 * @throws ApexEventException on invalid events with missing header fields
235 private ApexEvent processApexEventHeader(final String eventName, final Map<?, ?> yamlMap)
236 throws ApexEventException {
237 String name = getYamlStringField(yamlMap, ApexEvent.NAME_HEADER_FIELD, yamlPars.getNameAlias(),
238 ApexEvent.NAME_REGEXP, false);
240 // Check that an event name has been specified
241 if (name == null && eventName == null) {
242 throw new ApexEventRuntimeException(
243 "event received without mandatory parameter \"name\" on configuration or on event");
246 // Check if an event name was specified on the event parameters
247 if (eventName != null) {
248 if (name != null && !eventName.equals(name)) {
249 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\", "
250 + "using configured event name", name, eventName);
255 // Now, find the event definition in the model service. If version is null, the newest event
256 // definition in the model service is used
257 String version = getYamlStringField(yamlMap, ApexEvent.VERSION_HEADER_FIELD, yamlPars.getVersionAlias(),
258 ApexEvent.VERSION_REGEXP, false);
259 final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(name, version);
260 if (eventDefinition == null) {
261 throw new ApexEventRuntimeException("an event definition for an event named \"" + name
262 + "\" with version \"" + version + "\" not found in Apex model");
265 // Use the defined event version if no version is specified on the incoming fields
266 if (version == null) {
267 version = eventDefinition.getKey().getVersion();
270 String namespace = getEventHeaderNamespace(yamlMap, name, eventDefinition);
272 String source = getEventHeaderSource(yamlMap, eventDefinition);
274 String target = getHeaderTarget(yamlMap, eventDefinition);
276 return new ApexEvent(name, version, namespace, source, target);
280 * Get the event header name space.
282 * @param yamlMap the YAML map to read from
283 * @param eventDefinition the event definition
284 * @return the event header name space
286 private String getEventHeaderNamespace(final Map<?, ?> yamlMap, String name, final AxEvent eventDefinition) {
287 // Check the name space is OK if it is defined, if not, use the name space from the model
288 String namespace = getYamlStringField(yamlMap, ApexEvent.NAMESPACE_HEADER_FIELD, yamlPars.getNameSpaceAlias(),
289 ApexEvent.NAMESPACE_REGEXP, false);
290 if (namespace != null) {
291 if (!namespace.equals(eventDefinition.getNameSpace())) {
292 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
293 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
294 + "\" for that event in the Apex model");
297 namespace = eventDefinition.getNameSpace();
303 * Get the event header source.
305 * @param yamlMap the YAML map to read from
306 * @param eventDefinition the event definition
307 * @return the event header source
309 private String getEventHeaderSource(final Map<?, ?> yamlMap, final AxEvent eventDefinition) {
310 // For source, use the defined source only if the source is not found on the incoming event
311 String source = getYamlStringField(yamlMap, ApexEvent.SOURCE_HEADER_FIELD, yamlPars.getSourceAlias(),
312 ApexEvent.SOURCE_REGEXP, false);
313 if (source == null) {
314 source = eventDefinition.getSource();
320 * Get the event header target.
322 * @param yamlMap the YAML map to read from
323 * @param eventDefinition the event definition
324 * @return the event header target
326 private String getHeaderTarget(final Map<?, ?> yamlMap, final AxEvent eventDefinition) {
327 // For target, use the defined source only if the source is not found on the incoming event
328 String target = getYamlStringField(yamlMap, ApexEvent.TARGET_HEADER_FIELD, yamlPars.getTargetAlias(),
329 ApexEvent.TARGET_REGEXP, false);
330 if (target == null) {
331 target = eventDefinition.getTarget();
337 * This method gets an event string field from a JSON object.
339 * @param yamlMap the YAML containing the YAML representation of the incoming event
340 * @param fieldName the field name to find in the event
341 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
342 * @param fieldRegexp the regular expression to check the field against for validity
343 * @param mandatory true if the field is mandatory
344 * @return the value of the field in the JSON object or null if the field is optional
345 * @throws ApexEventRuntimeException the apex event runtime exception
347 private String getYamlStringField(final Map<?, ?> yamlMap, final String fieldName, final String fieldAlias,
348 final String fieldRegexp, final boolean mandatory) {
349 // Get the YAML field for the string field
350 final Object yamlField = getYamlField(yamlMap, fieldName, fieldAlias, mandatory);
352 // Null strings are allowed
353 if (yamlField == null) {
357 if (!(yamlField instanceof String)) {
358 // The element is not a string so throw an error
359 throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
360 + yamlField.getClass().getCanonicalName() + "\" is not a string value");
363 final String fieldValueString = (String) yamlField;
365 // Is regular expression checking required
366 if (fieldRegexp == null) {
367 return fieldValueString;
370 // Check the event field against its regular expression
371 if (!fieldValueString.matches(fieldRegexp)) {
372 throw new ApexEventRuntimeException(
373 "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
376 return fieldValueString;
380 * This method gets an event field from a YAML object.
382 * @param yamlMap the YAML containing the YAML representation of the incoming event
383 * @param fieldName the field name to find in the event
384 * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
385 * @param mandatory true if the field is mandatory
386 * @return the value of the field in the YAML object or null if the field is optional
387 * @throws ApexEventRuntimeException the apex event runtime exception
389 private Object getYamlField(final Map<?, ?> yamlMap, final String fieldName, final String fieldAlias,
390 final boolean mandatory) {
392 // Check if we should use the alias for this field
393 String fieldToFind = fieldName;
394 if (fieldAlias != null) {
395 fieldToFind = fieldAlias;
398 // Get the event field
399 final Object eventElement = yamlMap.get(fieldToFind);
400 if (eventElement == null) {
404 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");