cef0b70e90876f27838124ffba8571565c56b745
[aai/data-router.git] / src / main / java / org / onap / aai / datarouter / policy / SpikeEntityEventPolicy.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.onap.aai.datarouter.policy;
24
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.security.NoSuchAlgorithmException;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35
36 import org.apache.camel.Exchange;
37 import org.apache.camel.Processor;
38 import org.eclipse.persistence.dynamic.DynamicType;
39 import org.eclipse.persistence.internal.helper.DatabaseField;
40 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
41 import org.json.JSONException;
42 import org.json.JSONObject;
43 import org.onap.aai.cl.api.Logger;
44 import org.onap.aai.cl.eelf.LoggerFactory;
45 import org.onap.aai.cl.mdc.MdcContext;
46 import org.onap.aai.datarouter.entity.SpikeEventEntity;
47 import org.onap.aai.datarouter.entity.DocumentStoreDataEntity;
48 import org.onap.aai.datarouter.entity.SpikeEventVertex;
49 import org.onap.aai.datarouter.entity.OxmEntityDescriptor;
50 import org.onap.aai.datarouter.logging.EntityEventPolicyMsgs;
51 import org.onap.aai.datarouter.util.EntityOxmReferenceHelper;
52 import org.onap.aai.datarouter.util.ExternalOxmModelProcessor;
53 import org.onap.aai.datarouter.util.OxmModelLoader;
54 import org.onap.aai.datarouter.util.RouterServiceUtil;
55 import org.onap.aai.datarouter.util.SearchServiceAgent;
56 import org.onap.aai.restclient.client.Headers;
57 import org.onap.aai.restclient.client.OperationResult;
58 import org.onap.aai.restclient.rest.HttpUtil;
59 import org.slf4j.MDC;
60
61 import com.fasterxml.jackson.core.JsonProcessingException;
62 import com.fasterxml.jackson.databind.JsonNode;
63 import com.fasterxml.jackson.databind.ObjectMapper;
64 import com.fasterxml.jackson.databind.ObjectWriter;
65
66 public class SpikeEntityEventPolicy implements Processor {
67
68   public static final String additionalInfo = "Response of SpikeEntityEventPolicy";
69   private static final String entitySearchSchema = "entitysearch_schema.json";
70
71   private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
72
73
74   private final String ACTION_CREATE = "create";
75   private final String EVENT_VERTEX = "vertex";
76   private final static String ACTION_DELETE = "delete";
77   private final String ACTION_UPDATE = "update";
78   private final String PROCESS_SPIKE_EVENT = "Process Spike Event";
79   private final String OPERATION_KEY = "operation";
80
81
82   private final List<String> SUPPORTED_ACTIONS =
83       Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
84
85   Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
86   private String oxmVersion = null;
87
88   /** Agent for communicating with the Search Service. */
89   private SearchServiceAgent searchAgent = null;
90   private String entitySearchIndex;
91   private String srcDomain;
92
93   private Logger logger;
94   private Logger metricsLogger;
95
96   public enum ResponseType {
97     SUCCESS, PARTIAL_SUCCESS, FAILURE;
98   };
99
100   public SpikeEntityEventPolicy(SpikeEntityEventPolicyConfig config) throws FileNotFoundException {
101     LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
102     logger = loggerFactoryInstance.getLogger(SpikeEntityEventPolicy.class.getName());
103     metricsLogger = loggerFactoryInstance.getMetricsLogger(SpikeEntityEventPolicy.class.getName());
104
105
106     srcDomain = config.getSourceDomain();
107
108     // Populate the index names.
109     entitySearchIndex = config.getSearchEntitySearchIndex();
110
111     // Instantiate the agent that we will use for interacting with the Search Service.
112     searchAgent = new SearchServiceAgent(config.getSearchCertName(), config.getSearchKeystore(),
113         config.getSearchKeystorePwd(),
114         EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint()),
115         config.getSearchEndpointDocuments(), logger);
116
117     this.externalOxmModelProcessors = new ArrayList<>();
118     this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
119     OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
120     OxmModelLoader.loadModels();
121     oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
122     parseLatestOxmVersion();
123   }
124
125   private void parseLatestOxmVersion() {
126     int latestVersion = -1;
127     if (oxmVersionContextMap != null) {
128       Iterator it = oxmVersionContextMap.entrySet().iterator();
129       while (it.hasNext()) {
130         Map.Entry pair = (Map.Entry) it.next();
131
132         String version = pair.getKey().toString();
133         int versionNum = Integer.parseInt(version.substring(1, version.length()));
134
135         if (versionNum > latestVersion) {
136           latestVersion = versionNum;
137           oxmVersion = pair.getKey().toString();
138         }
139
140         logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
141       }
142     } else {
143       logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
144     }
145   }
146
147   public void startup() {
148
149     // Create the indexes in the search service if they do not already exist.
150     searchAgent.createSearchIndex(entitySearchIndex, entitySearchSchema);
151     logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
152   }
153
154
155   /**
156    * Convert object to json.
157    *
158    * @param object the object
159    * @param pretty the pretty
160    * @return the string
161    * @throws JsonProcessingException the json processing exception
162    */
163   public static String convertObjectToJson(Object object, boolean pretty)
164       throws JsonProcessingException {
165     ObjectWriter ow;
166
167     if (pretty) {
168       ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
169
170     } else {
171       ow = new ObjectMapper().writer();
172     }
173
174     return ow.writeValueAsString(object);
175   }
176
177   public void returnWithError(Exchange exchange, String payload, String errorMsg) {
178     logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE, errorMsg);
179     logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, errorMsg, payload);
180     setResponse(exchange, ResponseType.FAILURE, additionalInfo);
181   }
182
183   public boolean isJSONValid(String test) {
184     try {
185       new JSONObject(test);
186     } catch (JSONException ex) {
187       return false;
188     }
189     return true;
190   }
191
192   @Override
193   public void process(Exchange exchange) throws Exception {
194
195     long startTime = System.currentTimeMillis();
196     String uebPayload = exchange.getIn().getBody().toString();
197     if (uebPayload == null || !isJSONValid(uebPayload)) {
198       uebPayload = exchange.getIn().getBody(String.class);
199       if (uebPayload == null || !isJSONValid(uebPayload)) {
200         returnWithError(exchange, uebPayload, "Invalid Payload");
201         return;
202       }
203     }
204
205     
206     JSONObject mainJson = new JSONObject(uebPayload);
207     String action = mainJson.getString(OPERATION_KEY);
208     if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
209       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
210           "Unrecognized action '" + action + "'", uebPayload);
211       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
212           "Unrecognized action '" + action + "'");
213       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
214       return;
215     }
216
217     // Load the UEB payload data, any errors will result in a failure and discard
218
219     JSONObject spikeObjVertex = getUebContentAsJson(uebPayload, EVENT_VERTEX);
220     if (spikeObjVertex == null) {
221       returnWithError(exchange, uebPayload, "Payload is missing " + EVENT_VERTEX);
222       return;
223     }
224
225     SpikeEventVertex eventVertex = initializeSpikeEventVertex(spikeObjVertex.toString());
226
227     DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
228     if (oxmJaxbContext == null) {
229       logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
230       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "OXM version mismatch", uebPayload);
231
232       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
233       return;
234     }
235
236
237
238     String entityType = eventVertex.getType();
239     if (entityType == null || entityType.isEmpty()) {
240       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
241           "Payload header missing entity type", uebPayload);
242       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
243           "Payload header missing entity type");
244
245       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
246       return;
247     }
248
249     String entityKey = eventVertex.getKey();
250     if (entityKey == null || entityKey.isEmpty()) {
251       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Payload vertex missing entity key",
252           uebPayload);
253       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
254           "Payload vertex missing entity key");
255
256       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
257       return;
258     }
259     String entityLink = eventVertex.getEntityLink();
260     if (entityLink == null || entityLink.isEmpty()) {
261       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
262           "Payload header missing entity link", uebPayload);
263       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
264           "Payload header missing entity link");
265
266       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
267       return;
268     }
269
270     // log the fact that all data are in good shape
271     logger.info(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_NONVERBOSE, action, entityType);
272     logger.debug(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
273         uebPayload);
274
275
276     // Process for building SpikeEventEntity object
277     String[] entityTypeArr = entityType.split("-");
278     String oxmEntityType = "";
279     for (String entityWord : entityTypeArr) {
280       oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
281     }
282
283     List<String> searchableAttr =
284         getOxmAttributes(oxmJaxbContext, oxmEntityType, entityType, "searchable");
285     if (searchableAttr == null) {
286       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
287           "Searchable attribute not found for payload entity type '" + entityType + "'");
288       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
289           "Searchable attribute not found for payload entity type '" + entityType + "'",
290           uebPayload);
291
292       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
293       return;
294     }
295
296     String entityPrimaryKeyFieldName =
297         getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
298     if (entityPrimaryKeyFieldName == null) {
299       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
300           "Payload missing primary key attribute");
301       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
302           "Payload missing primary key attribute", uebPayload);
303       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
304       return;
305     }
306     String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
307     if (entityPrimaryKeyFieldValue.isEmpty()) {
308       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
309           "Payload missing primary value attribute");
310       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
311           "Payload missing primary value attribute", uebPayload);
312
313       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
314       return;
315     }
316
317     SpikeEventEntity spikeEventEntity = new SpikeEventEntity();
318
319     /*
320      * Use the OXM Model to determine the primary key field name based on the entity-type
321      */
322
323     spikeEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
324     spikeEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
325     spikeEventEntity.setEntityType(entityType);
326     spikeEventEntity.setLink(entityLink);
327
328     if (!getSearchTags(spikeEventEntity, searchableAttr, uebPayload, action)) {
329       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
330           "Payload missing searchable attribute for entity type '" + entityType + "'");
331       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
332           "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
333
334       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
335       return;
336     }
337
338     try {
339       spikeEventEntity.deriveFields();
340
341     } catch (NoSuchAlgorithmException e) {
342       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Cannot create unique SHA digest");
343       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Cannot create unique SHA digest",
344           uebPayload);
345
346       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
347       return;
348     }
349
350
351     handleSearchServiceOperation(spikeEventEntity, action, entitySearchIndex);
352
353     long stopTime = System.currentTimeMillis();
354     metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_SPIKE_EVENT,
355         String.valueOf(stopTime - startTime));
356
357     setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
358     return;
359   }
360
361
362
363   private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
364
365     exchange.getOut().setHeader("ResponseType", responseType.toString());
366     exchange.getOut().setBody(additionalInfo);
367   }
368
369
370   /*
371    * Load the UEB JSON payload, any errors would result to a failure case response.
372    */
373   private JSONObject getUebContentAsJson(String payload, String contentKey) {
374
375     JSONObject uebJsonObj;
376     JSONObject uebObjContent;
377
378     try {
379       uebJsonObj = new JSONObject(payload);
380     } catch (JSONException e) {
381       logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
382       logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
383       return null;
384     }
385
386     if (uebJsonObj.has(contentKey)) {
387       uebObjContent = uebJsonObj.getJSONObject(contentKey);
388     } else {
389       logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
390       logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
391       return null;
392     }
393
394     return uebObjContent;
395   }
396
397
398   private SpikeEventVertex initializeSpikeEventVertex(String payload) {
399
400     SpikeEventVertex eventVertex = null;
401     ObjectMapper mapper = new ObjectMapper();
402
403     // Make sure that were were actually passed in a valid string.
404     if (payload == null || payload.isEmpty()) {
405       logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_VERTEX);
406       logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_VERTEX);
407
408       return eventVertex;
409     }
410
411     // Marshal the supplied string into a UebEventHeader object.
412     try {
413       eventVertex = mapper.readValue(payload, SpikeEventVertex.class);
414     } catch (JsonProcessingException e) {
415       logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
416     } catch (Exception e) {
417       logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
418     }
419
420     if (eventVertex != null) {
421       logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventVertex.toString());
422     }
423
424     return eventVertex;
425
426   }
427
428
429   private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
430       String oxmEntityType, String entityType) {
431
432     DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
433     if (entity == null) {
434       return null;
435     }
436
437     List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
438     if (list != null && !list.isEmpty()) {
439       String keyName = list.get(0).getName();
440       return keyName.substring(0, keyName.indexOf('/'));
441     }
442
443     return "";
444   }
445
446   private String lookupValueUsingKey(String payload, String key) throws JSONException {
447     JsonNode jsonNode = convertToJsonNode(payload);
448     return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
449   }
450
451
452   private JsonNode convertToJsonNode(String payload) {
453
454     ObjectMapper mapper = new ObjectMapper();
455     JsonNode jsonNode = null;
456     try {
457       jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
458     } catch (IOException e) {
459       logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, EVENT_VERTEX + " missing",
460           payload);
461       logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, EVENT_VERTEX + " missing",
462           "");
463     }
464
465     return jsonNode;
466   }
467
468
469   private boolean getSearchTags(SpikeEventEntity spikeEventEntity, List<String> searchableAttr,
470       String payload, String action) {
471
472     boolean hasSearchableAttr = false;
473     for (String searchTagField : searchableAttr) {
474       String searchTagValue;
475       if (searchTagField.equalsIgnoreCase(spikeEventEntity.getEntityPrimaryKeyName())) {
476         searchTagValue = spikeEventEntity.getEntityPrimaryKeyValue();
477       } else {
478         searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
479       }
480
481       if (searchTagValue != null && !searchTagValue.isEmpty()) {
482         hasSearchableAttr = true;
483         spikeEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
484       }
485     }
486     return hasSearchableAttr;
487   }
488
489   /*
490    * Check if OXM version is available. If available, load it.
491    */
492   private DynamicJAXBContext loadOxmContext(String version) {
493     if (version == null) {
494       logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
495       return null;
496     }
497
498     return oxmVersionContextMap.get(version);
499   }
500
501   private List<String> getOxmAttributes(DynamicJAXBContext oxmJaxbContext, String oxmEntityType,
502       String entityType, String fieldName) {
503
504     DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
505     if (entity == null) {
506       return null;
507     }
508
509     /*
510      * Check for searchable XML tag
511      */
512     List<String> fieldValues = null;
513     Map<String, String> properties = entity.getDescriptor().getProperties();
514     for (Map.Entry<String, String> entry : properties.entrySet()) {
515       if (entry.getKey().equalsIgnoreCase(fieldName)) {
516         fieldValues = Arrays.asList(entry.getValue().split(","));
517         break;
518       }
519     }
520
521     return fieldValues;
522   }
523
524
525
526   protected SpikeEventEntity getPopulatedEntity(JsonNode entityNode,
527       OxmEntityDescriptor resultDescriptor) {
528     SpikeEventEntity d = new SpikeEventEntity();
529
530     d.setEntityType(resultDescriptor.getEntityName());
531
532     List<String> primaryKeyValues = new ArrayList<>();
533     List<String> primaryKeyNames = new ArrayList<>();
534     String pkeyValue;
535
536     for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
537       pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
538       if (pkeyValue != null) {
539         primaryKeyValues.add(pkeyValue);
540         primaryKeyNames.add(keyName);
541       } else {
542         // logger.warn("getPopulatedDocument(), pKeyValue is null for entityType = " +
543         // resultDescriptor.getEntityName());
544         logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
545             resultDescriptor.getEntityName());
546       }
547     }
548
549     final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
550     d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
551     final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
552     d.setEntityPrimaryKeyName(primaryCompositeKeyName);
553
554     final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
555
556     /*
557      * Based on configuration, use the configured field names for this entity-Type to build a
558      * multi-value collection of search tags for elastic search entity search criteria.
559      */
560
561
562     for (String searchTagField : searchTagFields) {
563       String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
564       if (searchTagValue != null && !searchTagValue.isEmpty()) {
565         d.addSearchTagWithKey(searchTagValue, searchTagField);
566       }
567     }
568
569     return d;
570   }
571
572
573   /**
574    * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
575    * index
576    * 
577    * @param eventEntity Entity/data to use in operation
578    * @param action The operation to perform
579    * @param target Resource to perform the operation on
580    * @param allowDeleteEvent Allow delete operation to be performed on resource
581    */
582   protected void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity, String action,
583       String index) {
584     try {
585
586       Map<String, List<String>> headers = new HashMap<>();
587       headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
588       headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
589
590       String entityId = eventEntity.getId();
591
592       if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
593           || action.equalsIgnoreCase(ACTION_UPDATE)) {
594
595         // Run the GET to retrieve the ETAG from the search service
596         OperationResult storedEntity = searchAgent.getDocument(index, entityId);
597
598         if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
599           List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
600
601           if (etag != null && !etag.isEmpty()) {
602             headers.put(Headers.IF_MATCH, etag);
603           } else {
604             logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index, entityId);
605           }
606         }
607
608         // Write the entity to the search service.
609         // PUT
610         searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
611       } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
612         // Write the entry to the search service.
613         searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
614
615       } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
616         // Run the GET to retrieve the ETAG from the search service
617         OperationResult storedEntity = searchAgent.getDocument(index, entityId);
618
619         if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
620           List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
621
622           if (etag != null && !etag.isEmpty()) {
623             headers.put(Headers.IF_MATCH, etag);
624           } else {
625             logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index, entityId);
626           }
627
628           searchAgent.deleteDocument(index, eventEntity.getId(), headers);
629         } else {
630           logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index, entityId);
631         }
632       } else {
633         logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
634       }
635     } catch (IOException e) {
636       logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
637           action);
638     }
639   }
640
641
642
643   // put this here until we find a better spot
644   /**
645    * Helper utility to concatenate substrings of a URI together to form a proper URI.
646    * 
647    * @param suburis the list of substrings to concatenate together
648    * @return the concatenated list of substrings
649    */
650   public static String concatSubUri(String... suburis) {
651     String finalUri = "";
652
653     for (String suburi : suburis) {
654
655       if (suburi != null) {
656         // Remove any leading / since we only want to append /
657         suburi = suburi.replaceFirst("^/*", "");
658
659         // Add a trailing / if one isn't already there
660         finalUri += suburi.endsWith("/") ? suburi : suburi + "/";
661       }
662     }
663
664     return finalUri;
665   }
666 }