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