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