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.json.JSONException;
41 import org.json.JSONObject;
42 import org.onap.aai.datarouter.entity.AaiEventEntity;
43 import org.onap.aai.datarouter.entity.AggregationEntity;
44 import org.onap.aai.datarouter.entity.DocumentStoreDataEntity;
45 import org.onap.aai.datarouter.entity.OxmEntityDescriptor;
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.CrossEntityReference;
51 import org.onap.aai.datarouter.util.EntityOxmReferenceHelper;
52 import org.onap.aai.datarouter.util.ExternalOxmModelProcessor;
53 import org.onap.aai.datarouter.util.NodeUtils;
54 import org.onap.aai.datarouter.util.OxmModelLoader;
55 import org.onap.aai.datarouter.util.RouterServiceUtil;
56 import org.onap.aai.datarouter.util.SearchServiceAgent;
57 import org.onap.aai.datarouter.util.SearchSuggestionPermutation;
58 import org.onap.aai.datarouter.util.Version;
59 import org.onap.aai.datarouter.util.VersionedOxmEntities;
60 import org.onap.aai.cl.api.Logger;
61 import org.onap.aai.cl.eelf.LoggerFactory;
62 import org.onap.aai.cl.mdc.MdcContext;
63 import org.onap.aai.restclient.client.Headers;
64 import org.onap.aai.restclient.client.OperationResult;
65 import org.onap.aai.restclient.rest.HttpUtil;
68 import com.fasterxml.jackson.core.JsonProcessingException;
69 import com.fasterxml.jackson.databind.JsonNode;
70 import com.fasterxml.jackson.databind.ObjectMapper;
71 import com.fasterxml.jackson.databind.ObjectWriter;
72 import com.fasterxml.jackson.databind.node.ObjectNode;
74 public class EntityEventPolicy implements Processor {
76 public static final String additionalInfo = "Response of AAIEntityEventPolicy";
77 private static final String ENTITY_SEARCH_SCHEMA = "entitysearch_schema.json";
78 private static final String TOPOGRAPHICAL_SEARCH_SCHEMA = "topographysearch_schema.json";
79 private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
81 private static final String EVENT_HEADER = "event-header";
82 private static final String ENTITY_HEADER = "entity";
83 private static final String ACTION_CREATE = "create";
84 private static final String ACTION_DELETE = "delete";
85 private static final String ACTION_UPDATE = "update";
86 private static final String PROCESS_AAI_EVENT = "Process AAI Event";
87 private static final String TOPO_LAT = "latitude";
88 private static final String TOPO_LONG = "longitude";
90 private static final List<String> SUPPORTED_ACTIONS =
91 Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
93 Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
94 private String oxmVersion = null;
96 /** Agent for communicating with the Search Service. */
97 private SearchServiceAgent searchAgent = null;
99 /** Search index name for storing AAI event entities. */
100 private String entitySearchIndex;
102 /** Search index name for storing topographical search data. */
103 private String topographicalSearchIndex;
105 /** Search index name for suggestive search data. */
106 private String aggregateGenericVnfIndex;
108 private String autosuggestIndex;
110 private String srcDomain;
112 private Logger logger;
113 private Logger metricsLogger;
115 public enum ResponseType {
116 SUCCESS, PARTIAL_SUCCESS, FAILURE;
119 public EntityEventPolicy(EntityEventPolicyConfig config) throws FileNotFoundException {
120 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
121 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
122 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
125 srcDomain = config.getSourceDomain();
127 // Populate the index names.
128 entitySearchIndex = config.getSearchEntitySearchIndex();
129 topographicalSearchIndex = config.getSearchTopographySearchIndex();
130 aggregateGenericVnfIndex = config.getSearchAggregationVnfIndex();
131 autosuggestIndex = config.getSearchEntityAutoSuggestIndex();
133 // Instantiate the agent that we will use for interacting with the Search Service.
134 searchAgent = new SearchServiceAgent(config.getSearchCertName(),
135 config.getSearchKeystore(),
136 config.getSearchKeystorePwd(),
137 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
138 config.getSearchEndpoint()),
139 config.getSearchEndpointDocuments(),
142 this.externalOxmModelProcessors = new ArrayList<>();
143 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
144 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
145 OxmModelLoader.loadModels();
146 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
147 parseLatestOxmVersion();
150 private void parseLatestOxmVersion() {
151 int latestVersion = -1;
152 if (oxmVersionContextMap != null) {
153 Iterator it = oxmVersionContextMap.entrySet().iterator();
154 while (it.hasNext()) {
155 Map.Entry pair = (Map.Entry) it.next();
157 String version = pair.getKey().toString();
158 int versionNum = Integer.parseInt(version.substring(1, version.length()));
160 if (versionNum > latestVersion) {
161 latestVersion = versionNum;
162 oxmVersion = pair.getKey().toString();
165 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
168 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
172 public void startup() {
174 // Create the indexes in the search service if they do not already exist.
175 searchAgent.createSearchIndex(entitySearchIndex, ENTITY_SEARCH_SCHEMA);
176 searchAgent.createSearchIndex(topographicalSearchIndex, TOPOGRAPHICAL_SEARCH_SCHEMA);
178 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
183 * Convert object to json.
185 * @param object the object
186 * @param pretty the pretty
188 * @throws JsonProcessingException the json processing exception
190 public static String convertObjectToJson(Object object, boolean pretty)
191 throws JsonProcessingException {
195 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
198 ow = new ObjectMapper().writer();
201 return ow.writeValueAsString(object);
204 public void returnWithError(Exchange exchange, String payload, String errorMsg){
205 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE, errorMsg);
206 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, errorMsg, payload);
207 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
211 public void process(Exchange exchange) throws Exception {
213 long startTime = System.currentTimeMillis();
215 String uebPayload = exchange.getIn().getBody().toString();
217 JsonNode uebAsJson =null;
218 ObjectMapper mapper = new ObjectMapper();
220 uebAsJson = mapper.readTree(uebPayload);
221 } catch (IOException e){
222 returnWithError(exchange, uebPayload, "Invalid Payload");
226 // Load the UEB payload data, any errors will result in a failure and discard
227 JSONObject uebObjHeader = getUebContentAsJson(uebPayload, EVENT_HEADER);
228 if (uebObjHeader == null) {
229 returnWithError(exchange, uebPayload, "Payload is missing " + EVENT_HEADER);
233 JSONObject uebObjEntity = getUebContentAsJson(uebPayload, ENTITY_HEADER);
234 if (uebObjEntity == null) {
235 returnWithError(exchange, uebPayload, "Payload is missing " + ENTITY_HEADER);
239 UebEventHeader eventHeader;
240 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
242 // Get src domain from header; discard event if not originated from same domain
243 String payloadSrcDomain = eventHeader.getDomain();
244 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
245 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
246 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
247 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
248 "Unrecognized source domain '" + payloadSrcDomain + "'");
250 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
254 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
255 if (oxmJaxbContext == null) {
256 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
257 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "OXM version mismatch",
260 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
264 String action = eventHeader.getAction();
265 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
266 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
267 "Unrecognized action '" + action + "'", uebPayload);
268 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
269 "Unrecognized action '" + action + "'");
271 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
275 String entityType = eventHeader.getEntityType();
276 if (entityType == null) {
277 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
278 "Payload header missing entity type", uebPayload);
279 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
280 "Payload header missing entity type");
282 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
286 String topEntityType = eventHeader.getTopEntityType();
287 if (topEntityType == null) {
288 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
289 "Payload header missing top entity type", uebPayload);
290 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
291 "Payload header top missing entity type");
293 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
297 String entityLink = eventHeader.getEntityLink();
298 if (entityLink == null) {
299 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
300 "Payload header missing entity link", uebPayload);
301 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
302 "Payload header missing entity link");
304 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
308 // log the fact that all data are in good shape
309 logger.info(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_NONVERBOSE, action,
311 logger.debug(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
315 // Process for building AaiEventEntity object
316 String[] entityTypeArr = entityType.split("-");
317 String oxmEntityType = "";
318 for (String entityWord : entityTypeArr) {
319 oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
322 List<String> searchableAttr =
323 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
324 if (searchableAttr == null) {
325 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
326 "Searchable attribute not found for payload entity type '" + entityType + "'");
327 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
328 "Searchable attribute not found for payload entity type '" + entityType + "'",
331 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
335 String entityPrimaryKeyFieldName =
336 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
337 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
338 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
339 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
340 "Payload missing primary key attribute");
341 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
342 "Payload missing primary key attribute", uebPayload);
344 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
348 AaiEventEntity aaiEventEntity = new AaiEventEntity();
351 * Use the OXM Model to determine the primary key field name based on the entity-type
354 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
355 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
356 aaiEventEntity.setEntityType(entityType);
357 aaiEventEntity.setLink(entityLink);
359 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
360 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
361 "Payload missing searchable attribute for entity type '" + entityType + "'");
362 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
363 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
365 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
371 aaiEventEntity.deriveFields();
373 } catch (NoSuchAlgorithmException e) {
374 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
375 "Cannot create unique SHA digest");
376 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
377 "Cannot create unique SHA digest", uebPayload);
379 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
383 handleSearchServiceOperation(aaiEventEntity, action, entitySearchIndex);
385 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
386 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
389 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
391 VersionedOxmEntities oxmEntities =
392 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion));
396 * 1. If the entity type is "customer", the below check will return true if any nested entityType
397 * in that model could contain a CER based on the OXM model version that has been loaded.
398 * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
399 * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
400 * assumption that AAI will push down all required cascade-deletes for nested entities as well
401 * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
402 * CREATE service-instance.
405 if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
406 && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
408 // We know the model "can" contain a CER reference definition, let's process a bit more
410 HashMap<String, CrossEntityReference> crossEntityRefMap =
411 oxmEntities.getCrossEntityReferences();
413 JSONObject entityJsonObject = getUebEntity(uebPayload);
415 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
417 String parentEntityType = entityType;
419 String targetEntityUrl = entityLink;
421 for (Map.Entry<String, CrossEntityReference> entry : crossEntityRefMap.entrySet()) {
424 * if we know service-subscription is in the tree, then we can pull our all instances and
425 * process from there.
428 String key = entry.getKey();
429 CrossEntityReference cerDescriptor = entry.getValue();
431 ArrayList<JsonNode> foundNodes = new ArrayList<>();
433 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
435 if (!foundNodes.isEmpty()) {
437 for (JsonNode n : foundNodes) {
438 if ("customer".equalsIgnoreCase(parentEntityType)){
441 * 1. prepare to hand-create url for service-instance
442 * 2. this will break if the URL structure for service-instance changes
444 if (n.has("service-type")){
445 targetEntityUrl += "/service-subscriptions/service-subscription/"
446 + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
447 + "/service-instances/service-instance/";
452 List<String> extractedParentEntityAttributeValues = new ArrayList<>();
454 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
455 extractedParentEntityAttributeValues);
457 List<JsonNode> nestedTargetEntityInstances = new ArrayList<>();
458 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
459 nestedTargetEntityInstances);
461 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
464 * 1. build the AAIEntityType (IndexDocument) based on the extract entity
465 * 2. Get data from ES
467 * 4. Merge ES Doc + AAIEntityType + Extracted Parent Cross-Entity-Reference Values
468 * 5. Put data into ES with ETAG + updated doc
471 // Get the complete URL for target entity
472 if (targetEntityInstance.has("link")) { // nested SI has url mentioned
473 targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
475 } else if ("customer".equalsIgnoreCase(parentEntityType) &&
476 targetEntityInstance.has("service-instance-id")){
477 targetEntityUrl += RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
478 "service-instance-id");
481 OxmEntityDescriptor searchableDescriptor =
482 oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
484 if (searchableDescriptor != null) {
486 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
488 AaiEventEntity entityToSync = null;
492 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
495 * Ready to do some ElasticSearch ops
498 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
500 .addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
503 entityToSync.setLink(targetEntityUrl);
504 entityToSync.deriveFields();
506 updateCerInEntity(entityToSync);
508 } catch (NoSuchAlgorithmException e) {
509 logger.debug(e.getMessage());
513 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
514 "failure to find searchable descriptor for type "
515 + cerDescriptor.getTargetEntityType());
522 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
523 "failed to find 0 instances of cross-entity-reference with entity " + key);
529 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
530 + topEntityType + " does not contain a cross-entity-reference entity");
534 * Process for autosuggestable entities
536 if (oxmEntities != null) {
537 Map<String, OxmEntityDescriptor> rootDescriptor =
538 oxmEntities.getSuggestableEntityDescriptors();
539 if (!rootDescriptor.isEmpty()) {
540 List<String> suggestibleAttrInPayload = new ArrayList<>();
541 List<String> suggestibleAttrInOxm = extractSuggestableAttr(oxmEntities, entityType);
542 if (suggestibleAttrInOxm != null) {
543 for (String attr: suggestibleAttrInOxm){
544 if ( uebObjEntity.has(attr) ){
545 suggestibleAttrInPayload.add(attr);
550 if (suggestibleAttrInPayload.isEmpty()) {
554 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
555 AggregationEntity ae = new AggregationEntity();
556 ae.setLink(entityLink);
557 ae.deriveFields(uebAsJson);
559 handleSearchServiceOperation(ae, action, aggregateGenericVnfIndex);
562 * It was decided to silently ignore DELETE requests for resources we don't allow to be
563 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
565 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
566 List<ArrayList<String>> listOfValidPowerSetElements =
567 SearchSuggestionPermutation.getNonEmptyUniqueLists(suggestibleAttrInPayload);
569 // Now we have a list containing the power-set (minus empty element) for the status that are
570 // available in the payload. Try inserting a document for every combination.
571 for (ArrayList<String> list : listOfValidPowerSetElements) {
572 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
573 suggestionSearchEntity.setEntityType(entityType);
574 suggestionSearchEntity.setSuggestableAttr(list);
575 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
576 suggestionSearchEntity.setFilterBasedPayloadFromResponse(uebAsJson.get("entity"),
577 suggestibleAttrInOxm, list);
578 suggestionSearchEntity.setSuggestionInputPermutations(
579 suggestionSearchEntity.generateSuggestionInputPermutations());
581 if (suggestionSearchEntity.isSuggestableDoc()) {
583 suggestionSearchEntity.generateSearchSuggestionDisplayStringAndId();
584 } catch (NoSuchAlgorithmException e) {
585 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
586 "Cannot create unique SHA digest for search suggestion data. Exception: "
587 + e.getLocalizedMessage());
590 handleSearchServiceOperation(suggestionSearchEntity, action, autosuggestIndex);
597 long stopTime = System.currentTimeMillis();
599 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
600 String.valueOf(stopTime - startTime));
602 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
606 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
607 // Extract suggestable attributeshandleTopographicalData
608 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
610 if (rootDescriptor == null) {
611 return Collections.emptyList();
614 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
617 return Collections.emptyList();
620 return desc.getSuggestableAttributes();
623 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
627 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
629 if (rootDescriptor == null) {
630 return Collections.emptyList();
633 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
634 return desc.getAlias();
637 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
639 exchange.getOut().setHeader("ResponseType", responseType.toString());
640 exchange.getOut().setBody(additionalInfo);
643 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
644 List<String> suggestableAttr, List<String> alias) {
646 // Extract suggestable attributes
647 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
649 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
650 suggestableAttr = desc.getSuggestableAttributes();
653 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
654 desc = rootDescriptor.get(entityType);
655 alias = desc.getAlias();
659 * Load the UEB JSON payload, any errors would result to a failure case response.
661 private JSONObject getUebContentAsJson(String payload, String contentKey) {
663 JSONObject uebJsonObj;
664 JSONObject uebObjContent;
667 uebJsonObj = new JSONObject(payload);
668 } catch (JSONException e) {
669 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
670 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
674 if (uebJsonObj.has(contentKey)) {
675 uebObjContent = uebJsonObj.getJSONObject(contentKey);
677 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
678 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
682 return uebObjContent;
686 private UebEventHeader initializeUebEventHeader(String payload) {
688 UebEventHeader eventHeader = null;
689 ObjectMapper mapper = new ObjectMapper();
691 // Make sure that were were actually passed in a valid string.
692 if (payload == null || payload.isEmpty()) {
693 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
694 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
699 // Marshal the supplied string into a UebEventHeader object.
701 eventHeader = mapper.readValue(payload, UebEventHeader.class);
702 } catch (JsonProcessingException e) {
703 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
704 } catch (Exception e) {
705 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
708 if (eventHeader != null) {
709 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
717 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
718 String oxmEntityType, String entityType) {
720 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
721 if (entity == null) {
725 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
726 if (list != null && !list.isEmpty()) {
727 String keyName = list.get(0).getName();
728 return keyName.substring(0, keyName.indexOf('/'));
734 private String lookupValueUsingKey(String payload, String key) throws JSONException {
735 JsonNode jsonNode = convertToJsonNode(payload);
736 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
739 private JsonNode convertToJsonNode(String payload) {
741 ObjectMapper mapper = new ObjectMapper();
742 JsonNode jsonNode = null;
744 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
745 } catch (IOException e) {
746 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
748 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
755 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
756 String payload, String action) {
758 boolean hasSearchableAttr = false;
759 for (String searchTagField : searchableAttr) {
760 String searchTagValue;
761 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
762 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
764 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
767 if (searchTagValue != null && !searchTagValue.isEmpty()) {
768 hasSearchableAttr = true;
769 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
772 return hasSearchableAttr;
776 * Check if OXM version is available. If available, load it.
778 private DynamicJAXBContext loadOxmContext(String version) {
779 if (version == null) {
780 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
784 return oxmVersionContextMap.get(version);
787 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
788 String oxmEntityType, String entityType, String fieldName) {
790 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
791 if (entity == null) {
796 * Check for searchable XML tag
798 List<String> fieldValues = null;
799 Map<String, String> properties = entity.getDescriptor().getProperties();
800 for (Map.Entry<String, String> entry : properties.entrySet()) {
801 if (entry.getKey().equalsIgnoreCase(fieldName)) {
802 fieldValues = Arrays.asList(entry.getValue().split(","));
810 private JSONObject getUebEntity(String payload) {
811 JSONObject uebJsonObj;
814 uebJsonObj = new JSONObject(payload);
815 } catch (JSONException e) {
816 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
817 "Payload has invalid JSON Format", payload);
818 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
819 "Payload has invalid JSON Format");
823 if (uebJsonObj.has(ENTITY_HEADER)) {
824 return uebJsonObj.getJSONObject(ENTITY_HEADER);
826 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing", payload);
827 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
832 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
833 OxmEntityDescriptor resultDescriptor) {
834 AaiEventEntity d = new AaiEventEntity();
836 d.setEntityType(resultDescriptor.getEntityName());
838 List<String> primaryKeyValues = new ArrayList<>();
839 List<String> primaryKeyNames = new ArrayList<>();
842 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
843 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
844 if (pkeyValue != null) {
845 primaryKeyValues.add(pkeyValue);
846 primaryKeyNames.add(keyName);
848 logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
849 resultDescriptor.getEntityName());
853 final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
854 d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
855 final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
856 d.setEntityPrimaryKeyName(primaryCompositeKeyName);
858 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
861 * Based on configuration, use the configured field names for this entity-Type to build a
862 * multi-value collection of search tags for elastic search entity search criteria.
866 for (String searchTagField : searchTagFields) {
867 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
868 if (searchTagValue != null && !searchTagValue.isEmpty()) {
869 d.addSearchTagWithKey(searchTagValue, searchTagField);
876 private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
878 Map<String, List<String>> headers = new HashMap<>();
879 headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
880 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
882 String entityId = aaiEventEntity.getId();
885 // Run the GET to retrieve the ETAG from the search service
886 OperationResult storedEntity = searchAgent.getDocument(entitySearchIndex, entityId);
888 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
890 * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
891 * the pre-existing object,
892 * so all we want to do is update the CER on the pre-existing object (if needed).
895 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
897 if (etag != null && !etag.isEmpty()) {
898 headers.put(Headers.IF_MATCH, etag);
900 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
901 entitySearchIndex, entityId);
904 ArrayList<JsonNode> sourceObject = new ArrayList<>();
905 NodeUtils.extractObjectsByKey(
906 NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
907 "content", sourceObject);
909 if (!sourceObject.isEmpty()) {
910 JsonNode node = sourceObject.get(0);
911 final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
912 "crossEntityReferenceValues");
913 String newCer = aaiEventEntity.getCrossReferenceEntityValues();
914 boolean hasNewCer = true;
915 if (sourceCer != null && sourceCer.length() > 0){ // already has CER
916 if ( !sourceCer.contains(newCer)){//don't re-add
917 newCer = sourceCer + ";" + newCer;
924 // Do the PUT with new CER
925 ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
926 jsonPayload = NodeUtils.convertObjectToJson(node, false);
927 searchAgent.putDocument(entitySearchIndex, entityId, jsonPayload, headers);
932 if (storedEntity.getResultCode() == 404) {
933 // entity not found, so attempt to do a PUT
934 searchAgent.putDocument(entitySearchIndex, entityId, aaiEventEntity.getAsJson(), headers);
936 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
937 aaiEventEntity.getId(), "SYNC_ENTITY");
940 } catch (IOException e) {
941 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
942 aaiEventEntity.getId(), "SYNC_ENTITY");
947 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
950 * @param eventEntity Entity/data to use in operation
951 * @param action The operation to perform
952 * @param target Resource to perform the operation on
953 * @param allowDeleteEvent Allow delete operation to be performed on resource
955 protected void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity,
960 Map<String, List<String>> headers = new HashMap<>();
961 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
962 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
964 String entityId = eventEntity.getId();
966 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
967 || action.equalsIgnoreCase(ACTION_UPDATE)) {
969 // Run the GET to retrieve the ETAG from the search service
970 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
972 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
973 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
975 if (etag != null && !etag.isEmpty()) {
976 headers.put(Headers.IF_MATCH, etag);
978 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
983 // Write the entity to the search service.
985 searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
986 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
987 // Write the entry to the search service.
988 searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
990 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
991 // Run the GET to retrieve the ETAG from the search service
992 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
994 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
995 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
997 if (etag != null && !etag.isEmpty()) {
998 headers.put(Headers.IF_MATCH, etag);
1000 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1004 searchAgent.deleteDocument(index, eventEntity.getId(), headers);
1006 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1010 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1012 } catch (IOException e) {
1013 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1018 private void handleTopographicalData(String payload, String action, String entityType,
1019 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1020 String entityPrimaryKeyFieldValue) {
1022 Map<String, String> topoData = new HashMap<>();
1024 List<String> topographicalAttr =
1025 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1026 if (topographicalAttr == null) {
1027 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1028 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1029 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1030 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1033 entityLink = lookupValueUsingKey(payload, "entity-link");
1034 for (String topoAttr : topographicalAttr) {
1035 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1037 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1038 entityPrimaryKeyFieldValue, entityLink);
1043 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1044 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1046 TopographicalEntity topoEntity = new TopographicalEntity();
1047 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1048 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1049 topoEntity.setEntityType(entityType);
1050 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1051 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1052 topoEntity.setSelfLink(entityLink);
1054 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1055 entityPrimaryKeyValue));
1056 } catch (NoSuchAlgorithmException e) {
1057 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1058 "Cannot create unique SHA digest for topographical data.");
1061 this.handleSearchServiceOperation(topoEntity, action, topographicalSearchIndex);
1065 // put this here until we find a better spot
1067 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1069 * @param suburis the list of substrings to concatenate together
1070 * @return the concatenated list of substrings
1072 public static String concatSubUri(String... suburis) {
1073 String finalUri = "";
1075 for (String suburi : suburis) {
1077 if (suburi != null) {
1078 // Remove any leading / since we only want to append /
1079 suburi = suburi.replaceFirst("^/*", "");
1081 // Add a trailing / if one isn't already there
1082 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";