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