2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.datarouter.policy;
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.Collections;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
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.datarouter.entity.AaiEventEntity;
44 import org.onap.aai.datarouter.entity.AggregationEntity;
45 import org.onap.aai.datarouter.entity.DocumentStoreDataEntity;
46 import org.onap.aai.datarouter.entity.SuggestionSearchEntity;
47 import org.onap.aai.datarouter.entity.TopographicalEntity;
48 import org.onap.aai.datarouter.entity.UebEventHeader;
49 import org.onap.aai.datarouter.logging.EntityEventPolicyMsgs;
50 import org.onap.aai.datarouter.util.NodeUtils;
51 import org.onap.aai.datarouter.util.RouterServiceUtil;
52 import org.onap.aai.datarouter.util.SearchServiceAgent;
53 import org.onap.aai.datarouter.util.SearchSuggestionPermutation;
54 import org.onap.aai.entity.OxmEntityDescriptor;
55 import org.onap.aai.util.CrossEntityReference;
56 import org.onap.aai.util.EntityOxmReferenceHelper;
57 import org.onap.aai.util.ExternalOxmModelProcessor;
58 import org.onap.aai.schema.OxmModelLoader;
59 import org.onap.aai.util.Version;
60 import org.onap.aai.util.VersionedOxmEntities;
61 import org.onap.aai.cl.api.Logger;
62 import org.onap.aai.cl.eelf.LoggerFactory;
63 import org.onap.aai.cl.mdc.MdcContext;
64 import org.onap.aai.restclient.client.Headers;
65 import org.onap.aai.restclient.client.OperationResult;
66 import org.onap.aai.restclient.rest.HttpUtil;
69 import com.fasterxml.jackson.core.JsonProcessingException;
70 import com.fasterxml.jackson.databind.JsonNode;
71 import com.fasterxml.jackson.databind.ObjectMapper;
72 import com.fasterxml.jackson.databind.ObjectWriter;
73 import com.fasterxml.jackson.databind.node.ObjectNode;
75 public class EntityEventPolicy implements Processor {
77 public static final String additionalInfo = "Response of AAIEntityEventPolicy";
78 private static final String ENTITY_SEARCH_SCHEMA = "entitysearch_schema.json";
79 private static final String TOPOGRAPHICAL_SEARCH_SCHEMA = "topographysearch_schema.json";
80 private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
82 private static final String EVENT_HEADER = "event-header";
83 private static final String ENTITY_HEADER = "entity";
84 private static final String ACTION_CREATE = "create";
85 private static final String ACTION_DELETE = "delete";
86 private static final String ACTION_UPDATE = "update";
87 private static final String PROCESS_AAI_EVENT = "Process AAI Event";
88 private static final String TOPO_LAT = "latitude";
89 private static final String TOPO_LONG = "longitude";
91 private static final List<String> SUPPORTED_ACTIONS =
92 Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
94 Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
95 private String oxmVersion = null;
97 /** Agent for communicating with the Search Service. */
98 private SearchServiceAgent searchAgent = null;
100 /** Search index name for storing AAI event entities. */
101 private String entitySearchIndex;
103 /** Search index name for storing topographical search data. */
104 private String topographicalSearchIndex;
106 /** Search index name for suggestive search data. */
107 private String aggregateGenericVnfIndex;
109 private String autosuggestIndex;
111 private String srcDomain;
113 private Logger logger;
114 private Logger metricsLogger;
116 public enum ResponseType {
117 SUCCESS, PARTIAL_SUCCESS, FAILURE;
120 public EntityEventPolicy(EntityEventPolicyConfig config) throws FileNotFoundException {
121 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
122 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
123 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
126 srcDomain = config.getSourceDomain();
128 // Populate the index names.
129 entitySearchIndex = config.getSearchEntitySearchIndex();
130 topographicalSearchIndex = config.getSearchTopographySearchIndex();
131 aggregateGenericVnfIndex = config.getSearchAggregationVnfIndex();
132 autosuggestIndex = config.getSearchEntityAutoSuggestIndex();
134 // Instantiate the agent that we will use for interacting with the Search Service.
135 searchAgent = new SearchServiceAgent(config.getSearchCertName(),
136 config.getSearchKeystore(),
137 config.getSearchKeystorePwd(),
138 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
139 config.getSearchEndpoint()),
140 config.getSearchEndpointDocuments(),
143 this.externalOxmModelProcessors = new ArrayList<>();
144 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
145 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
146 OxmModelLoader.loadModels();
147 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
148 parseLatestOxmVersion();
151 private void parseLatestOxmVersion() {
152 int latestVersion = -1;
153 if (oxmVersionContextMap != null) {
154 Iterator it = oxmVersionContextMap.entrySet().iterator();
155 while (it.hasNext()) {
156 Map.Entry pair = (Map.Entry) it.next();
158 String version = pair.getKey().toString();
159 int versionNum = Integer.parseInt(version.substring(1, version.length()));
161 if (versionNum > latestVersion) {
162 latestVersion = versionNum;
163 oxmVersion = pair.getKey().toString();
166 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
169 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
173 public void startup() {
175 // Create the indexes in the search service if they do not already exist.
176 searchAgent.createSearchIndex(entitySearchIndex, ENTITY_SEARCH_SCHEMA);
177 searchAgent.createSearchIndex(topographicalSearchIndex, TOPOGRAPHICAL_SEARCH_SCHEMA);
179 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
184 * Convert object to json.
186 * @param object the object
187 * @param pretty the pretty
189 * @throws JsonProcessingException the json processing exception
191 public static String convertObjectToJson(Object object, boolean pretty)
192 throws JsonProcessingException {
196 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
199 ow = new ObjectMapper().writer();
202 return ow.writeValueAsString(object);
205 public void returnWithError(Exchange exchange, String payload, String errorMsg){
206 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE, errorMsg);
207 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, errorMsg, payload);
208 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
212 public void process(Exchange exchange) throws Exception {
214 long startTime = System.currentTimeMillis();
216 String uebPayload = exchange.getIn().getBody().toString();
218 JsonNode uebAsJson =null;
219 ObjectMapper mapper = new ObjectMapper();
221 uebAsJson = mapper.readTree(uebPayload);
222 } catch (IOException e){
223 returnWithError(exchange, uebPayload, "Invalid Payload");
227 // Load the UEB payload data, any errors will result in a failure and discard
228 JSONObject uebObjHeader = getUebContentAsJson(uebPayload, EVENT_HEADER);
229 if (uebObjHeader == null) {
230 returnWithError(exchange, uebPayload, "Payload is missing " + EVENT_HEADER);
234 JSONObject uebObjEntity = getUebContentAsJson(uebPayload, ENTITY_HEADER);
235 if (uebObjEntity == null) {
236 returnWithError(exchange, uebPayload, "Payload is missing " + ENTITY_HEADER);
240 UebEventHeader eventHeader;
241 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
243 // Get src domain from header; discard event if not originated from same domain
244 String payloadSrcDomain = eventHeader.getDomain();
245 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
246 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
247 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
248 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
249 "Unrecognized source domain '" + payloadSrcDomain + "'");
251 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
255 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion);
256 if (oxmJaxbContext == null) {
257 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
258 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "OXM version mismatch",
261 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
265 String action = eventHeader.getAction();
266 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
267 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
268 "Unrecognized action '" + action + "'", uebPayload);
269 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
270 "Unrecognized action '" + action + "'");
272 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
276 String entityType = eventHeader.getEntityType();
277 if (entityType == null) {
278 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
279 "Payload header missing entity type", uebPayload);
280 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
281 "Payload header missing entity type");
283 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
287 String topEntityType = eventHeader.getTopEntityType();
288 if (topEntityType == null) {
289 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
290 "Payload header missing top entity type", uebPayload);
291 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
292 "Payload header top missing entity type");
294 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
298 String entityLink = eventHeader.getEntityLink();
299 if (entityLink == null) {
300 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
301 "Payload header missing entity link", uebPayload);
302 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
303 "Payload header missing entity link");
305 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
309 // log the fact that all data are in good shape
310 logger.info(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_NONVERBOSE, action,
312 logger.debug(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
316 // Process for building AaiEventEntity object
317 String oxmEntityType = new OxmEntityTypeConverter().convert(entityType);
319 List<String> searchableAttr =
320 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
321 if (searchableAttr == null) {
322 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
323 "Searchable attribute not found for payload entity type '" + entityType + "'");
324 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
325 "Searchable attribute not found for payload entity type '" + entityType + "'",
328 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
332 String entityPrimaryKeyFieldName =
333 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
334 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
335 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
336 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
337 "Payload missing primary key attribute");
338 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
339 "Payload missing primary key attribute", uebPayload);
341 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
345 AaiEventEntity aaiEventEntity = new AaiEventEntity();
348 * Use the OXM Model to determine the primary key field name based on the entity-type
351 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
352 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
353 aaiEventEntity.setEntityType(entityType);
354 aaiEventEntity.setLink(entityLink);
356 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
357 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
358 "Payload missing searchable attribute for entity type '" + entityType + "'");
359 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
360 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
362 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
368 aaiEventEntity.deriveFields();
370 } catch (NoSuchAlgorithmException e) {
371 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
372 "Cannot create unique SHA digest");
373 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
374 "Cannot create unique SHA digest", uebPayload);
376 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
380 handleSearchServiceOperation(aaiEventEntity, action, entitySearchIndex);
382 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
383 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
386 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
388 VersionedOxmEntities oxmEntities =
389 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion.toLowerCase()));
393 * 1. If the entity type is "customer", the below check will return true if any nested entityType
394 * in that model could contain a CER based on the OXM model version that has been loaded.
395 * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
396 * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
397 * assumption that AAI will push down all required cascade-deletes for nested entities as well
398 * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
399 * CREATE service-instance.
402 if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
403 && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
405 // We know the model "can" contain a CER reference definition, let's process a bit more
407 HashMap<String, CrossEntityReference> crossEntityRefMap =
408 oxmEntities.getCrossEntityReferences();
410 JSONObject entityJsonObject = getUebEntity(uebPayload);
412 if (entityJsonObject != null) {
413 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
415 String parentEntityType = entityType;
417 String targetEntityUrl = entityLink;
419 for (Map.Entry<String, CrossEntityReference> entry : crossEntityRefMap.entrySet()) {
422 * if we know service-subscription is in the tree, then we can pull our all instances and process
426 String key = entry.getKey();
427 CrossEntityReference cerDescriptor = entry.getValue();
429 ArrayList<JsonNode> foundNodes = new ArrayList<>();
431 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
433 if (!foundNodes.isEmpty()) {
435 for (JsonNode n : foundNodes) {
436 if ("customer".equalsIgnoreCase(parentEntityType)) {
438 * NOTES: 1. prepare to hand-create url for service-instance 2. this will break if the
439 * URL structure for service-instance changes
441 if (n.has("service-type")) {
442 targetEntityUrl += "/service-subscriptions/service-subscription/"
443 + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
444 + "/service-instances/service-instance/";
448 List<String> extractedParentEntityAttributeValues = new ArrayList<>();
450 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
451 extractedParentEntityAttributeValues);
453 List<JsonNode> nestedTargetEntityInstances = new ArrayList<>();
454 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
455 nestedTargetEntityInstances);
457 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
459 * Now: 1. build the AAIEntityType (IndexDocument) based on the extract entity 2. Get
460 * data from ES 3. Extract ETAG 4. Merge ES Doc + AAIEntityType + Extracted Parent
461 * Cross-Entity-Reference Values 5. Put data into ES with ETAG + updated doc
464 // Get the complete URL for target entity
465 if (targetEntityInstance.has("link")) { // nested SI has url mentioned
466 targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance, "link");
467 } else if ("customer".equalsIgnoreCase(parentEntityType) && targetEntityInstance.has("service-instance-id")) {
468 targetEntityUrl += RouterServiceUtil.getNodeFieldAsText(targetEntityInstance, "service-instance-id");
471 OxmEntityDescriptor searchableDescriptor = oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
473 if (searchableDescriptor != null) {
475 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
477 AaiEventEntity entityToSync = null;
481 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
484 * Ready to do some ElasticSearch ops
487 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
488 entityToSync.addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
491 entityToSync.setLink(targetEntityUrl);
492 entityToSync.deriveFields();
494 updateCerInEntity(entityToSync);
496 } catch (NoSuchAlgorithmException e) {
497 logger.debug(e.getMessage());
501 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
502 "failure to find searchable descriptor for type "
503 + cerDescriptor.getTargetEntityType());
510 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
511 "failed to find 0 instances of cross-entity-reference with entity " + key);
516 logger.info(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, "Unable to get UEB object");
520 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
521 + topEntityType + " does not contain a cross-entity-reference entity");
525 * Process for autosuggestable entities
527 if (oxmEntities != null) {
528 Map<String, OxmEntityDescriptor> rootDescriptor =
529 oxmEntities.getSuggestableEntityDescriptors();
530 if (!rootDescriptor.isEmpty()) {
531 List<String> suggestibleAttrInPayload = new ArrayList<>();
532 List<String> suggestibleAttrInOxm = extractSuggestableAttr(oxmEntities, entityType);
533 if (suggestibleAttrInOxm != null) {
534 for (String attr: suggestibleAttrInOxm){
535 if ( uebObjEntity.has(attr) ){
536 suggestibleAttrInPayload.add(attr);
541 if (suggestibleAttrInPayload.isEmpty()) {
545 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
546 AggregationEntity ae = new AggregationEntity();
547 ae.setLink(entityLink);
548 ae.deriveFields(uebAsJson);
550 handleSearchServiceOperation(ae, action, aggregateGenericVnfIndex);
553 * It was decided to silently ignore DELETE requests for resources we don't allow to be
554 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
556 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
557 List<ArrayList<String>> listOfValidPowerSetElements =
558 SearchSuggestionPermutation.getNonEmptyUniqueLists(suggestibleAttrInPayload);
560 // Now we have a list containing the power-set (minus empty element) for the status that are
561 // available in the payload. Try inserting a document for every combination.
562 for (ArrayList<String> list : listOfValidPowerSetElements) {
563 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
564 suggestionSearchEntity.setEntityType(entityType);
565 suggestionSearchEntity.setSuggestableAttr(list);
566 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
567 suggestionSearchEntity.setFilterBasedPayloadFromResponse(uebAsJson.get("entity"),
568 suggestibleAttrInOxm, list);
569 suggestionSearchEntity.setSuggestionInputPermutations(
570 suggestionSearchEntity.generateSuggestionInputPermutations());
572 if (suggestionSearchEntity.isSuggestableDoc()) {
574 suggestionSearchEntity.generateSearchSuggestionDisplayStringAndId();
575 } catch (NoSuchAlgorithmException e) {
576 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
577 "Cannot create unique SHA digest for search suggestion data. Exception: "
578 + e.getLocalizedMessage());
581 handleSearchServiceOperation(suggestionSearchEntity, action, autosuggestIndex);
588 long stopTime = System.currentTimeMillis();
590 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
591 String.valueOf(stopTime - startTime));
593 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
597 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
598 // Extract suggestable attributeshandleTopographicalData
599 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
601 if (rootDescriptor == null) {
602 return Collections.emptyList();
605 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
608 return Collections.emptyList();
611 return desc.getSuggestableAttributes();
614 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
618 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
620 if (rootDescriptor == null) {
621 return Collections.emptyList();
624 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
625 return desc.getAlias();
628 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
630 exchange.getOut().setHeader("ResponseType", responseType.toString());
631 exchange.getOut().setBody(additionalInfo);
634 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
635 List<String> suggestableAttr, List<String> alias) {
637 // Extract suggestable attributes
638 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
640 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
641 suggestableAttr = desc.getSuggestableAttributes();
644 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
645 desc = rootDescriptor.get(entityType);
646 alias = desc.getAlias();
650 * Load the UEB JSON payload, any errors would result to a failure case response.
652 private JSONObject getUebContentAsJson(String payload, String contentKey) {
654 JSONObject uebJsonObj;
655 JSONObject uebObjContent;
658 uebJsonObj = new JSONObject(payload);
659 } catch (JSONException e) {
660 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
661 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
665 if (uebJsonObj.has(contentKey)) {
666 uebObjContent = uebJsonObj.getJSONObject(contentKey);
668 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
669 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
673 return uebObjContent;
677 private UebEventHeader initializeUebEventHeader(String payload) {
679 UebEventHeader eventHeader = null;
680 ObjectMapper mapper = new ObjectMapper();
682 // Make sure that were were actually passed in a valid string.
683 if (payload == null || payload.isEmpty()) {
684 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
685 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
690 // Marshal the supplied string into a UebEventHeader object.
692 eventHeader = mapper.readValue(payload, UebEventHeader.class);
693 } catch (JsonProcessingException e) {
694 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
695 } catch (Exception e) {
696 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
699 if (eventHeader != null) {
700 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
708 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
709 String oxmEntityType, String entityType) {
711 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
712 if (entity == null) {
716 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
717 if (list != null && !list.isEmpty()) {
718 String keyName = list.get(0).getName();
719 return keyName.substring(0, keyName.indexOf('/'));
725 private String lookupValueUsingKey(String payload, String key) throws JSONException {
726 JsonNode jsonNode = convertToJsonNode(payload);
727 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
730 private JsonNode convertToJsonNode(String payload) {
732 ObjectMapper mapper = new ObjectMapper();
733 JsonNode jsonNode = null;
735 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
736 } catch (IOException e) {
737 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
739 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
746 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
747 String payload, String action) {
749 boolean hasSearchableAttr = false;
750 for (String searchTagField : searchableAttr) {
751 String searchTagValue;
752 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
753 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
755 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
758 if (searchTagValue != null && !searchTagValue.isEmpty()) {
759 hasSearchableAttr = true;
760 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
763 return hasSearchableAttr;
767 * Check if OXM version is available. If available, load it.
769 private DynamicJAXBContext loadOxmContext(String version) {
770 if (version == null) {
771 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
775 return oxmVersionContextMap.get(version);
778 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
779 String oxmEntityType, String entityType, String fieldName) {
781 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
782 if (entity == null) {
787 * Check for searchable XML tag
789 List<String> fieldValues = null;
790 Map<String, String> properties = entity.getDescriptor().getProperties();
791 for (Map.Entry<String, String> entry : properties.entrySet()) {
792 if (entry.getKey().equalsIgnoreCase(fieldName)) {
793 fieldValues = Arrays.asList(entry.getValue().split(","));
801 private JSONObject getUebEntity(String payload) {
802 JSONObject uebJsonObj;
805 uebJsonObj = new JSONObject(payload);
806 } catch (JSONException e) {
807 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
808 "Payload has invalid JSON Format", payload);
809 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
810 "Payload has invalid JSON Format");
814 if (uebJsonObj.has(ENTITY_HEADER)) {
815 return uebJsonObj.getJSONObject(ENTITY_HEADER);
817 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing", payload);
818 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
823 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
824 OxmEntityDescriptor resultDescriptor) {
825 AaiEventEntity d = new AaiEventEntity();
827 d.setEntityType(resultDescriptor.getEntityName());
829 List<String> primaryKeyValues = new ArrayList<>();
830 List<String> primaryKeyNames = new ArrayList<>();
833 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
834 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
835 if (pkeyValue != null) {
836 primaryKeyValues.add(pkeyValue);
837 primaryKeyNames.add(keyName);
839 logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
840 resultDescriptor.getEntityName());
844 final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
845 d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
846 final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
847 d.setEntityPrimaryKeyName(primaryCompositeKeyName);
849 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
852 * Based on configuration, use the configured field names for this entity-Type to build a
853 * multi-value collection of search tags for elastic search entity search criteria.
857 for (String searchTagField : searchTagFields) {
858 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
859 if (searchTagValue != null && !searchTagValue.isEmpty()) {
860 d.addSearchTagWithKey(searchTagValue, searchTagField);
867 private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
869 Map<String, List<String>> headers = new HashMap<>();
870 headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
871 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
873 String entityId = aaiEventEntity.getId();
876 // Run the GET to retrieve the ETAG from the search service
877 OperationResult storedEntity = searchAgent.getDocument(entitySearchIndex, entityId);
879 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
881 * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
882 * the pre-existing object,
883 * so all we want to do is update the CER on the pre-existing object (if needed).
886 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
888 if (etag != null && !etag.isEmpty()) {
889 headers.put(Headers.IF_MATCH, etag);
891 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
892 entitySearchIndex, entityId);
895 ArrayList<JsonNode> sourceObject = new ArrayList<>();
896 NodeUtils.extractObjectsByKey(
897 NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
898 "content", sourceObject);
900 if (!sourceObject.isEmpty()) {
901 JsonNode node = sourceObject.get(0);
902 final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
903 "crossEntityReferenceValues");
904 String newCer = aaiEventEntity.getCrossReferenceEntityValues();
905 boolean hasNewCer = true;
906 if (sourceCer != null && sourceCer.length() > 0){ // already has CER
907 if ( !sourceCer.contains(newCer)){//don't re-add
908 newCer = sourceCer + ";" + newCer;
915 // Do the PUT with new CER
916 ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
917 jsonPayload = NodeUtils.convertObjectToJson(node, false);
918 searchAgent.putDocument(entitySearchIndex, entityId, jsonPayload, headers);
923 if (storedEntity.getResultCode() == 404) {
924 // entity not found, so attempt to do a PUT
925 searchAgent.putDocument(entitySearchIndex, entityId, aaiEventEntity.getAsJson(), headers);
927 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
928 aaiEventEntity.getId(), "SYNC_ENTITY");
931 } catch (IOException e) {
932 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
933 aaiEventEntity.getId(), "SYNC_ENTITY");
938 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
941 * @param eventEntity Entity/data to use in operation
942 * @param action The operation to perform
943 * @param target Resource to perform the operation on
944 * @param allowDeleteEvent Allow delete operation to be performed on resource
946 protected void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity,
951 Map<String, List<String>> headers = new HashMap<>();
952 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
953 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
955 String entityId = eventEntity.getId();
957 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
958 || action.equalsIgnoreCase(ACTION_UPDATE)) {
960 // Run the GET to retrieve the ETAG from the search service
961 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
963 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
964 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
966 if (etag != null && !etag.isEmpty()) {
967 headers.put(Headers.IF_MATCH, etag);
969 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
974 // Write the entity to the search service.
976 searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
977 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
978 // Write the entry to the search service.
979 searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
981 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
982 // Run the GET to retrieve the ETAG from the search service
983 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
985 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
986 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
988 if (etag != null && !etag.isEmpty()) {
989 headers.put(Headers.IF_MATCH, etag);
991 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
996 * The Spring-Boot version of the search-data-service rejects the DELETE operation unless
997 * we specify a Content-Type.
1000 headers.put("Content-Type", Arrays.asList(MediaType.APPLICATION_JSON.getMediaType()));
1002 searchAgent.deleteDocument(index, eventEntity.getId(), headers);
1004 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1008 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1010 } catch (IOException e) {
1011 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1016 private void handleTopographicalData(String payload, String action, String entityType,
1017 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1018 String entityPrimaryKeyFieldValue) {
1020 Map<String, String> topoData = new HashMap<>();
1022 List<String> topographicalAttr =
1023 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1024 if (topographicalAttr == null) {
1025 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1026 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1027 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1028 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1031 entityLink = lookupValueUsingKey(payload, "entity-link");
1032 for (String topoAttr : topographicalAttr) {
1033 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1035 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1036 entityPrimaryKeyFieldValue, entityLink);
1041 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1042 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1044 TopographicalEntity topoEntity = new TopographicalEntity();
1045 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1046 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1047 topoEntity.setEntityType(entityType);
1048 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1049 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1050 topoEntity.setSelfLink(entityLink);
1052 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1053 entityPrimaryKeyValue));
1054 } catch (NoSuchAlgorithmException e) {
1055 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1056 "Cannot create unique SHA digest for topographical data.");
1059 this.handleSearchServiceOperation(topoEntity, action, topographicalSearchIndex);
1063 // put this here until we find a better spot
1065 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1067 * @param suburis the list of substrings to concatenate together
1068 * @return the concatenated list of substrings
1070 public static String concatSubUri(String... suburis) {
1071 String finalUri = "";
1073 for (String suburi : suburis) {
1075 if (suburi != null) {
1076 // Remove any leading / since we only want to append /
1077 suburi = suburi.replaceFirst("^/*", "");
1079 // Add a trailing / if one isn't already there
1080 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";