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