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