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