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.plugins.event.protocol.yaml;
 
  24 import java.util.ArrayList;
 
  25 import java.util.LinkedHashMap;
 
  26 import java.util.List;
 
  29 import org.onap.policy.apex.context.SchemaHelper;
 
  30 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
 
  31 import org.onap.policy.apex.model.basicmodel.service.ModelService;
 
  32 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
 
  33 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
 
  34 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
 
  35 import org.onap.policy.apex.service.engine.event.ApexEvent;
 
  36 import org.onap.policy.apex.service.engine.event.ApexEventException;
 
  37 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
 
  38 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
 
  39 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
 
  40 import org.slf4j.ext.XLogger;
 
  41 import org.slf4j.ext.XLoggerFactory;
 
  42 import org.yaml.snakeyaml.DumperOptions.FlowStyle;
 
  43 import org.yaml.snakeyaml.Yaml;
 
  46  * The Class Apex2YamlEventConverter converts {@link ApexEvent} instances to and from YAML string representations of
 
  49  * @author Liam Fallon (liam.fallon@ericsson.com)
 
  51 public class Apex2YamlEventConverter implements ApexEventProtocolConverter {
 
  52     private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2YamlEventConverter.class);
 
  54     // The parameters for the YAML event protocol
 
  55     private YamlEventProtocolParameters yamlPars;
 
  61     public void init(final EventProtocolParameters parameters) {
 
  62         // Check and get the YAML parameters
 
  63         if (!(parameters instanceof YamlEventProtocolParameters)) {
 
  64             final String errorMessage = "specified consumer properties are not applicable to the YAML event protocol";
 
  65             LOGGER.warn(errorMessage);
 
  66             throw new ApexEventRuntimeException(errorMessage);
 
  69         yamlPars = (YamlEventProtocolParameters) parameters;
 
  76     public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
 
  77         // Check the event eventObject
 
  78         if (eventObject == null) {
 
  79             LOGGER.warn("event processing failed, event is null");
 
  80             throw new ApexEventException("event processing failed, event is null");
 
  83         // Cast the event to a string, if our conversion is correctly configured, this cast should
 
  85         if (!(eventObject instanceof String)) {
 
  86             final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
 
  87             LOGGER.debug(errorMessage);
 
  88             throw new ApexEventException(errorMessage);
 
  91         final String yamlEventString = (String) eventObject;
 
  93         // The list of events we will return
 
  94         final List<ApexEvent> eventList = new ArrayList<>();
 
  96         // Convert the YAML document string into an object
 
  97         Object yamlObject = new Yaml().load(yamlEventString);
 
  99         // If the incoming YAML did not create a map it is a primitive type or a collection so we
 
 100         // convert it into a map for processing
 
 102         if (yamlObject instanceof Map) {
 
 103             // We already have a map so just cast the object
 
 104             yamlMap = (Map<?, ?>) yamlObject;
 
 106             // Create a single entry map, new map creation and assignment is to avoid a
 
 107             // type checking warning
 
 108             LinkedHashMap<String, Object> newYamlMap = new LinkedHashMap<>();
 
 109             newYamlMap.put(yamlPars.getYamlFieldName(), yamlObject);
 
 110             yamlMap = newYamlMap;
 
 114             eventList.add(yamlMap2ApexEvent(eventName, yamlMap));
 
 115         } catch (final Exception e) {
 
 116             final String errorString = "Failed to unmarshal YAML event: " + e.getMessage() + ", event="
 
 118             LOGGER.warn(errorString, e);
 
 119             throw new ApexEventException(errorString, e);
 
 122         // Return the list of events we have unmarshalled
 
 130     public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
 
 131         // Check the Apex event
 
 132         if (apexEvent == null) {
 
 133             LOGGER.warn("event processing failed, Apex event is null");
 
 134             throw new ApexEventException("event processing failed, Apex event is null");
 
 137         // Get the event definition for the event from the model service
 
 138         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
 
 139                         apexEvent.getVersion());
 
 141         // Create a map for output of the APEX event to YAML
 
 142         LinkedHashMap<String, Object> yamlMap = new LinkedHashMap<>();
 
 144         yamlMap.put(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
 
 145         yamlMap.put(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
 
 146         yamlMap.put(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
 
 147         yamlMap.put(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
 
 148         yamlMap.put(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
 
 150         if (apexEvent.getExceptionMessage() != null) {
 
 151             yamlMap.put(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
 
 154         for (final AxField eventField : eventDefinition.getFields()) {
 
 155             final String fieldName = eventField.getKey().getLocalName();
 
 157             if (!apexEvent.containsKey(fieldName)) {
 
 158                 if (!eventField.getOptional()) {
 
 159                     final String errorMessage = "error parsing " + eventDefinition.getId() + " event to Json. "
 
 160                                     + "Field \"" + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
 
 161                     LOGGER.debug(errorMessage);
 
 162                     throw new ApexEventRuntimeException(errorMessage);
 
 167             yamlMap.put(fieldName, apexEvent.get(fieldName));
 
 170         // Use Snake YAML to convert the APEX event to YAML
 
 171         Yaml yaml = new Yaml();
 
 172         return yaml.dumpAs(yamlMap, null, FlowStyle.BLOCK);
 
 176      * This method converts a YAML map into an Apex event.
 
 178      * @param eventName the name of the event
 
 179      * @param yamlMap the YAML map that holds the event
 
 180      * @return the apex event that we have converted the JSON object into
 
 181      * @throws ApexEventException thrown on unmarshaling exceptions
 
 183     private ApexEvent yamlMap2ApexEvent(final String eventName, final Map<?, ?> yamlMap) throws ApexEventException {
 
 184         // Process the mandatory Apex header
 
 185         final ApexEvent apexEvent = processApexEventHeader(eventName, yamlMap);
 
 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         // Iterate over the input fields in the event
 
 192         for (final AxField eventField : eventDefinition.getFields()) {
 
 193             final String fieldName = eventField.getKey().getLocalName();
 
 194             if (!yamlMap.containsKey(fieldName)) {
 
 195                 if (!eventField.getOptional()) {
 
 196                     final String errorMessage = "error parsing " + eventDefinition.getId() + " event from Json. "
 
 197                                     + "Field \"" + fieldName + "\" is missing, but is mandatory.";
 
 198                     LOGGER.debug(errorMessage);
 
 199                     throw new ApexEventException(errorMessage);
 
 204             final Object fieldValue = getYamlField(yamlMap, fieldName, null, !eventField.getOptional());
 
 206             if (fieldValue != null) {
 
 207                 // Get the schema helper
 
 208                 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
 
 209                                 eventField.getSchema());
 
 210                 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
 
 212                 apexEvent.put(fieldName, null);
 
 220      * This method processes the event header of an Apex event.
 
 222      * @param eventName the name of the event
 
 223      * @param yamlMap the YAML map that holds the event
 
 224      * @return an apex event constructed using the header fields of the event
 
 225      * @throws ApexEventRuntimeException the apex event runtime exception
 
 226      * @throws ApexEventException on invalid events with missing header fields
 
 228     private ApexEvent processApexEventHeader(final String eventName, final Map<?, ?> yamlMap)
 
 229                     throws ApexEventException {
 
 230         String name = getYamlStringField(yamlMap, ApexEvent.NAME_HEADER_FIELD, yamlPars.getNameAlias(),
 
 231                         ApexEvent.NAME_REGEXP, false);
 
 233         // Check that an event name has been specified
 
 234         if (name == null && eventName == null) {
 
 235             throw new ApexEventRuntimeException(
 
 236                             "event received without mandatory parameter \"name\" on configuration or on event");
 
 239         // Check if an event name was specified on the event parameters
 
 240         if (eventName != null) {
 
 241             if (name != null && !eventName.equals(name)) {
 
 242                 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\", "
 
 243                                 + "using configured event name", name, eventName);
 
 248         // Now, find the event definition in the model service. If version is null, the newest event
 
 249         // definition in the model service is used
 
 250         String version = getYamlStringField(yamlMap, ApexEvent.VERSION_HEADER_FIELD, yamlPars.getVersionAlias(),
 
 251                         ApexEvent.VERSION_REGEXP, false);
 
 252         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(name, version);
 
 253         if (eventDefinition == null) {
 
 254             throw new ApexEventRuntimeException("an event definition for an event named \"" + name
 
 255                             + "\" with version \"" + version + "\" not found in Apex model");
 
 258         // Use the defined event version if no version is specified on the incoming fields
 
 259         if (version == null) {
 
 260             version = eventDefinition.getKey().getVersion();
 
 263         String namespace = getEventHeaderNamespace(yamlMap, name, eventDefinition);
 
 265         String source = getEventHeaderSource(yamlMap, eventDefinition);
 
 267         String target = getHeaderTarget(yamlMap, eventDefinition);
 
 269         return new ApexEvent(name, version, namespace, source, target);
 
 273      * Get the event header name space.
 
 275      * @param yamlMap the YAML map to read from
 
 276      * @param eventDefinition the event definition
 
 277      * @return the event header name space
 
 279     private String getEventHeaderNamespace(final Map<?, ?> yamlMap, String name, final AxEvent eventDefinition) {
 
 280         // Check the name space is OK if it is defined, if not, use the name space from the model
 
 281         String namespace = getYamlStringField(yamlMap, ApexEvent.NAMESPACE_HEADER_FIELD, yamlPars.getNameSpaceAlias(),
 
 282                         ApexEvent.NAMESPACE_REGEXP, false);
 
 283         if (namespace != null) {
 
 284             if (!namespace.equals(eventDefinition.getNameSpace())) {
 
 285                 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
 
 286                                 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
 
 287                                 + "\" for that event in the Apex model");
 
 290             namespace = eventDefinition.getNameSpace();
 
 296      * Get the event header source.
 
 298      * @param yamlMap the YAML map to read from
 
 299      * @param eventDefinition the event definition
 
 300      * @return the event header source
 
 302     private String getEventHeaderSource(final Map<?, ?> yamlMap, final AxEvent eventDefinition) {
 
 303         // For source, use the defined source only if the source is not found on the incoming event
 
 304         String source = getYamlStringField(yamlMap, ApexEvent.SOURCE_HEADER_FIELD, yamlPars.getSourceAlias(),
 
 305                         ApexEvent.SOURCE_REGEXP, false);
 
 306         if (source == null) {
 
 307             source = eventDefinition.getSource();
 
 313      * Get the event header target.
 
 315      * @param yamlMap the YAML map to read from
 
 316      * @param eventDefinition the event definition
 
 317      * @return the event header target
 
 319     private String getHeaderTarget(final Map<?, ?> yamlMap, final AxEvent eventDefinition) {
 
 320         // For target, use the defined source only if the source is not found on the incoming event
 
 321         String target = getYamlStringField(yamlMap, ApexEvent.TARGET_HEADER_FIELD, yamlPars.getTargetAlias(),
 
 322                         ApexEvent.TARGET_REGEXP, false);
 
 323         if (target == null) {
 
 324             target = eventDefinition.getTarget();
 
 330      * This method gets an event string field from a JSON object.
 
 332      * @param yamlMap the YAML containing the YAML representation of the incoming event
 
 333      * @param fieldName the field name to find in the event
 
 334      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
 
 335      * @param fieldRegexp the regular expression to check the field against for validity
 
 336      * @param mandatory true if the field is mandatory
 
 337      * @return the value of the field in the JSON object or null if the field is optional
 
 338      * @throws ApexEventRuntimeException the apex event runtime exception
 
 340     private String getYamlStringField(final Map<?, ?> yamlMap, final String fieldName, final String fieldAlias,
 
 341                     final String fieldRegexp, final boolean mandatory) {
 
 342         // Get the YAML field for the string field
 
 343         final Object yamlField = getYamlField(yamlMap, fieldName, fieldAlias, mandatory);
 
 345         // Null strings are allowed
 
 346         if (yamlField == null) {
 
 350         if (!(yamlField instanceof String)) {
 
 351             // The element is not a string so throw an error
 
 352             throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
 
 353                             + yamlField.getClass().getName() + "\" is not a string value");
 
 356         final String fieldValueString = (String) yamlField;
 
 358         // Is regular expression checking required
 
 359         if (fieldRegexp == null) {
 
 360             return fieldValueString;
 
 363         // Check the event field against its regular expression
 
 364         if (!fieldValueString.matches(fieldRegexp)) {
 
 365             throw new ApexEventRuntimeException(
 
 366                             "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
 
 369         return fieldValueString;
 
 373      * This method gets an event field from a YAML object.
 
 375      * @param yamlMap the YAML containing the YAML representation of the incoming event
 
 376      * @param fieldName the field name to find in the event
 
 377      * @param fieldAlias the alias for the field to find in the event, overrides the field name if it is not null
 
 378      * @param mandatory true if the field is mandatory
 
 379      * @return the value of the field in the YAML object or null if the field is optional
 
 380      * @throws ApexEventRuntimeException the apex event runtime exception
 
 382     private Object getYamlField(final Map<?, ?> yamlMap, final String fieldName, final String fieldAlias,
 
 383                     final boolean mandatory) {
 
 385         // Check if we should use the alias for this field
 
 386         String fieldToFind = fieldName;
 
 387         if (fieldAlias != null) {
 
 388             fieldToFind = fieldAlias;
 
 391         // Get the event field
 
 392         final Object eventElement = yamlMap.get(fieldToFind);
 
 393         if (eventElement == null) {
 
 397                 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");