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