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