router-core 1.3.0 changes
[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 import java.util.Map.Entry;
34
35 import org.apache.camel.Exchange;
36 import org.apache.camel.Processor;
37 import org.eclipse.persistence.dynamic.DynamicType;
38 import org.eclipse.persistence.internal.helper.DatabaseField;
39 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
40 import org.eclipse.persistence.oxm.MediaType;
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.DocumentStoreDataEntity;
47 import org.onap.aai.datarouter.entity.SpikeEventEntity;
48 import org.onap.aai.datarouter.entity.SpikeEventVertex;
49 import org.onap.aai.datarouter.logging.EntityEventPolicyMsgs;
50 import org.onap.aai.datarouter.util.RouterServiceUtil;
51 import org.onap.aai.datarouter.util.SearchServiceAgent;
52 import org.onap.aai.entity.OxmEntityDescriptor;
53 import org.onap.aai.util.EntityOxmReferenceHelper;
54 import org.onap.aai.util.ExternalOxmModelProcessor;
55 import org.onap.aai.schema.OxmModelLoader;
56 import org.onap.aai.setup.SchemaVersions;
57 import org.onap.aai.restclient.client.Headers;
58 import org.onap.aai.restclient.client.OperationResult;
59 import org.onap.aai.restclient.rest.HttpUtil;
60 import org.slf4j.MDC;
61
62 import com.fasterxml.jackson.core.JsonProcessingException;
63 import com.fasterxml.jackson.databind.JsonNode;
64 import com.fasterxml.jackson.databind.ObjectMapper;
65 import com.fasterxml.jackson.databind.ObjectWriter;
66
67 public class SpikeEntityEventPolicy implements Processor {
68
69   public static final String additionalInfo = "Response of SpikeEntityEventPolicy";
70   private static final String ENTITY_SEARCH_SCHEMA = "entitysearch_schema.json";
71
72   private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
73
74   /**
75    * Note (8-June-2018):
76    * 
77    * At present we don't need to support every event-type that could be present in the spike-events.
78    * The only one we want is a SPIKE "update-notification". In the future perhaps we need to add some
79    * configurability to the camel-route itself with a json camel filtering component so that routing
80    * logic can be modified as part of the camel route spring-xml instead of hard-coding the
81    * event filtering in here.
82    */
83   
84   private static final String PROCESS_SPIKE_EVENT = "Process Spike Event";
85   
86   private static final String UPDATE_NOTIFICATION = "update-notification";
87   private static final String SPIKE = "SPIKE";
88   
89   private static final String HEADER_KEY = "header";
90   private static final String EVENT_TYPE_KEY = "event-type";
91   private static final String SOURCE_NAME_KEY = "source-name";
92   private static final String BODY_KEY = "body";
93   private static final String OPERATION_KEY = "operation";
94
95   private static final String VERTEX_KEY = "vertex";
96   private static final String VERTEX_KEY_KEY = "key";
97   private static final String VERTEX_TYPE_KEY = "type";
98   private static final String VERTEX_SCHEMA_VERSION_KEY = "schema-version";
99   
100   private static final String CREATE = "create";
101   private static final String DELETE = "delete";
102   private static final String UPDATE = "update";
103
104   private static final List<String> SUPPORTED_ACTIONS =
105       Arrays.asList(CREATE, UPDATE, DELETE);
106
107   Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
108   private String oxmVersion = null;
109
110   /** Agent for communicating with the Search Service. */
111   private SearchServiceAgent searchAgent = null;
112   private String entitySearchIndex;
113
114   private Logger logger;
115   private Logger metricsLogger;
116   
117   public enum ResponseType {
118     SUCCESS, PARTIAL_SUCCESS, FAILURE;
119   };
120
121   public SpikeEntityEventPolicy(SpikeEntityEventPolicyConfig config) throws FileNotFoundException {
122     LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
123     logger = loggerFactoryInstance.getLogger(SpikeEntityEventPolicy.class.getName());
124     metricsLogger = loggerFactoryInstance.getMetricsLogger(SpikeEntityEventPolicy.class.getName());
125
126
127     //srcDomain = config.getSourceDomain();
128
129     // Populate the index names.
130     entitySearchIndex = config.getSearchEntitySearchIndex();
131
132     // Instantiate the agent that we will use for interacting with the Search Service.
133     searchAgent = new SearchServiceAgent(config.getSearchCertName(), config.getSearchKeystore(),
134         config.getSearchKeystorePwd(),
135         EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint()),
136         config.getSearchEndpointDocuments(), logger);
137
138     this.externalOxmModelProcessors = new ArrayList<>();
139     this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
140     OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
141     OxmModelLoader.loadModels(config.getSchemaVersions(), config.getSchemaLocationsBean());
142     oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
143     parseLatestOxmVersion();
144   }
145
146   private void parseLatestOxmVersion() {
147     int latestVersion = -1;
148     if (oxmVersionContextMap != null) {
149       Iterator<Entry<String, DynamicJAXBContext>> it = oxmVersionContextMap.entrySet().iterator();
150       while (it.hasNext()) {
151         Map.Entry pair = (Map.Entry) it.next();
152
153         String version = pair.getKey().toString();
154         int versionNum = Integer.parseInt(version.substring(1, version.length()));
155
156         if (versionNum > latestVersion) {
157           latestVersion = versionNum;
158           oxmVersion = pair.getKey().toString();
159         }
160
161         logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
162       }
163     } else {
164       logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
165     }
166   }
167
168   public void startup() {
169
170     // Create the indexes in the search service if they do not already exist.
171     searchAgent.createSearchIndex(entitySearchIndex, ENTITY_SEARCH_SCHEMA);
172     logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
173   }
174
175   
176
177   /**
178    * Convert object to json.
179    *
180    * @param object the object
181    * @param pretty the pretty
182    * @return the string
183    * @throws JsonProcessingException the json processing exception
184    */
185   public static String convertObjectToJson(Object object, boolean pretty)
186       throws JsonProcessingException {
187     ObjectWriter ow;
188
189     if (pretty) {
190       ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
191
192     } else {
193       ow = new ObjectMapper().writer();
194     }
195
196     return ow.writeValueAsString(object);
197   }
198
199   public void returnWithError(Exchange exchange, String payload, String errorMsg) {
200     logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE, errorMsg);
201     logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, errorMsg, payload);
202     setResponse(exchange, ResponseType.FAILURE, additionalInfo);
203   }
204
205   @Override
206   public void process(Exchange exchange) /*throws Exception*/ {
207
208     long startTime = System.currentTimeMillis();
209     
210     final String eventPayload = exchange.getIn().getBody().toString();
211     JSONObject mainJson = null;
212     
213     try {
214       
215       /*
216        * It is expected that mainJson will have multiple top level objects:
217        *  - header
218        *  - body
219        *  - result
220        */
221       
222       mainJson = new JSONObject(eventPayload);
223     } catch (JSONException exc) {
224       returnWithError(exchange, eventPayload, "Invalid Payload");
225       return;
226     }
227     
228     JSONObject eventHeader = mainJson.getJSONObject(HEADER_KEY);
229
230     if (eventHeader == null) {
231       returnWithError(exchange, eventPayload, "Payload is missing " + HEADER_KEY);
232       return;
233     }
234     
235     /*
236      * Only process SPIKE update-notification events 
237      */
238     
239     final String sourceName = eventHeader.getString(SOURCE_NAME_KEY);
240     final String eventType = eventHeader.getString(EVENT_TYPE_KEY);
241
242     if (!(SPIKE.equals(sourceName) && UPDATE_NOTIFICATION.equals(eventType))) {
243       // drop event
244       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Ignoring event with sourceName='"
245           + sourceName + "' and eventType='" + eventType + "'.  Payload=" + eventPayload);
246       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
247           "Ignoring event with sourceName='" + sourceName + "' and eventType='" + eventType + "'.");
248
249       /*
250        * I don't think ignoring a non-applicable event constitutes a failure.
251        */
252
253       setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
254       return;
255     }
256     
257     JSONObject eventBody = mainJson.getJSONObject(BODY_KEY);
258
259     if (eventBody == null) {
260       returnWithError(exchange, eventPayload, "Payload is missing " + BODY_KEY);
261       return;
262     }
263     
264     String action = eventBody.getString(OPERATION_KEY);
265     if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
266       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
267           "Unrecognized action '" + action + "'", eventPayload);
268       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
269           "Unrecognized action '" + action + "'");
270       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
271       return;
272     }
273
274     // Load the event body data, any errors will result in a failure and discard
275     
276     JSONObject spikeVertex = eventBody.getJSONObject(VERTEX_KEY);
277     if (spikeVertex == null) {
278       returnWithError(exchange, eventPayload, "Payload is missing " + VERTEX_KEY);
279       return;
280     }
281
282     SpikeEventVertex eventVertex = null;
283     try {
284       eventVertex = initializeSpikeEventVertex(spikeVertex);
285     } catch (JSONException exc) {
286       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Error initializating spike event.  Error: " + exc.getMessage(),
287           eventPayload);
288       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
289           "Error initializating spike event.  Error: " + exc.getMessage());
290
291       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
292       return;
293     }
294
295     DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion);
296     if (oxmJaxbContext == null) {
297       logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
298       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "OXM version mismatch", eventPayload);
299
300       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
301       return;
302     }
303
304     String entityType = eventVertex.getType();
305     if (entityType == null || entityType.isEmpty()) {
306       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
307           "Payload header missing entity type", eventPayload);
308       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
309           "Payload header missing entity type");
310
311       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
312       return;
313     }
314
315     String entityKey = eventVertex.getKey();
316     if (entityKey == null || entityKey.isEmpty()) {
317       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Payload vertex missing entity key",
318           eventPayload);
319       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
320           "Payload vertex missing entity key");
321
322       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
323       return;
324     }
325     String entityLink = eventVertex.getEntityLink();
326     if (entityLink == null || entityLink.isEmpty()) {
327       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
328           "Payload header missing entity link", eventPayload);
329       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
330           "Payload header missing entity link");
331
332       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
333       return;
334     }
335
336     // log the fact that all data are in good shape
337     logger.info(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_NONVERBOSE, action, entityType);
338     logger.debug(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
339         eventPayload);
340
341     // Process for building SpikeEventEntity object
342     String oxmEntityType = new OxmEntityTypeConverter().convert(entityType);
343     
344     List<String> searchableAttr =
345         getOxmAttributes(oxmJaxbContext, oxmEntityType, entityType, "searchable");
346     if (searchableAttr == null) {
347       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
348           "Searchable attribute not found for payload entity type '" + entityType + "'");
349       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
350           "Searchable attribute not found for payload entity type '" + entityType + "'",
351           eventPayload);
352
353       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
354       return;
355     }
356
357     String entityPrimaryKeyFieldName =
358         getEntityPrimaryKeyFieldName(oxmJaxbContext, eventPayload, oxmEntityType, entityType);
359     if (entityPrimaryKeyFieldName == null) {
360       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
361           "Payload missing primary key attribute");
362       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
363           "Payload missing primary key attribute", eventPayload);
364       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
365       return;
366     }
367     String entityPrimaryKeyFieldValue = lookupValueUsingKey(eventPayload, entityPrimaryKeyFieldName);
368     if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
369       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
370           "Payload missing primary value attribute");
371       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
372           "Payload missing primary value attribute", eventPayload);
373
374       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
375       return;
376     }
377
378     SpikeEventEntity spikeEventEntity = new SpikeEventEntity();
379
380     /*
381      * Use the OXM Model to determine the primary key field name based on the entity-type
382      */
383
384     spikeEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
385     spikeEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
386     spikeEventEntity.setEntityType(entityType);
387     spikeEventEntity.setLink(entityLink);
388     
389     System.out.println(spikeEventEntity);
390
391     if (!getSearchTags(spikeEventEntity, searchableAttr, eventPayload, action)) {
392       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
393           "Payload missing searchable attribute for entity type '" + entityType + "'");
394       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
395           "Payload missing searchable attribute for entity type '" + entityType + "'", eventPayload);
396
397       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
398       return;
399     }
400
401     try {
402       spikeEventEntity.deriveFields();
403
404     } catch (NoSuchAlgorithmException e) {
405       logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Cannot create unique SHA digest");
406       logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "Cannot create unique SHA digest",
407           eventPayload);
408
409       setResponse(exchange, ResponseType.FAILURE, additionalInfo);
410       return;
411     }
412
413
414     handleSearchServiceOperation(spikeEventEntity, action, entitySearchIndex);
415
416     long stopTime = System.currentTimeMillis();
417     metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_SPIKE_EVENT,
418         String.valueOf(stopTime - startTime));
419
420     setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
421     return;
422   }
423
424
425
426   private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
427
428     exchange.getOut().setHeader("ResponseType", responseType.toString());
429     exchange.getOut().setBody(additionalInfo);
430   }
431
432   private SpikeEventVertex initializeSpikeEventVertex(JSONObject vertexObject) throws JSONException {
433
434     /*
435      * These are all critical keys
436      */
437
438     final String vertexType = vertexObject.getString(VERTEX_TYPE_KEY);
439     final String vertexKey = vertexObject.getString(VERTEX_KEY_KEY);
440     final String vertexSchemaVersion = vertexObject.getString(VERTEX_SCHEMA_VERSION_KEY);
441
442     SpikeEventVertex eventVertex = new SpikeEventVertex(vertexType, vertexKey);
443     eventVertex.setSchemaVersion(vertexSchemaVersion);
444     logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventVertex.toString());
445
446     return eventVertex;
447
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) {
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, VERTEX_KEY + " missing",
482           payload);
483       logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, VERTEX_KEY + " 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 = 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
547
548   protected SpikeEventEntity getPopulatedEntity(JsonNode entityNode,
549       OxmEntityDescriptor resultDescriptor) {
550     SpikeEventEntity d = new SpikeEventEntity();
551
552     d.setEntityType(resultDescriptor.getEntityName());
553
554     List<String> primaryKeyValues = new ArrayList<>();
555     List<String> primaryKeyNames = new ArrayList<>();
556     String pkeyValue;
557
558     for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
559       pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
560       if (pkeyValue != null) {
561         primaryKeyValues.add(pkeyValue);
562         primaryKeyNames.add(keyName);
563       } else {
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    * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
595    * index
596    * 
597    * @param eventEntity Entity/data to use in operation
598    * @param action The operation to perform
599    * @param target Resource to perform the operation on
600    * @param allowDeleteEvent Allow delete operation to be performed on resource
601    */
602   protected void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity, String action,
603       String index) {
604     try {
605
606       Map<String, List<String>> headers = new HashMap<>();
607       headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
608       headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
609
610       String entityId = eventEntity.getId();
611
612       if ((action.equalsIgnoreCase(CREATE) && entityId != null)
613           || action.equalsIgnoreCase(UPDATE)) {
614
615         // Run the GET to retrieve the ETAG from the search service
616         OperationResult storedEntity = searchAgent.getDocument(index, entityId);
617
618         if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
619           List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
620
621           if (etag != null && !etag.isEmpty()) {
622             headers.put(Headers.IF_MATCH, etag);
623           } else {
624             logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index, entityId);
625           }
626         }
627
628         // Write the entity to the search service.
629         // PUT
630         searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
631       } else if (action.equalsIgnoreCase(CREATE)) {
632         // Write the entry to the search service.
633         searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
634
635       } else if (action.equalsIgnoreCase(DELETE)) {
636         // Run the GET to retrieve the ETAG from the search service
637         OperationResult storedEntity = searchAgent.getDocument(index, entityId);
638
639         if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
640           List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
641
642           if (etag != null && !etag.isEmpty()) {
643             headers.put(Headers.IF_MATCH, etag);
644           } else {
645             logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index, entityId);
646           }
647           
648           /*
649            * The Spring-Boot version of the search-data-service rejects the DELETE operation unless
650            * we specify a Content-Type.
651            */
652
653           headers.put("Content-Type", Arrays.asList(MediaType.APPLICATION_JSON.getMediaType()));
654
655           searchAgent.deleteDocument(index, eventEntity.getId(), headers);
656         } else {
657           logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index, entityId);
658         }
659       } else {
660         logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
661       }
662     } catch (IOException e) {
663       logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
664           action);
665     }
666   }
667
668
669
670   // put this here until we find a better spot
671   /**
672    * Helper utility to concatenate substrings of a URI together to form a proper URI.
673    * 
674    * @param suburis the list of substrings to concatenate together
675    * @return the concatenated list of substrings
676    */
677   public static String concatSubUri(String... suburis) {
678     String finalUri = "";
679
680     for (String suburi : suburis) {
681
682       if (suburi != null) {
683         // Remove any leading / since we only want to append /
684         suburi = suburi.replaceFirst("^/*", "");
685
686         // Add a trailing / if one isn't already there
687         finalUri += suburi.endsWith("/") ? suburi : suburi + "/";
688       }
689     }
690
691     return finalUri;
692   }
693 }