2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6 * Copyright © 2017 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 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
23 package org.onap.aai.datarouter.policy;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.security.NoSuchAlgorithmException;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
37 import org.apache.camel.Exchange;
38 import org.apache.camel.Processor;
39 import org.eclipse.persistence.dynamic.DynamicType;
40 import org.eclipse.persistence.internal.helper.DatabaseField;
41 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
42 import org.json.JSONException;
43 import org.json.JSONObject;
44 import org.onap.aai.datarouter.entity.AaiEventEntity;
45 import org.onap.aai.datarouter.entity.AggregationEntity;
46 import org.onap.aai.datarouter.entity.DocumentStoreDataEntity;
47 import org.onap.aai.datarouter.entity.OxmEntityDescriptor;
48 import org.onap.aai.datarouter.entity.SuggestionSearchEntity;
49 import org.onap.aai.datarouter.entity.TopographicalEntity;
50 import org.onap.aai.datarouter.entity.UebEventHeader;
51 import org.onap.aai.datarouter.logging.EntityEventPolicyMsgs;
52 import org.onap.aai.datarouter.util.CrossEntityReference;
53 import org.onap.aai.datarouter.util.EntityOxmReferenceHelper;
54 import org.onap.aai.datarouter.util.ExternalOxmModelProcessor;
55 import org.onap.aai.datarouter.util.NodeUtils;
56 import org.onap.aai.datarouter.util.OxmModelLoader;
57 import org.onap.aai.datarouter.util.RouterServiceUtil;
58 import org.onap.aai.datarouter.util.SearchServiceAgent;
59 import org.onap.aai.datarouter.util.SearchSuggestionPermutation;
60 import org.onap.aai.datarouter.util.Version;
61 import org.onap.aai.datarouter.util.VersionedOxmEntities;
62 import org.onap.aai.cl.api.Logger;
63 import org.onap.aai.cl.eelf.LoggerFactory;
64 import org.onap.aai.cl.mdc.MdcContext;
65 import org.onap.aai.restclient.client.Headers;
66 import org.onap.aai.restclient.client.OperationResult;
67 import org.onap.aai.restclient.rest.HttpUtil;
70 import com.fasterxml.jackson.core.JsonProcessingException;
71 import com.fasterxml.jackson.databind.JsonNode;
72 import com.fasterxml.jackson.databind.ObjectMapper;
73 import com.fasterxml.jackson.databind.ObjectWriter;
74 import com.fasterxml.jackson.databind.node.ObjectNode;
76 public class EntityEventPolicy implements Processor {
78 public static final String additionalInfo = "Response of AAIEntityEventPolicy";
79 private static final String entitySearchSchema = "entitysearch_schema.json";
80 private static final String topographicalSearchSchema = "topographysearch_schema.json";
81 private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
83 private final String EVENT_HEADER = "event-header";
84 private final String ENTITY_HEADER = "entity";
85 private final String ACTION_CREATE = "create";
86 private final static String ACTION_DELETE = "delete";
87 private final String ACTION_UPDATE = "update";
88 private final String PROCESS_AAI_EVENT = "Process AAI Event";
89 private final String TOPO_LAT = "latitude";
90 private final String TOPO_LONG = "longitude";
92 private final List<String> SUPPORTED_ACTIONS =
93 Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
95 Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
96 private String oxmVersion = null;
98 /** Agent for communicating with the Search Service. */
99 private SearchServiceAgent searchAgent = null;
101 /** Search index name for storing AAI event entities. */
102 private String entitySearchIndex;
104 /** Search index name for storing topographical search data. */
105 private String topographicalSearchIndex;
107 /** Search index name for suggestive search data. */
108 private String aggregateGenericVnfIndex;
110 private String autosuggestIndex;
112 private String srcDomain;
114 private Logger logger;
115 private Logger metricsLogger;
117 public enum ResponseType {
118 SUCCESS, PARTIAL_SUCCESS, FAILURE;
121 public EntityEventPolicy(EntityEventPolicyConfig config) throws FileNotFoundException {
122 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
123 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
124 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
127 srcDomain = config.getSourceDomain();
129 // Populate the index names.
130 entitySearchIndex = config.getSearchEntitySearchIndex();
131 topographicalSearchIndex = config.getSearchTopographySearchIndex();
132 aggregateGenericVnfIndex = config.getSearchAggregationVnfIndex();
133 autosuggestIndex = config.getSearchEntityAutoSuggestIndex();
135 // Instantiate the agent that we will use for interacting with the Search Service.
136 searchAgent = new SearchServiceAgent(config.getSearchCertName(),
137 config.getSearchKeystore(),
138 config.getSearchKeystorePwd(),
139 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
140 config.getSearchEndpoint()),
141 config.getSearchEndpointDocuments(),
144 this.externalOxmModelProcessors = new ArrayList<>();
145 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
146 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
147 OxmModelLoader.loadModels();
148 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
149 parseLatestOxmVersion();
152 private void parseLatestOxmVersion() {
153 int latestVersion = -1;
154 if (oxmVersionContextMap != null) {
155 Iterator it = oxmVersionContextMap.entrySet().iterator();
156 while (it.hasNext()) {
157 Map.Entry pair = (Map.Entry) it.next();
159 String version = pair.getKey().toString();
160 int versionNum = Integer.parseInt(version.substring(1, version.length()));
162 if (versionNum > latestVersion) {
163 latestVersion = versionNum;
164 oxmVersion = pair.getKey().toString();
167 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
170 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
174 public void startup() {
176 // Create the indexes in the search service if they do not already exist.
177 searchAgent.createSearchIndex(entitySearchIndex, entitySearchSchema);
178 searchAgent.createSearchIndex(topographicalSearchIndex, topographicalSearchSchema);
180 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
185 * Convert object to json.
187 * @param object the object
188 * @param pretty the pretty
190 * @throws JsonProcessingException the json processing exception
192 public static String convertObjectToJson(Object object, boolean pretty)
193 throws JsonProcessingException {
197 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
200 ow = new ObjectMapper().writer();
203 return ow.writeValueAsString(object);
206 public void returnWithError(Exchange exchange, String payload, String errorMsg){
207 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE, errorMsg);
208 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, errorMsg, payload);
209 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
213 public void process(Exchange exchange) throws Exception {
215 long startTime = System.currentTimeMillis();
217 String uebPayload = exchange.getIn().getBody().toString();
219 JsonNode uebAsJson =null;
220 ObjectMapper mapper = new ObjectMapper();
222 uebAsJson = mapper.readTree(uebPayload);
223 } catch (IOException e){
224 returnWithError(exchange, uebPayload, "Invalid Payload");
228 // Load the UEB payload data, any errors will result in a failure and discard
229 JSONObject uebObjHeader = getUebContentAsJson(uebPayload, EVENT_HEADER);
230 if (uebObjHeader == null) {
231 returnWithError(exchange, uebPayload, "Payload is missing " + EVENT_HEADER);
235 JSONObject uebObjEntity = getUebContentAsJson(uebPayload, ENTITY_HEADER);
236 if (uebObjEntity == null) {
237 returnWithError(exchange, uebPayload, "Payload is missing " + ENTITY_HEADER);
241 UebEventHeader eventHeader;
242 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
244 // Get src domain from header; discard event if not originated from same domain
245 String payloadSrcDomain = eventHeader.getDomain();
246 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
247 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
248 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
249 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
250 "Unrecognized source domain '" + payloadSrcDomain + "'");
252 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
256 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
257 if (oxmJaxbContext == null) {
258 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
259 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE, "OXM version mismatch",
262 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
266 String action = eventHeader.getAction();
267 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
268 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
269 "Unrecognized action '" + action + "'", uebPayload);
270 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
271 "Unrecognized action '" + action + "'");
273 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
277 String entityType = eventHeader.getEntityType();
278 if (entityType == null) {
279 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
280 "Payload header missing entity type", uebPayload);
281 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
282 "Payload header missing entity type");
284 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
288 String topEntityType = eventHeader.getTopEntityType();
289 if (topEntityType == null) {
290 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
291 "Payload header missing top entity type", uebPayload);
292 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
293 "Payload header top missing entity type");
295 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
299 String entityLink = eventHeader.getEntityLink();
300 if (entityLink == null) {
301 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
302 "Payload header missing entity link", uebPayload);
303 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
304 "Payload header missing entity link");
306 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
310 // log the fact that all data are in good shape
311 logger.info(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_NONVERBOSE, action,
313 logger.debug(EntityEventPolicyMsgs.PROCESS_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
317 // Process for building AaiEventEntity object
318 String[] entityTypeArr = entityType.split("-");
319 String oxmEntityType = "";
320 for (String entityWord : entityTypeArr) {
321 oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
324 List<String> searchableAttr =
325 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
326 if (searchableAttr == null) {
327 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
328 "Searchable attribute not found for payload entity type '" + entityType + "'");
329 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
330 "Searchable attribute not found for payload entity type '" + entityType + "'",
333 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
337 String entityPrimaryKeyFieldName =
338 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
339 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
340 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
341 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
342 "Payload missing primary key attribute");
343 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
344 "Payload missing primary key attribute", uebPayload);
346 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
350 AaiEventEntity aaiEventEntity = new AaiEventEntity();
353 * Use the OXM Model to determine the primary key field name based on the entity-type
356 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
357 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
358 aaiEventEntity.setEntityType(entityType);
359 aaiEventEntity.setLink(entityLink);
361 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
362 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
363 "Payload missing searchable attribute for entity type '" + entityType + "'");
364 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
365 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
367 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
373 aaiEventEntity.deriveFields();
375 } catch (NoSuchAlgorithmException e) {
376 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
377 "Cannot create unique SHA digest");
378 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
379 "Cannot create unique SHA digest", uebPayload);
381 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
385 handleSearchServiceOperation(aaiEventEntity, action, entitySearchIndex);
387 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
388 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
391 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
393 VersionedOxmEntities oxmEntities =
394 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion));
398 * 1. If the entity type is "customer", the below check will return true if any nested entityType
399 * in that model could contain a CER based on the OXM model version that has been loaded.
400 * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
401 * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
402 * assumption that AAI will push down all required cascade-deletes for nested entities as well
403 * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
404 * CREATE service-instance.
407 if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
408 && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
410 // We know the model "can" contain a CER reference definition, let's process a bit more
412 HashMap<String, CrossEntityReference> crossEntityRefMap =
413 oxmEntities.getCrossEntityReferences();
415 JSONObject entityJsonObject = getUebEntity(uebPayload);
417 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
419 String parentEntityType = entityType;
421 String targetEntityUrl = entityLink;
423 for (Map.Entry<String, CrossEntityReference> entry : crossEntityRefMap.entrySet()) {
426 * if we know service-subscription is in the tree, then we can pull our all instances and
427 * process from there.
430 String key = entry.getKey();
431 CrossEntityReference cerDescriptor = entry.getValue();
433 ArrayList<JsonNode> foundNodes = new ArrayList<>();
435 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
437 if (!foundNodes.isEmpty()) {
439 for (JsonNode n : foundNodes) {
440 if ("customer".equalsIgnoreCase(parentEntityType)){
443 * 1. prepare to hand-create url for service-instance
444 * 2. this will break if the URL structure for service-instance changes
446 if (n.has("service-type")){
447 targetEntityUrl += "/service-subscriptions/service-subscription/"
448 + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
449 + "/service-instances/service-instance/";
454 List<String> extractedParentEntityAttributeValues = new ArrayList<>();
456 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
457 extractedParentEntityAttributeValues);
459 List<JsonNode> nestedTargetEntityInstances = new ArrayList<>();
460 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
461 nestedTargetEntityInstances);
463 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
466 * 1. build the AAIEntityType (IndexDocument) based on the extract entity
467 * 2. Get data from ES
469 * 4. Merge ES Doc + AAIEntityType + Extracted Parent Cross-Entity-Reference Values
470 * 5. Put data into ES with ETAG + updated doc
473 // Get the complete URL for target entity
474 if (targetEntityInstance.has("link")) { // nested SI has url mentioned
475 targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
477 } else if ("customer".equalsIgnoreCase(parentEntityType) &&
478 targetEntityInstance.has("service-instance-id")){
479 targetEntityUrl += RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
480 "service-instance-id");
483 OxmEntityDescriptor searchableDescriptor =
484 oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
486 if (searchableDescriptor != null) {
488 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
490 AaiEventEntity entityToSync = null;
494 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
497 * Ready to do some ElasticSearch ops
500 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
502 .addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
505 entityToSync.setLink(targetEntityUrl);
506 entityToSync.deriveFields();
508 updateCerInEntity(entityToSync);
510 } catch (NoSuchAlgorithmException e) {
515 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
516 "failure to find searchable descriptor for type "
517 + cerDescriptor.getTargetEntityType());
524 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
525 "failed to find 0 instances of cross-entity-reference with entity " + key);
531 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
532 + topEntityType + " does not contain a cross-entity-reference entity");
536 * Process for autosuggestable entities
538 if (oxmEntities != null) {
539 Map<String, OxmEntityDescriptor> rootDescriptor =
540 oxmEntities.getSuggestableEntityDescriptors();
541 if (!rootDescriptor.isEmpty()) {
542 List<String> suggestibleAttrInPayload = new ArrayList<>();
543 List<String> suggestibleAttrInOxm = extractSuggestableAttr(oxmEntities, entityType);
544 if (suggestibleAttrInOxm != null) {
545 for (String attr: suggestibleAttrInOxm){
546 if ( uebObjEntity.has(attr) ){
547 suggestibleAttrInPayload.add(attr);
552 if (suggestibleAttrInPayload.isEmpty()) {
556 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
557 AggregationEntity ae = new AggregationEntity();
558 ae.setLink(entityLink);
559 ae.deriveFields(uebAsJson);
561 handleSearchServiceOperation(ae, action, aggregateGenericVnfIndex);
564 * It was decided to silently ignore DELETE requests for resources we don't allow to be
565 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
567 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
568 List<ArrayList<String>> listOfValidPowerSetElements =
569 SearchSuggestionPermutation.getNonEmptyUniqueLists(suggestibleAttrInPayload);
571 // Now we have a list containing the power-set (minus empty element) for the status that are
572 // available in the payload. Try inserting a document for every combination.
573 for (ArrayList<String> list : listOfValidPowerSetElements) {
574 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
575 suggestionSearchEntity.setEntityType(entityType);
576 suggestionSearchEntity.setSuggestableAttr(list);
577 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
578 suggestionSearchEntity.setFilterBasedPayloadFromResponse(uebAsJson.get("entity"),
579 suggestibleAttrInOxm, list);
580 suggestionSearchEntity.setSuggestionInputPermutations(
581 suggestionSearchEntity.generateSuggestionInputPermutations());
583 if (suggestionSearchEntity.isSuggestableDoc()) {
585 suggestionSearchEntity.generateSearchSuggestionDisplayStringAndId();
586 } catch (NoSuchAlgorithmException e) {
587 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
588 "Cannot create unique SHA digest for search suggestion data. Exception: "
589 + e.getLocalizedMessage());
592 handleSearchServiceOperation(suggestionSearchEntity, action, autosuggestIndex);
599 long stopTime = System.currentTimeMillis();
601 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
602 String.valueOf(stopTime - startTime));
604 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
608 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
609 // Extract suggestable attributeshandleTopographicalData
610 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
612 if (rootDescriptor == null) {
613 return Collections.emptyList();
616 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
619 return Collections.emptyList();
622 return desc.getSuggestableAttributes();
625 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
629 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
631 if (rootDescriptor == null) {
632 return Collections.emptyList();
635 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
636 return desc.getAlias();
639 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
641 exchange.getOut().setHeader("ResponseType", responseType.toString());
642 exchange.getOut().setBody(additionalInfo);
645 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
646 List<String> suggestableAttr, List<String> alias) {
648 // Extract suggestable attributes
649 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
651 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
652 suggestableAttr = desc.getSuggestableAttributes();
655 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
656 desc = rootDescriptor.get(entityType);
657 alias = desc.getAlias();
661 * Load the UEB JSON payload, any errors would result to a failure case response.
663 private JSONObject getUebContentAsJson(String payload, String contentKey) {
665 JSONObject uebJsonObj;
666 JSONObject uebObjContent;
669 uebJsonObj = new JSONObject(payload);
670 } catch (JSONException e) {
671 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
672 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
676 if (uebJsonObj.has(contentKey)) {
677 uebObjContent = uebJsonObj.getJSONObject(contentKey);
679 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
680 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
684 return uebObjContent;
688 private UebEventHeader initializeUebEventHeader(String payload) {
690 UebEventHeader eventHeader = null;
691 ObjectMapper mapper = new ObjectMapper();
693 // Make sure that were were actually passed in a valid string.
694 if (payload == null || payload.isEmpty()) {
695 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
696 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
701 // Marshal the supplied string into a UebEventHeader object.
703 eventHeader = mapper.readValue(payload, UebEventHeader.class);
704 } catch (JsonProcessingException e) {
705 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
706 } catch (Exception e) {
707 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
710 if (eventHeader != null) {
711 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
719 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
720 String oxmEntityType, String entityType) {
722 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
723 if (entity == null) {
727 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
728 if (list != null && !list.isEmpty()) {
729 String keyName = list.get(0).getName();
730 return keyName.substring(0, keyName.indexOf('/'));
736 private String lookupValueUsingKey(String payload, String key) throws JSONException {
737 JsonNode jsonNode = convertToJsonNode(payload);
738 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
741 private JsonNode convertToJsonNode(String payload) {
743 ObjectMapper mapper = new ObjectMapper();
744 JsonNode jsonNode = null;
746 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
747 } catch (IOException e) {
748 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
750 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
757 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
758 String payload, String action) {
760 boolean hasSearchableAttr = false;
761 for (String searchTagField : searchableAttr) {
762 String searchTagValue;
763 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
764 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
766 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
769 if (searchTagValue != null && !searchTagValue.isEmpty()) {
770 hasSearchableAttr = true;
771 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
774 return hasSearchableAttr;
778 * Check if OXM version is available. If available, load it.
780 private DynamicJAXBContext loadOxmContext(String version) {
781 if (version == null) {
782 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
786 return oxmVersionContextMap.get(version);
789 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
790 String oxmEntityType, String entityType, String fieldName) {
792 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
793 if (entity == null) {
798 * Check for searchable XML tag
800 List<String> fieldValues = null;
801 Map<String, String> properties = entity.getDescriptor().getProperties();
802 for (Map.Entry<String, String> entry : properties.entrySet()) {
803 if (entry.getKey().equalsIgnoreCase(fieldName)) {
804 fieldValues = Arrays.asList(entry.getValue().split(","));
812 private JSONObject getUebEntity(String payload) {
813 JSONObject uebJsonObj;
816 uebJsonObj = new JSONObject(payload);
817 } catch (JSONException e) {
818 logger.debug(EntityEventPolicyMsgs.DISCARD_EVENT_VERBOSE,
819 "Payload has invalid JSON Format", payload.toString());
820 logger.error(EntityEventPolicyMsgs.DISCARD_EVENT_NONVERBOSE,
821 "Payload has invalid JSON Format");
825 if (uebJsonObj.has(ENTITY_HEADER)) {
826 return uebJsonObj.getJSONObject(ENTITY_HEADER);
828 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
830 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
835 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
836 OxmEntityDescriptor resultDescriptor) {
837 AaiEventEntity d = new AaiEventEntity();
839 d.setEntityType(resultDescriptor.getEntityName());
841 List<String> primaryKeyValues = new ArrayList<>();
842 List<String> primaryKeyNames = new ArrayList<>();
845 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
846 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
847 if (pkeyValue != null) {
848 primaryKeyValues.add(pkeyValue);
849 primaryKeyNames.add(keyName);
851 // logger.warn("getPopulatedDocument(), pKeyValue is null for entityType = " +
852 // resultDescriptor.getEntityName());
853 logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
854 resultDescriptor.getEntityName());
858 final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
859 d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
860 final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
861 d.setEntityPrimaryKeyName(primaryCompositeKeyName);
863 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
866 * Based on configuration, use the configured field names for this entity-Type to build a
867 * multi-value collection of search tags for elastic search entity search criteria.
871 for (String searchTagField : searchTagFields) {
872 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
873 if (searchTagValue != null && !searchTagValue.isEmpty()) {
874 d.addSearchTagWithKey(searchTagValue, searchTagField);
881 private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
883 Map<String, List<String>> headers = new HashMap<>();
884 headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
885 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
887 String entityId = aaiEventEntity.getId();
890 // Run the GET to retrieve the ETAG from the search service
891 OperationResult storedEntity = searchAgent.getDocument(entitySearchIndex, entityId);
893 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
895 * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
896 * the pre-existing object,
897 * so all we want to do is update the CER on the pre-existing object (if needed).
900 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
902 if (etag != null && !etag.isEmpty()) {
903 headers.put(Headers.IF_MATCH, etag);
905 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
906 entitySearchIndex, entityId);
909 ArrayList<JsonNode> sourceObject = new ArrayList<>();
910 NodeUtils.extractObjectsByKey(
911 NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
912 "content", sourceObject);
914 if (!sourceObject.isEmpty()) {
915 JsonNode node = sourceObject.get(0);
916 final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
917 "crossEntityReferenceValues");
918 String newCer = aaiEventEntity.getCrossReferenceEntityValues();
919 boolean hasNewCer = true;
920 if (sourceCer != null && sourceCer.length() > 0){ // already has CER
921 if ( !sourceCer.contains(newCer)){//don't re-add
922 newCer = sourceCer + ";" + newCer;
929 // Do the PUT with new CER
930 ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
931 jsonPayload = NodeUtils.convertObjectToJson(node, false);
932 searchAgent.putDocument(entitySearchIndex, entityId, jsonPayload, headers);
937 if (storedEntity.getResultCode() == 404) {
938 // entity not found, so attempt to do a PUT
939 searchAgent.putDocument(entitySearchIndex, entityId, aaiEventEntity.getAsJson(), headers);
941 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
942 aaiEventEntity.getId(), "SYNC_ENTITY");
945 } catch (IOException e) {
946 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
947 aaiEventEntity.getId(), "SYNC_ENTITY");
952 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
955 * @param eventEntity Entity/data to use in operation
956 * @param action The operation to perform
957 * @param target Resource to perform the operation on
958 * @param allowDeleteEvent Allow delete operation to be performed on resource
960 protected void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity,
965 Map<String, List<String>> headers = new HashMap<>();
966 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
967 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
969 String entityId = eventEntity.getId();
971 // System.out.println("aaiEventEntity as json = " + aaiEventEntity.getAsJson());
973 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
974 || action.equalsIgnoreCase(ACTION_UPDATE)) {
976 // Run the GET to retrieve the ETAG from the search service
977 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
979 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
980 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
982 if (etag != null && !etag.isEmpty()) {
983 headers.put(Headers.IF_MATCH, etag);
985 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
990 // Write the entity to the search service.
992 searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
993 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
994 // Write the entry to the search service.
995 searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
997 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
998 // Run the GET to retrieve the ETAG from the search service
999 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
1001 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1002 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1004 if (etag != null && !etag.isEmpty()) {
1005 headers.put(Headers.IF_MATCH, etag);
1007 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1011 searchAgent.deleteDocument(index, eventEntity.getId(), headers);
1013 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1017 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1019 } catch (IOException e) {
1020 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1025 private void handleTopographicalData(String payload, String action, String entityType,
1026 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1027 String entityPrimaryKeyFieldValue) {
1029 Map<String, String> topoData = new HashMap<>();
1031 List<String> topographicalAttr =
1032 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1033 if (topographicalAttr == null) {
1034 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1035 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1036 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1037 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1040 entityLink = lookupValueUsingKey(payload, "entity-link");
1041 for (String topoAttr : topographicalAttr) {
1042 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1044 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1045 entityPrimaryKeyFieldValue, entityLink);
1050 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1051 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1053 TopographicalEntity topoEntity = new TopographicalEntity();
1054 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1055 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1056 topoEntity.setEntityType(entityType);
1057 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1058 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1059 topoEntity.setSelfLink(entityLink);
1061 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1062 entityPrimaryKeyValue));
1063 } catch (NoSuchAlgorithmException e) {
1064 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1065 "Cannot create unique SHA digest for topographical data.");
1068 this.handleSearchServiceOperation(topoEntity, action, topographicalSearchIndex);
1072 // put this here until we find a better spot
1074 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1076 * @param suburis the list of substrings to concatenate together
1077 * @return the concatenated list of substrings
1079 public static String concatSubUri(String... suburis) {
1080 String finalUri = "";
1082 for (String suburi : suburis) {
1084 if (suburi != null) {
1085 // Remove any leading / since we only want to append /
1086 suburi = suburi.replaceFirst("^/*", "");
1088 // Add a trailing / if one isn't already there
1089 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";