Fix sonar issues
[aai/data-router.git] / src / main / java / org / onap / aai / datarouter / policy / EntityEventPolicy.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017-2018 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21 package org.onap.aai.datarouter.policy;
22
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.security.NoSuchAlgorithmException;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34
35 import org.apache.camel.Exchange;
36 import org.apache.camel.Processor;
37 import org.eclipse.persistence.dynamic.DynamicType;
38 import org.eclipse.persistence.internal.helper.DatabaseField;
39 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
40 import org.eclipse.persistence.oxm.MediaType;
41 import org.json.JSONException;
42 import org.json.JSONObject;
43 import org.onap.aai.datarouter.entity.AaiEventEntity;
44 import org.onap.aai.datarouter.entity.AggregationEntity;
45 import org.onap.aai.datarouter.entity.DocumentStoreDataEntity;
46 import org.onap.aai.datarouter.entity.SuggestionSearchEntity;
47 import org.onap.aai.datarouter.entity.TopographicalEntity;
48 import org.onap.aai.datarouter.entity.UebEventHeader;
49 import org.onap.aai.datarouter.logging.EntityEventPolicyMsgs;
50 import org.onap.aai.datarouter.util.NodeUtils;
51 import org.onap.aai.datarouter.util.RouterServiceUtil;
52 import org.onap.aai.datarouter.util.SearchServiceAgent;
53 import org.onap.aai.datarouter.util.SearchSuggestionPermutation;
54 import org.onap.aai.entity.OxmEntityDescriptor;
55 import org.onap.aai.util.CrossEntityReference;
56 import org.onap.aai.util.EntityOxmReferenceHelper;
57 import org.onap.aai.util.ExternalOxmModelProcessor;
58 import org.onap.aai.schema.OxmModelLoader;
59 import org.onap.aai.util.Version;
60 import org.onap.aai.util.VersionedOxmEntities;
61 import org.onap.aai.cl.api.Logger;
62 import org.onap.aai.cl.eelf.LoggerFactory;
63 import org.onap.aai.cl.mdc.MdcContext;
64 import org.onap.aai.restclient.client.Headers;
65 import org.onap.aai.restclient.client.OperationResult;
66 import org.onap.aai.restclient.rest.HttpUtil;
67 import org.slf4j.MDC;
68
69 import com.fasterxml.jackson.core.JsonProcessingException;
70 import com.fasterxml.jackson.databind.JsonNode;
71 import com.fasterxml.jackson.databind.ObjectMapper;
72 import com.fasterxml.jackson.databind.ObjectWriter;
73 import com.fasterxml.jackson.databind.node.ObjectNode;
74
75 public class EntityEventPolicy implements Processor {
76
77   public static final String additionalInfo = "Response of AAIEntityEventPolicy";
78   private static final String ENTITY_SEARCH_SCHEMA = "entitysearch_schema.json";
79   private static final String TOPOGRAPHICAL_SEARCH_SCHEMA = "topographysearch_schema.json";
80   private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
81
82   private static final String EVENT_HEADER = "event-header";
83   private static final String ENTITY_HEADER = "entity";
84   private static final String ACTION_CREATE = "create";
85   private static final String ACTION_DELETE = "delete";
86   private static final String ACTION_UPDATE = "update";
87   private static final String PROCESS_AAI_EVENT = "Process AAI Event";
88   private static final String TOPO_LAT = "latitude";
89   private static final String TOPO_LONG = "longitude";
90
91   private static final List<String> SUPPORTED_ACTIONS =
92       Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
93
94   Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
95   private String oxmVersion = null;
96
97   /** Agent for communicating with the Search Service. */
98   private SearchServiceAgent searchAgent = null;
99
100   /** Search index name for storing AAI event entities. */
101   private String entitySearchIndex;
102
103   /** Search index name for storing topographical search data. */
104   private String topographicalSearchIndex;
105
106   /** Search index name for suggestive search data. */
107   private String aggregateGenericVnfIndex;
108
109   private String autosuggestIndex;
110
111   private String srcDomain;
112
113   private Logger logger;
114   private Logger metricsLogger;
115
116   public enum ResponseType {
117     SUCCESS, PARTIAL_SUCCESS, FAILURE;
118   };
119
120   public EntityEventPolicy(EntityEventPolicyConfig config) throws FileNotFoundException {
121     LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
122     logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
123     metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
124
125
126     srcDomain = config.getSourceDomain();
127
128     // Populate the index names.
129     entitySearchIndex        = config.getSearchEntitySearchIndex();
130     topographicalSearchIndex = config.getSearchTopographySearchIndex();
131     aggregateGenericVnfIndex = config.getSearchAggregationVnfIndex();
132     autosuggestIndex             = config.getSearchEntityAutoSuggestIndex();
133
134     // Instantiate the agent that we will use for interacting with the Search Service.
135     searchAgent = new SearchServiceAgent(config.getSearchCertName(),
136                                          config.getSearchKeystore(),
137                                          config.getSearchKeystorePwd(),
138                                          EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
139                                                                         config.getSearchEndpoint()),
140                                          config.getSearchEndpointDocuments(),
141                                          logger);
142
143     this.externalOxmModelProcessors = new ArrayList<>();
144     this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
145     OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
146     OxmModelLoader.loadModels();
147     oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
148     parseLatestOxmVersion();
149   }
150
151   private void parseLatestOxmVersion() {
152     int latestVersion = -1;
153     if (oxmVersionContextMap != null) {
154       Iterator it = oxmVersionContextMap.entrySet().iterator();
155       while (it.hasNext()) {
156         Map.Entry pair = (Map.Entry) it.next();
157
158         String version = pair.getKey().toString();
159         int versionNum = Integer.parseInt(version.substring(1, version.length()));
160
161         if (versionNum > latestVersion) {
162           latestVersion = versionNum;
163           oxmVersion = pair.getKey().toString();
164         }
165
166         logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
167       }
168     } else {
169       logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
170     }
171   }
172
173   public void startup() {
174
175     // Create the indexes in the search service if they do not already exist.
176     searchAgent.createSearchIndex(entitySearchIndex, ENTITY_SEARCH_SCHEMA);
177     searchAgent.createSearchIndex(topographicalSearchIndex, TOPOGRAPHICAL_SEARCH_SCHEMA);
178
179     logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
180   }
181
182
183   /**
184    * Convert object to json.
185    *
186    * @param object the object
187    * @param pretty the pretty
188    * @return the string
189    * @throws JsonProcessingException the json processing exception
190    */
191   public static String convertObjectToJson(Object object, boolean pretty)
192       throws JsonProcessingException {
193     ObjectWriter ow;
194
195     if (pretty) {
196       ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
197
198     } else {
199       ow = new ObjectMapper().writer();
200     }
201
202     return ow.writeValueAsString(object);
203   }
204
205   public void returnWithError(Exchange exchange, String payload, String errorMsg){
206     logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE, errorMsg);
207     logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, errorMsg, payload);
208     setResponse(exchange, ResponseType.FAILURE, additionalInfo);
209   }
210
211   @Override
212   public void process(Exchange exchange) throws Exception {
213
214     long startTime = System.currentTimeMillis();
215
216     String uebPayload = exchange.getIn().getBody().toString();
217
218     JsonNode uebAsJson =null;
219     ObjectMapper mapper = new ObjectMapper();
220     try{
221       uebAsJson = mapper.readTree(uebPayload);
222     } catch (IOException e){
223       returnWithError(exchange, uebPayload, "Invalid Payload");
224       return;
225     }
226
227     // Load the UEB payload data, any errors will result in a failure and discard
228     JSONObject uebObjHeader = getUebContentAsJson(uebPayload, EVENT_HEADER);
229     if (uebObjHeader == null) {
230       returnWithError(exchange, uebPayload, "Payload is missing " + EVENT_HEADER);
231       return;
232     }
233
234     JSONObject uebObjEntity = getUebContentAsJson(uebPayload, ENTITY_HEADER);
235     if (uebObjEntity == null) {
236       returnWithError(exchange, uebPayload, "Payload is missing " + ENTITY_HEADER);
237       return;
238     }
239
240     UebEventHeader eventHeader;
241     eventHeader = initializeUebEventHeader(uebObjHeader.toString());
242
243     // Get src domain from header; discard event if not originated from same domain
244     String payloadSrcDomain = eventHeader.getDomain();
245     if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
246       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
247           "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
248       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
249           "Unrecognized source domain '" + payloadSrcDomain + "'");
250
251       setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
252       return;
253     }
254
255     DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion);
256     if (oxmJaxbContext == null) {
257       logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
258       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "OXM version mismatch",
259           uebPayload);
260
261       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
262       return;
263     }
264
265     String action = eventHeader.getAction();
266     if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
267       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
268           "Unrecognized action '" + action + "'", uebPayload);
269       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
270           "Unrecognized action '" + action + "'");
271
272       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
273       return;
274     }
275
276     String entityType = eventHeader.getEntityType();
277     if (entityType == null) {
278       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
279           "Payload header missing entity type", uebPayload);
280       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
281           "Payload header missing entity type");
282
283       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
284       return;
285     }
286
287     String topEntityType = eventHeader.getTopEntityType();
288     if (topEntityType == null) {
289       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
290           "Payload header missing top entity type", uebPayload);
291       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
292           "Payload header top missing entity type");
293
294       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
295       return;
296     }
297
298     String entityLink = eventHeader.getEntityLink();
299     if (entityLink == null) {
300       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
301           "Payload header missing entity link", uebPayload);
302       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
303           "Payload header missing entity link");
304
305       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
306       return;
307     }
308
309     // log the fact that all data are in good shape
310     logger.info(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_NONVERBOSE, action,
311         entityType);
312     logger.debug(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
313         uebPayload);
314
315
316     // Process for building AaiEventEntity object
317     String oxmEntityType = new OxmEntityTypeConverter().convert(entityType);
318
319     List<String> searchableAttr =
320         getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
321     if (searchableAttr == null) {
322       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
323           "Searchable attribute not found for payload entity type '" + entityType + "'");
324       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
325           "Searchable attribute not found for payload entity type '" + entityType + "'",
326           uebPayload);
327
328       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
329       return;
330     }
331
332     String entityPrimaryKeyFieldName =
333         getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
334     String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
335     if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
336       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
337           "Payload missing primary key attribute");
338       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
339           "Payload missing primary key attribute", uebPayload);
340
341       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
342       return;
343     }
344
345     AaiEventEntity aaiEventEntity = new AaiEventEntity();
346
347     /*
348      * Use the OXM Model to determine the primary key field name based on the entity-type
349      */
350
351     aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
352     aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
353     aaiEventEntity.setEntityType(entityType);
354     aaiEventEntity.setLink(entityLink);
355
356     if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
357       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
358           "Payload missing searchable attribute for entity type '" + entityType + "'");
359       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
360           "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
361
362       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
363       return;
364
365     }
366
367     try {
368       aaiEventEntity.deriveFields();
369
370     } catch (NoSuchAlgorithmException e) {
371       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
372           "Cannot create unique SHA digest");
373       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
374           "Cannot create unique SHA digest", uebPayload);
375
376       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
377       return;
378     }
379
380     handleSearchServiceOperation(aaiEventEntity, action, entitySearchIndex);
381
382     handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
383         entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
384
385     /*
386      * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
387      */
388     VersionedOxmEntities oxmEntities =
389         EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion.toLowerCase()));
390
391     /**
392      * NOTES:
393      * 1. If the entity type is "customer", the below check will return true if any nested entityType
394      * in that model could contain a CER based on the OXM model version that has been loaded.
395      * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
396      * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
397      * assumption that AAI will push down all required cascade-deletes for nested entities as well
398      * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
399      *  CREATE service-instance.
400      */
401
402     if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
403         && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
404
405       // We know the model "can" contain a CER reference definition, let's process a bit more
406
407       HashMap<String, CrossEntityReference> crossEntityRefMap =
408           oxmEntities.getCrossEntityReferences();
409
410       JSONObject entityJsonObject = getUebEntity(uebPayload);
411
412       if (entityJsonObject != null) {
413           JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
414
415           String parentEntityType = entityType;
416
417           String targetEntityUrl = entityLink;
418
419           for (Map.Entry<String, CrossEntityReference> entry : crossEntityRefMap.entrySet()) {
420
421               /*
422                * if we know service-subscription is in the tree, then we can pull our all instances and process
423                * from there.
424                */
425
426               String key = entry.getKey();
427               CrossEntityReference cerDescriptor = entry.getValue();
428
429               ArrayList<JsonNode> foundNodes = new ArrayList<>();
430
431               RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
432
433               if (!foundNodes.isEmpty()) {
434
435                   for (JsonNode n : foundNodes) {
436                       if ("customer".equalsIgnoreCase(parentEntityType)) {
437                           /*
438                            * NOTES: 1. prepare to hand-create url for service-instance 2. this will break if the
439                            * URL structure for service-instance changes
440                            */
441                           if (n.has("service-type")) {
442                               targetEntityUrl += "/service-subscriptions/service-subscription/"
443                                       + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
444                                       + "/service-instances/service-instance/";
445                           }
446                       }
447
448                       List<String> extractedParentEntityAttributeValues = new ArrayList<>();
449
450                       RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
451                               extractedParentEntityAttributeValues);
452
453                       List<JsonNode> nestedTargetEntityInstances = new ArrayList<>();
454                       RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
455                                     nestedTargetEntityInstances);
456
457                       for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
458                           /*
459                            * Now: 1. build the AAIEntityType (IndexDocument) based on the extract entity 2. Get
460                            * data from ES 3. Extract ETAG 4. Merge ES Doc + AAIEntityType + Extracted Parent
461                            * Cross-Entity-Reference Values 5. Put data into ES with ETAG + updated doc
462                            */
463
464                           // Get the complete URL for target entity
465                           if (targetEntityInstance.has("link")) { // nested SI has url mentioned
466                               targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance, "link");
467                           } else if ("customer".equalsIgnoreCase(parentEntityType) && targetEntityInstance.has("service-instance-id")) {
468                                     targetEntityUrl += RouterServiceUtil.getNodeFieldAsText(targetEntityInstance, "service-instance-id");
469                           }
470
471                           OxmEntityDescriptor searchableDescriptor = oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
472
473                           if (searchableDescriptor != null) {
474
475                               if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
476
477                                   AaiEventEntity entityToSync = null;
478
479                                   try {
480
481                                       entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
482
483                                       /*
484                                        * Ready to do some ElasticSearch ops
485                                        */
486
487                                       for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
488                                           entityToSync.addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
489                                       }
490
491                                       entityToSync.setLink(targetEntityUrl);
492                                       entityToSync.deriveFields();
493
494                                       updateCerInEntity(entityToSync);
495
496                                   } catch (NoSuchAlgorithmException e) {
497                                       logger.debug(e.getMessage());
498                                   }
499                               }
500                           } else {
501                               logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
502                                       "failure to find searchable descriptor for type "
503                                               + cerDescriptor.getTargetEntityType());
504                           }
505                       }
506
507                   }
508
509               } else {
510                   logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
511                           "failed to find 0 instances of cross-entity-reference with entity " + key);
512               }
513
514           }
515       } else {
516           logger.info(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, "Unable to get UEB object");
517       }
518
519     } else {
520         logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
521                 + topEntityType + " does not contain a cross-entity-reference entity");
522     }
523
524     /*
525      * Process for autosuggestable entities
526      */
527     if (oxmEntities != null) {
528       Map<String, OxmEntityDescriptor> rootDescriptor =
529           oxmEntities.getSuggestableEntityDescriptors();
530       if (!rootDescriptor.isEmpty()) {
531         List<String> suggestibleAttrInPayload = new ArrayList<>();
532         List<String> suggestibleAttrInOxm = extractSuggestableAttr(oxmEntities, entityType);
533         if (suggestibleAttrInOxm != null) {
534           for (String attr: suggestibleAttrInOxm){
535             if ( uebObjEntity.has(attr) ){
536               suggestibleAttrInPayload.add(attr);
537             }
538           }
539         }
540
541         if (suggestibleAttrInPayload.isEmpty()) {
542           return;
543         }
544
545         List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
546         AggregationEntity ae = new AggregationEntity();
547         ae.setLink(entityLink);
548         ae.deriveFields(uebAsJson);
549
550         handleSearchServiceOperation(ae, action, aggregateGenericVnfIndex);
551
552         /*
553          * It was decided to silently ignore DELETE requests for resources we don't allow to be
554          * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
555          */
556         if (!ACTION_DELETE.equalsIgnoreCase(action)) {
557           List<ArrayList<String>> listOfValidPowerSetElements =
558               SearchSuggestionPermutation.getNonEmptyUniqueLists(suggestibleAttrInPayload);
559
560           // Now we have a list containing the power-set (minus empty element) for the status that are
561           // available in the payload. Try inserting a document for every combination.
562           for (ArrayList<String> list : listOfValidPowerSetElements) {
563             SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
564             suggestionSearchEntity.setEntityType(entityType);
565             suggestionSearchEntity.setSuggestableAttr(list);
566             suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
567             suggestionSearchEntity.setFilterBasedPayloadFromResponse(uebAsJson.get("entity"),
568                 suggestibleAttrInOxm, list);
569             suggestionSearchEntity.setSuggestionInputPermutations(
570                 suggestionSearchEntity.generateSuggestionInputPermutations());
571
572             if (suggestionSearchEntity.isSuggestableDoc()) {
573               try {
574                 suggestionSearchEntity.generateSearchSuggestionDisplayStringAndId();
575               } catch (NoSuchAlgorithmException e) {
576                 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
577                     "Cannot create unique SHA digest for search suggestion data. Exception: "
578                         + e.getLocalizedMessage());
579               }
580
581               handleSearchServiceOperation(suggestionSearchEntity, action, autosuggestIndex);
582             }
583           }
584         }
585       }
586     }
587
588     long stopTime = System.currentTimeMillis();
589
590     metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
591         String.valueOf(stopTime - startTime));
592
593     setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
594     return;
595   }
596
597   public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
598     // Extract suggestable attributeshandleTopographicalData
599     Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
600
601     if (rootDescriptor == null) {
602       return Collections.emptyList();
603     }
604
605     OxmEntityDescriptor desc = rootDescriptor.get(entityType);
606
607     if (desc == null) {
608       return Collections.emptyList();
609     }
610
611     return desc.getSuggestableAttributes();
612   }
613
614   public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
615       String entityType) {
616
617     // Extract alias
618     Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
619
620     if (rootDescriptor == null) {
621       return Collections.emptyList();
622     }
623
624     OxmEntityDescriptor desc = rootDescriptor.get(entityType);
625     return desc.getAlias();
626   }
627
628   private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
629
630     exchange.getOut().setHeader("ResponseType", responseType.toString());
631     exchange.getOut().setBody(additionalInfo);
632   }
633
634   public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
635       List<String> suggestableAttr, List<String> alias) {
636
637     // Extract suggestable attributes
638     Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
639
640     OxmEntityDescriptor desc = rootDescriptor.get(entityType);
641     suggestableAttr = desc.getSuggestableAttributes();
642
643     // Extract alias
644     rootDescriptor = oxmEntities.getEntityAliasDescriptors();
645     desc = rootDescriptor.get(entityType);
646     alias = desc.getAlias();
647   }
648
649   /*
650    * Load the UEB JSON payload, any errors would result to a failure case response.
651    */
652   private JSONObject getUebContentAsJson(String payload, String contentKey) {
653
654     JSONObject uebJsonObj;
655     JSONObject uebObjContent;
656
657     try {
658       uebJsonObj = new JSONObject(payload);
659     } catch (JSONException e) {
660       logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
661       logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
662       return null;
663     }
664
665     if (uebJsonObj.has(contentKey)) {
666       uebObjContent = uebJsonObj.getJSONObject(contentKey);
667     } else {
668       logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
669       logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
670       return null;
671     }
672
673     return uebObjContent;
674   }
675
676
677   private UebEventHeader initializeUebEventHeader(String payload) {
678
679     UebEventHeader eventHeader = null;
680     ObjectMapper mapper = new ObjectMapper();
681
682     // Make sure that were were actually passed in a valid string.
683     if (payload == null || payload.isEmpty()) {
684       logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
685       logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
686
687       return eventHeader;
688     }
689
690     // Marshal the supplied string into a UebEventHeader object.
691     try {
692       eventHeader = mapper.readValue(payload, UebEventHeader.class);
693     } catch (JsonProcessingException e) {
694       logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
695     } catch (Exception e) {
696       logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
697     }
698
699     if (eventHeader != null) {
700       logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
701     }
702
703     return eventHeader;
704
705   }
706
707
708   private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
709       String oxmEntityType, String entityType) {
710
711     DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
712     if (entity == null) {
713       return null;
714     }
715
716     List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
717     if (list != null && !list.isEmpty()) {
718       String keyName = list.get(0).getName();
719       return keyName.substring(0, keyName.indexOf('/'));
720     }
721
722     return "";
723   }
724
725   private String lookupValueUsingKey(String payload, String key) throws JSONException {
726     JsonNode jsonNode = convertToJsonNode(payload);
727     return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
728   }
729
730   private JsonNode convertToJsonNode(String payload) {
731
732     ObjectMapper mapper = new ObjectMapper();
733     JsonNode jsonNode = null;
734     try {
735       jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
736     } catch (IOException e) {
737       logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
738           payload);
739       logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
740           "");
741     }
742
743     return jsonNode;
744   }
745
746   private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
747       String payload, String action) {
748
749     boolean hasSearchableAttr = false;
750     for (String searchTagField : searchableAttr) {
751       String searchTagValue;
752       if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
753         searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
754       } else {
755         searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
756       }
757
758       if (searchTagValue != null && !searchTagValue.isEmpty()) {
759         hasSearchableAttr = true;
760         aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
761       }
762     }
763     return hasSearchableAttr;
764   }
765
766   /*
767    * Check if OXM version is available. If available, load it.
768    */
769   private DynamicJAXBContext loadOxmContext(String version) {
770     if (version == null) {
771       logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
772       return null;
773     }
774
775     return oxmVersionContextMap.get(version);
776   }
777
778   private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
779       String oxmEntityType, String entityType, String fieldName) {
780
781     DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
782     if (entity == null) {
783       return null;
784     }
785
786     /*
787      * Check for searchable XML tag
788      */
789     List<String> fieldValues = null;
790     Map<String, String> properties = entity.getDescriptor().getProperties();
791     for (Map.Entry<String, String> entry : properties.entrySet()) {
792       if (entry.getKey().equalsIgnoreCase(fieldName)) {
793         fieldValues = Arrays.asList(entry.getValue().split(","));
794         break;
795       }
796     }
797
798     return fieldValues;
799   }
800
801   private JSONObject getUebEntity(String payload) {
802     JSONObject uebJsonObj;
803
804     try {
805       uebJsonObj = new JSONObject(payload);
806     } catch (JSONException e) {
807       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
808           "Payload has invalid JSON Format", payload);
809       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
810           "Payload has invalid JSON Format");
811       return null;
812     }
813
814     if (uebJsonObj.has(ENTITY_HEADER)) {
815       return uebJsonObj.getJSONObject(ENTITY_HEADER);
816     } else {
817       logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing", payload);
818       logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
819       return null;
820     }
821   }
822
823   protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
824       OxmEntityDescriptor resultDescriptor) {
825     AaiEventEntity d = new AaiEventEntity();
826
827     d.setEntityType(resultDescriptor.getEntityName());
828
829     List<String> primaryKeyValues = new ArrayList<>();
830     List<String> primaryKeyNames = new ArrayList<>();
831     String pkeyValue;
832
833     for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
834       pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
835       if (pkeyValue != null) {
836         primaryKeyValues.add(pkeyValue);
837         primaryKeyNames.add(keyName);
838       } else {
839         logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
840             resultDescriptor.getEntityName());
841       }
842     }
843
844     final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
845     d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
846     final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
847     d.setEntityPrimaryKeyName(primaryCompositeKeyName);
848
849     final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
850
851     /*
852      * Based on configuration, use the configured field names for this entity-Type to build a
853      * multi-value collection of search tags for elastic search entity search criteria.
854      */
855
856
857     for (String searchTagField : searchTagFields) {
858       String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
859       if (searchTagValue != null && !searchTagValue.isEmpty()) {
860         d.addSearchTagWithKey(searchTagValue, searchTagField);
861       }
862     }
863
864     return d;
865   }
866
867   private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
868     try {
869       Map<String, List<String>> headers = new HashMap<>();
870       headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
871       headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
872
873       String entityId = aaiEventEntity.getId();
874       String jsonPayload;
875
876       // Run the GET to retrieve the ETAG from the search service
877       OperationResult storedEntity = searchAgent.getDocument(entitySearchIndex, entityId);
878
879       if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
880         /*
881          * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
882          * the pre-existing object,
883          * so all we want to do is update the CER on the pre-existing object (if needed).
884          */
885
886         List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
887
888         if (etag != null && !etag.isEmpty()) {
889           headers.put(Headers.IF_MATCH, etag);
890         } else {
891           logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
892                   entitySearchIndex, entityId);
893         }
894
895         ArrayList<JsonNode> sourceObject = new ArrayList<>();
896         NodeUtils.extractObjectsByKey(
897             NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
898             "content", sourceObject);
899
900         if (!sourceObject.isEmpty()) {
901           JsonNode node = sourceObject.get(0);
902           final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
903               "crossEntityReferenceValues");
904           String newCer = aaiEventEntity.getCrossReferenceEntityValues();
905           boolean hasNewCer = true;
906           if (sourceCer != null && sourceCer.length() > 0){ // already has CER
907             if ( !sourceCer.contains(newCer)){//don't re-add
908               newCer = sourceCer + ";" + newCer;
909             } else {
910               hasNewCer = false;
911             }
912           }
913
914           if (hasNewCer){
915             // Do the PUT with new CER
916             ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
917             jsonPayload = NodeUtils.convertObjectToJson(node, false);
918             searchAgent.putDocument(entitySearchIndex, entityId, jsonPayload, headers);
919           }
920          }
921       } else {
922
923         if (storedEntity.getResultCode() == 404) {
924           // entity not found, so attempt to do a PUT
925           searchAgent.putDocument(entitySearchIndex, entityId, aaiEventEntity.getAsJson(), headers);
926         } else {
927           logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
928               aaiEventEntity.getId(), "SYNC_ENTITY");
929         }
930       }
931     } catch (IOException e) {
932       logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
933           aaiEventEntity.getId(), "SYNC_ENTITY");
934     }
935   }
936
937   /**
938    * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
939    * index
940    *
941    * @param eventEntity Entity/data to use in operation
942    * @param action The operation to perform
943    * @param target Resource to perform the operation on
944    * @param allowDeleteEvent Allow delete operation to be performed on resource
945    */
946   protected void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity,
947                                             String                  action,
948                                             String                  index) {
949     try {
950
951       Map<String, List<String>> headers = new HashMap<>();
952       headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
953       headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
954
955       String entityId = eventEntity.getId();
956
957       if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
958           || action.equalsIgnoreCase(ACTION_UPDATE)) {
959
960         // Run the GET to retrieve the ETAG from the search service
961         OperationResult storedEntity = searchAgent.getDocument(index, entityId);
962
963         if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
964           List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
965
966           if (etag != null && !etag.isEmpty()) {
967             headers.put(Headers.IF_MATCH, etag);
968           } else {
969             logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
970                 entityId);
971           }
972         }
973
974         // Write the entity to the search service.
975         // PUT
976         searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
977       } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
978         // Write the entry to the search service.
979         searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
980
981       } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
982         // Run the GET to retrieve the ETAG from the search service
983         OperationResult storedEntity = searchAgent.getDocument(index, entityId);
984
985         if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
986           List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
987
988           if (etag != null && !etag.isEmpty()) {
989             headers.put(Headers.IF_MATCH, etag);
990           } else {
991             logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
992                 entityId);
993           }
994
995           /*
996            * The Spring-Boot version of the search-data-service rejects the DELETE operation unless
997            * we specify a Content-Type.
998            */
999
1000           headers.put("Content-Type", Arrays.asList(MediaType.APPLICATION_JSON.getMediaType()));
1001
1002           searchAgent.deleteDocument(index, eventEntity.getId(), headers);
1003         } else {
1004           logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1005               entityId);
1006         }
1007       } else {
1008         logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1009       }
1010     } catch (IOException e) {
1011       logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1012           action);
1013     }
1014   }
1015
1016   private void handleTopographicalData(String payload, String action, String entityType,
1017       String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1018       String entityPrimaryKeyFieldValue) {
1019
1020     Map<String, String> topoData = new HashMap<>();
1021     String entityLink;
1022     List<String> topographicalAttr =
1023         getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1024     if (topographicalAttr == null) {
1025       logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1026           "Topograhical attribute not found for payload entity type '" + entityType + "'");
1027       logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1028           "Topograhical attribute not found for payload entity type '" + entityType + "'",
1029           payload);
1030     } else {
1031       entityLink = lookupValueUsingKey(payload, "entity-link");
1032       for (String topoAttr : topographicalAttr) {
1033         topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1034       }
1035       updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1036           entityPrimaryKeyFieldValue, entityLink);
1037     }
1038
1039   }
1040
1041   private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1042       String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1043
1044     TopographicalEntity topoEntity = new TopographicalEntity();
1045     topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1046     topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1047     topoEntity.setEntityType(entityType);
1048     topoEntity.setLatitude(topoData.get(TOPO_LAT));
1049     topoEntity.setLongitude(topoData.get(TOPO_LONG));
1050     topoEntity.setSelfLink(entityLink);
1051     try {
1052       topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1053           entityPrimaryKeyValue));
1054     } catch (NoSuchAlgorithmException e) {
1055       logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1056           "Cannot create unique SHA digest for topographical data.");
1057     }
1058
1059     this.handleSearchServiceOperation(topoEntity, action, topographicalSearchIndex);
1060   }
1061
1062
1063   // put this here until we find a better spot
1064   /**
1065    * Helper utility to concatenate substrings of a URI together to form a proper URI.
1066    *
1067    * @param suburis the list of substrings to concatenate together
1068    * @return the concatenated list of substrings
1069    */
1070   public static String concatSubUri(String... suburis) {
1071     String finalUri = "";
1072
1073     for (String suburi : suburis) {
1074
1075       if (suburi != null) {
1076         // Remove any leading / since we only want to append /
1077         suburi = suburi.replaceFirst("^/*", "");
1078
1079         // Add a trailing / if one isn't already there
1080         finalUri += suburi.endsWith("/") ? suburi : suburi + "/";
1081       }
1082     }
1083
1084     return finalUri;
1085   }
1086 }