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