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