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