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.openecomp.datarouter.policy;
25 import java.io.BufferedReader;
26 import java.io.IOException;
27 import java.io.InputStreamReader;
28 import java.nio.charset.StandardCharsets;
29 import java.security.NoSuchAlgorithmException;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
37 import java.util.UUID;
39 import javax.ws.rs.core.MediaType;
40 import javax.ws.rs.core.MultivaluedMap;
42 import org.apache.camel.Exchange;
43 import org.apache.camel.Processor;
44 import org.eclipse.jetty.util.security.Password;
45 import org.eclipse.persistence.dynamic.DynamicType;
46 import org.eclipse.persistence.internal.helper.DatabaseField;
47 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
48 import org.json.JSONException;
49 import org.json.JSONObject;
50 import org.openecomp.cl.api.Logger;
51 import org.openecomp.cl.eelf.LoggerFactory;
52 import org.openecomp.cl.mdc.MdcContext;
53 import org.openecomp.datarouter.entity.AaiEventEntity;
54 import org.openecomp.datarouter.entity.AggregationEntity;
55 import org.openecomp.datarouter.entity.DocumentStoreDataEntity;
56 import org.openecomp.datarouter.entity.OxmEntityDescriptor;
57 import org.openecomp.datarouter.entity.SuggestionSearchEntity;
58 import org.openecomp.datarouter.entity.TopographicalEntity;
59 import org.openecomp.datarouter.entity.UebEventHeader;
60 import org.openecomp.datarouter.logging.EntityEventPolicyMsgs;
61 import org.openecomp.datarouter.util.CrossEntityReference;
62 import org.openecomp.datarouter.util.DataRouterConstants;
63 import org.openecomp.datarouter.util.EntityOxmReferenceHelper;
64 import org.openecomp.datarouter.util.ExternalOxmModelProcessor;
65 import org.openecomp.datarouter.util.NodeUtils;
66 import org.openecomp.datarouter.util.OxmModelLoader;
67 import org.openecomp.datarouter.util.RouterServiceUtil;
68 import org.openecomp.datarouter.util.SearchServiceAgent;
69 import org.openecomp.datarouter.util.SearchSuggestionPermutation;
70 import org.openecomp.datarouter.util.Version;
71 import org.openecomp.datarouter.util.VersionedOxmEntities;
72 import org.openecomp.restclient.client.Headers;
73 import org.openecomp.restclient.client.OperationResult;
74 import org.openecomp.restclient.client.RestClient;
75 import org.openecomp.restclient.enums.RestAuthenticationMode;
76 import org.openecomp.restclient.rest.HttpUtil;
79 import com.fasterxml.jackson.core.JsonProcessingException;
80 import com.fasterxml.jackson.databind.JsonNode;
81 import com.fasterxml.jackson.databind.ObjectMapper;
82 import com.fasterxml.jackson.databind.ObjectWriter;
83 import com.fasterxml.jackson.databind.node.ObjectNode;
84 import com.sun.jersey.core.util.MultivaluedMapImpl;
86 public class EntityEventPolicy implements Processor {
88 public static final String additionalInfo = "Response of AAIEntityEventPolicy";
89 private static final String entitySearchSchema = "entitysearch_schema.json";
90 private static final String topographicalSearchSchema = "topographysearch_schema.json";
91 private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
93 private final String EVENT_HEADER = "event-header";
94 private final String ENTITY_HEADER = "entity";
95 private final String ACTION_CREATE = "create";
96 private final String ACTION_DELETE = "delete";
97 private final String ACTION_UPDATE = "update";
98 private final String PROCESS_AAI_EVENT = "Process AAI Event";
99 private final String TOPO_LAT = "latitude";
100 private final String TOPO_LONG = "longitude";
102 private final List<String> SUPPORTED_ACTIONS =
103 Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
105 Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
106 private String oxmVersion = null;
108 /** Agent for communicating with the Search Service. */
109 private SearchServiceAgent searchAgent = null;
111 /** Search index name for storing AAI event entities. */
112 private String entitySearchIndex;
114 /** Search index name for storing topographical search data. */
115 private String topographicalSearchIndex;
117 /** Search index name for suggestive search data. */
118 private String aggregateGenericVnfIndex;
120 private String entitySearchTarget = null;
121 private String topographicalSearchTarget = null;
122 private String autoSuggestSearchTarget = null;
123 private String aggregationSearchVnfTarget = null;
125 private String srcDomain;
127 private Logger logger;
128 private Logger metricsLogger;
129 private Logger auditLogger;
131 public enum ResponseType {
132 SUCCESS, PARTIAL_SUCCESS, FAILURE;
135 public EntityEventPolicy(EntityEventPolicyConfig config) {
136 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
137 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
138 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
139 auditLogger = loggerFactoryInstance.getAuditLogger(EntityEventPolicy.class.getName());
141 srcDomain = config.getSourceDomain();
143 // Populate the index names.
144 entitySearchIndex = config.getSearchEntitySearchIndex();
145 topographicalSearchIndex = config.getSearchTopographySearchIndex();
146 aggregateGenericVnfIndex = config.getSearchAggregationVnfIndex();
148 // Instantiate the agent that we will use for interacting with the Search Service.
149 searchAgent = new SearchServiceAgent(config.getSearchCertName(),
150 config.getSearchKeystore(),
151 config.getSearchKeystorePwd(),
152 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
153 config.getSearchEndpoint()),
154 config.getSearchEndpointDocuments(),
158 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
159 config.getSearchEntitySearchIndex(), config.getSearchEndpointDocuments());
161 topographicalSearchTarget = EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
162 config.getSearchEndpoint(), config.getSearchTopographySearchIndex());
164 autoSuggestSearchTarget =
165 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
166 config.getSearchEntityAutoSuggestIndex(), config.getSearchEndpointDocuments());
168 aggregationSearchVnfTarget =
169 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
170 config.getSearchAggregationVnfIndex(), config.getSearchEndpointDocuments());
172 this.externalOxmModelProcessors = new ArrayList<ExternalOxmModelProcessor>();
173 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
174 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
175 OxmModelLoader.loadModels();
176 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
177 parseLatestOxmVersion();
180 private void parseLatestOxmVersion() {
181 int latestVersion = -1;
182 if (oxmVersionContextMap != null) {
183 Iterator it = oxmVersionContextMap.entrySet().iterator();
184 while (it.hasNext()) {
185 Map.Entry pair = (Map.Entry) it.next();
187 String version = pair.getKey().toString();
188 int versionNum = Integer.parseInt(version.substring(1, version.length()));
190 if (versionNum > latestVersion) {
191 latestVersion = versionNum;
192 oxmVersion = pair.getKey().toString();
195 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
198 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
202 public void startup() {
204 // Create the indexes in the search service if they do not already exist.
205 searchAgent.createSearchIndex(entitySearchIndex, entitySearchSchema);
206 searchAgent.createSearchIndex(topographicalSearchIndex, topographicalSearchSchema);
208 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
213 * Convert object to json.
215 * @param object the object
216 * @param pretty the pretty
218 * @throws JsonProcessingException the json processing exception
220 public static String convertObjectToJson(Object object, boolean pretty)
221 throws JsonProcessingException {
222 ObjectWriter ow = null;
225 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
228 ow = new ObjectMapper().writer();
231 return ow.writeValueAsString(object);
234 public void returnWithError(Exchange exchange, String payload, String errorMsg){
235 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE, errorMsg);
236 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, errorMsg, payload);
237 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
241 public void process(Exchange exchange) throws Exception {
243 long startTime = System.currentTimeMillis();
245 String uebPayload = exchange.getIn().getBody().toString();
247 JsonNode uebAsJson =null;
248 ObjectMapper mapper = new ObjectMapper();
250 uebAsJson = mapper.readTree(uebPayload);
251 } catch (IOException e){
252 returnWithError(exchange, uebPayload, "Invalid Payload");
256 // Load the UEB payload data, any errors will result in a failure and discard
257 JSONObject uebObjHeader = getUebContentAsJson(uebPayload, EVENT_HEADER);
258 if (uebObjHeader == null) {
259 returnWithError(exchange, uebPayload, "Payload is missing " + EVENT_HEADER);
263 JSONObject uebObjEntity = getUebContentAsJson(uebPayload, ENTITY_HEADER);
264 if (uebObjEntity == null) {
265 returnWithError(exchange, uebPayload, "Payload is missing " + ENTITY_HEADER);
269 UebEventHeader eventHeader = null;
270 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
272 // Get src domain from header; discard event if not originated from same domain
273 String payloadSrcDomain = eventHeader.getDomain();
274 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
275 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
276 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
277 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
278 "Unrecognized source domain '" + payloadSrcDomain + "'");
280 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
284 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
285 if (oxmJaxbContext == null) {
286 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
287 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, "OXM version mismatch",
290 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
294 String action = eventHeader.getAction();
295 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
296 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
297 "Unrecognized action '" + action + "'", uebPayload);
298 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
299 "Unrecognized action '" + action + "'");
301 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
305 String entityType = eventHeader.getEntityType();
306 if (entityType == null) {
307 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
308 "Payload header missing entity type", uebPayload);
309 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
310 "Payload header missing entity type");
312 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
316 String topEntityType = eventHeader.getTopEntityType();
317 if (topEntityType == null) {
318 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
319 "Payload header missing top entity type", uebPayload);
320 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
321 "Payload header top missing entity type");
323 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
327 String entityLink = eventHeader.getEntityLink();
328 if (entityLink == null) {
329 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
330 "Payload header missing entity link", uebPayload);
331 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
332 "Payload header missing entity link");
334 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
338 // log the fact that all data are in good shape
339 logger.info(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_NONVERBOSE, action,
341 logger.debug(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
345 // Process for building AaiEventEntity object
346 String[] entityTypeArr = entityType.split("-");
347 String oxmEntityType = "";
348 for (String entityWord : entityTypeArr) {
349 oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
352 List<String> searchableAttr =
353 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
354 if (searchableAttr == null) {
355 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
356 "Searchable attribute not found for payload entity type '" + entityType + "'");
357 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
358 "Searchable attribute not found for payload entity type '" + entityType + "'",
361 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
365 String entityPrimaryKeyFieldName =
366 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
367 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
368 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
369 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
370 "Payload missing primary key attribute");
371 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
372 "Payload missing primary key attribute", uebPayload);
374 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
378 AaiEventEntity aaiEventEntity = new AaiEventEntity();
381 * Use the OXM Model to determine the primary key field name based on the entity-type
384 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
385 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
386 aaiEventEntity.setEntityType(entityType);
387 aaiEventEntity.setLink(entityLink);
389 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
390 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
391 "Payload missing searchable attribute for entity type '" + entityType + "'");
392 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
393 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
395 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
401 aaiEventEntity.deriveFields();
403 } catch (NoSuchAlgorithmException e) {
404 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
405 "Cannot create unique SHA digest");
406 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
407 "Cannot create unique SHA digest", uebPayload);
409 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
413 handleSearchServiceOperation(aaiEventEntity, action, entitySearchIndex);
415 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
416 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
419 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
421 VersionedOxmEntities oxmEntities =
422 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion));
426 * 1. If the entity type is "customer", the below check will return true if any nested entityType
427 * in that model could contain a CER based on the OXM model version that has been loaded.
428 * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
429 * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
430 * assumption that AAI will push down all required cascade-deletes for nested entities as well
431 * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
432 * CREATE service-instance.
435 if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
436 && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
438 // We know the model "can" contain a CER reference definition, let's process a bit more
440 HashMap<String, CrossEntityReference> crossEntityRefMap =
441 oxmEntities.getCrossEntityReferences();
443 JSONObject entityJsonObject = getUebEntity(uebPayload);
445 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
447 String parentEntityType = entityType;
449 String targetEntityUrl = entityLink;
451 for (String key : crossEntityRefMap.keySet()) {
454 * if we know service-subscription is in the tree, then we can pull our all instances and
455 * process from there.
458 CrossEntityReference cerDescriptor = crossEntityRefMap.get(key);
460 ArrayList<JsonNode> foundNodes = new ArrayList<JsonNode>();
462 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
464 if (foundNodes.size() > 0) {
466 for (JsonNode n : foundNodes) {
467 if (parentEntityType.equalsIgnoreCase("customer")){
470 * 1. prepare to hand-create url for service-instance
471 * 2. this will break if the URL structure for service-instance changes
473 if (n.has("service-type")){
474 targetEntityUrl += "/service-subscriptions/service-subscription/"
475 + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
476 + "/service-instances/service-instance/";
481 List<String> extractedParentEntityAttributeValues = new ArrayList<String>();
483 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
484 extractedParentEntityAttributeValues);
486 List<JsonNode> nestedTargetEntityInstances = new ArrayList<JsonNode>();
487 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
488 nestedTargetEntityInstances);
490 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
493 * 1. build the AAIEntityType (IndexDocument) based on the extract entity
494 * 2. Get data from ES
496 * 4. Merge ES Doc + AAIEntityType + Extracted Parent Cross-Entity-Reference Values
497 * 5. Put data into ES with ETAG + updated doc
500 // Get the complete URL for target entity
501 if (targetEntityInstance.has("link")) { // nested SI has url mentioned
502 targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
504 } else if (parentEntityType.equalsIgnoreCase("customer") &&
505 targetEntityInstance.has("service-instance-id")){
506 targetEntityUrl += "/" + RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
507 "service-instance-id");
510 OxmEntityDescriptor searchableDescriptor =
511 oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
513 if (searchableDescriptor != null) {
515 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
517 AaiEventEntity entityToSync = null;
521 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
524 * Ready to do some ElasticSearch ops
527 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
529 .addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
532 entityToSync.setLink(targetEntityUrl);
533 entityToSync.deriveFields();
535 updateCerInEntity(entityToSync);
537 } catch (NoSuchAlgorithmException e) {
542 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
543 "failure to find searchable descriptor for type "
544 + cerDescriptor.getTargetEntityType());
551 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
552 "failed to find 0 instances of cross-entity-reference with entity " + key);
558 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
559 + topEntityType + " does not contain a cross-entity-reference entity");
563 * Process for autosuggestable entities
565 if (oxmEntities != null) {
566 Map<String, OxmEntityDescriptor> rootDescriptor =
567 oxmEntities.getSuggestableEntityDescriptors();
568 if (!rootDescriptor.isEmpty()) {
569 List<String> suggestibleAttrInPayload = new ArrayList<String>();
570 List<String> suggestibleAttrInOxm = extractSuggestableAttr(oxmEntities, entityType);
571 if (suggestibleAttrInOxm != null) {
572 for (String attr: suggestibleAttrInOxm){
573 if ( uebObjEntity.has(attr) ){
574 suggestibleAttrInPayload.add(attr);
579 if (suggestibleAttrInPayload.isEmpty()) {
583 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
584 AggregationEntity ae = new AggregationEntity();
585 ae.setLink(entityLink);
586 ae.deriveFields(uebAsJson);
588 handleSearchServiceOperation(ae, action, this.aggregationSearchVnfTarget);
591 * It was decided to silently ignore DELETE requests for resources we don't allow to be
592 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
594 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
595 List<ArrayList<String>> listOfValidPowerSetElements =
596 SearchSuggestionPermutation.getNonEmptyUniqueLists(suggestibleAttrInPayload);
598 // Now we have a list containing the power-set (minus empty element) for the status that are
599 // available in the payload. Try inserting a document for every combination.
600 for (ArrayList<String> list : listOfValidPowerSetElements) {
601 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
602 suggestionSearchEntity.setEntityType(entityType);
603 suggestionSearchEntity.setSuggestableAttr(list);
604 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
605 suggestionSearchEntity.setFilterBasedPayloadFromResponse(uebAsJson.get("entity"),
606 suggestibleAttrInOxm, list);
607 suggestionSearchEntity.setSuggestionInputPermutations(
608 suggestionSearchEntity.generateSuggestionInputPermutations());
610 if (suggestionSearchEntity.isSuggestableDoc()) {
612 suggestionSearchEntity.generateSearchSuggestionDisplayStringAndId();
613 } catch (NoSuchAlgorithmException e) {
614 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
615 "Cannot create unique SHA digest for search suggestion data. Exception: "
616 + e.getLocalizedMessage());
619 handleSearchServiceOperation(suggestionSearchEntity, action,
620 this.autoSuggestSearchTarget);
627 long stopTime = System.currentTimeMillis();
629 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
630 String.valueOf(stopTime - startTime));
632 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
636 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
637 // Extract suggestable attributes
638 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
640 if (rootDescriptor == null) {
644 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
650 return desc.getSuggestableAttributes();
653 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
657 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
659 if (rootDescriptor == null) {
663 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
664 return desc.getAlias();
667 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
669 exchange.getOut().setHeader("ResponseType", responseType.toString());
670 exchange.getOut().setBody(additionalInfo);
673 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
674 List<String> suggestableAttr, List<String> alias) {
676 // Extract suggestable attributes
677 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
679 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
680 suggestableAttr = desc.getSuggestableAttributes();
683 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
684 desc = rootDescriptor.get(entityType);
685 alias = desc.getAlias();
689 * Load the UEB JSON payload, any errors would result to a failure case response.
691 private JSONObject getUebContentAsJson(String payload, String contentKey) {
693 JSONObject uebJsonObj;
694 JSONObject uebObjContent;
697 uebJsonObj = new JSONObject(payload);
698 } catch (JSONException e) {
699 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
700 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
704 if (uebJsonObj.has(contentKey)) {
705 uebObjContent = uebJsonObj.getJSONObject(contentKey);
707 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
708 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
712 return uebObjContent;
716 private UebEventHeader initializeUebEventHeader(String payload) {
718 UebEventHeader eventHeader = null;
719 ObjectMapper mapper = new ObjectMapper();
721 // Make sure that were were actually passed in a valid string.
722 if (payload == null || payload.isEmpty()) {
723 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
724 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
729 // Marshal the supplied string into a UebEventHeader object.
731 eventHeader = mapper.readValue(payload, UebEventHeader.class);
732 } catch (JsonProcessingException e) {
733 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
734 } catch (Exception e) {
735 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
738 if (eventHeader != null) {
739 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
747 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
748 String oxmEntityType, String entityType) {
750 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
751 if (entity == null) {
755 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
756 if (list != null && !list.isEmpty()) {
757 String keyName = list.get(0).getName();
758 return keyName.substring(0, keyName.indexOf('/'));
764 private String lookupValueUsingKey(String payload, String key) throws JSONException {
765 JsonNode jsonNode = convertToJsonNode(payload);
766 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
769 private JsonNode convertToJsonNode(String payload) {
771 ObjectMapper mapper = new ObjectMapper();
772 JsonNode jsonNode = null;
774 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
775 } catch (IOException e) {
776 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
778 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
785 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
786 String payload, String action) {
788 boolean hasSearchableAttr = false;
789 for (String searchTagField : searchableAttr) {
790 String searchTagValue = null;
791 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
792 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
794 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
797 if (searchTagValue != null && !searchTagValue.isEmpty()) {
798 hasSearchableAttr = true;
799 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
802 return hasSearchableAttr;
806 * Check if OXM version is available. If available, load it.
808 private DynamicJAXBContext loadOxmContext(String version) {
809 if (version == null) {
810 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
814 return oxmVersionContextMap.get(version);
817 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
818 String oxmEntityType, String entityType, String fieldName) {
820 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
821 if (entity == null) {
826 * Check for searchable XML tag
828 List<String> fieldValues = null;
829 Map<String, String> properties = entity.getDescriptor().getProperties();
830 for (Map.Entry<String, String> entry : properties.entrySet()) {
831 if (entry.getKey().equalsIgnoreCase(fieldName)) {
832 fieldValues = Arrays.asList(entry.getValue().split(","));
840 private JSONObject getUebEntity(String payload) {
841 JSONObject uebJsonObj;
844 uebJsonObj = new JSONObject(payload);
845 } catch (JSONException e) {
846 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
847 "Payload has invalid JSON Format", payload.toString());
848 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
849 "Payload has invalid JSON Format");
853 if (uebJsonObj.has(ENTITY_HEADER)) {
854 return uebJsonObj.getJSONObject(ENTITY_HEADER);
856 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
858 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
863 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
864 OxmEntityDescriptor resultDescriptor) {
865 AaiEventEntity d = new AaiEventEntity();
867 d.setEntityType(resultDescriptor.getEntityName());
869 List<String> primaryKeyValues = new ArrayList<String>();
870 List<String> primaryKeyNames = new ArrayList<String>();
871 String pkeyValue = null;
873 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
874 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
875 if (pkeyValue != null) {
876 primaryKeyValues.add(pkeyValue);
877 primaryKeyNames.add(keyName);
879 // logger.warn("getPopulatedDocument(), pKeyValue is null for entityType = " +
880 // resultDescriptor.getEntityName());
881 logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
882 resultDescriptor.getEntityName());
886 final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
887 d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
888 final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
889 d.setEntityPrimaryKeyName(primaryCompositeKeyName);
891 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
894 * Based on configuration, use the configured field names for this entity-Type to build a
895 * multi-value collection of search tags for elastic search entity search criteria.
899 for (String searchTagField : searchTagFields) {
900 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
901 if (searchTagValue != null && !searchTagValue.isEmpty()) {
902 d.addSearchTagWithKey(searchTagValue, searchTagField);
909 private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
911 Map<String, List<String>> headers = new HashMap<>();
912 headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
913 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
915 String entityId = aaiEventEntity.getId();
916 String jsonPayload = aaiEventEntity.getAsJson();
918 // Run the GET to retrieve the ETAG from the search service
919 OperationResult storedEntity = searchAgent.getDocument(entitySearchIndex, entityId);
921 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
923 * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
924 * the pre-existing object,
925 * so all we want to do is update the CER on the pre-existing object (if needed).
928 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
930 if (etag != null && etag.size() > 0) {
931 headers.put(Headers.IF_MATCH, etag);
933 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
934 entitySearchTarget + entityId, entityId);
937 ArrayList<JsonNode> sourceObject = new ArrayList<JsonNode>();
938 NodeUtils.extractObjectsByKey(
939 NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
940 "content", sourceObject);
942 if (!sourceObject.isEmpty()) {
943 JsonNode node = sourceObject.get(0);
944 final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
945 "crossEntityReferenceValues");
946 String newCer = aaiEventEntity.getCrossReferenceEntityValues();
947 boolean hasNewCer = true;
948 if (sourceCer != null && sourceCer.length() > 0){ // already has CER
949 if ( !sourceCer.contains(newCer)){//don't re-add
950 newCer = sourceCer + ";" + newCer;
957 // Do the PUT with new CER
958 ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
959 jsonPayload = NodeUtils.convertObjectToJson(node, false);
960 searchAgent.putDocument(entitySearchIndex, entityId, jsonPayload, headers);
965 if (storedEntity.getResultCode() == 404) {
966 // entity not found, so attempt to do a PUT
967 searchAgent.putDocument(entitySearchIndex, entityId, aaiEventEntity.getAsJson(), headers);
969 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
970 aaiEventEntity.getId(), "SYNC_ENTITY");
973 } catch (IOException e) {
974 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
975 aaiEventEntity.getId(), "SYNC_ENTITY");
980 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
983 * @param eventEntity Entity/data to use in operation
984 * @param action The operation to perform
985 * @param target Resource to perform the operation on
986 * @param allowDeleteEvent Allow delete operation to be performed on resource
988 private void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity,
993 Map<String, List<String>> headers = new HashMap<>();
994 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
995 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
997 String entityId = eventEntity.getId();
999 // System.out.println("aaiEventEntity as json = " + aaiEventEntity.getAsJson());
1001 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
1002 || action.equalsIgnoreCase(ACTION_UPDATE)) {
1004 // Run the GET to retrieve the ETAG from the search service
1005 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
1007 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1008 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1010 if (etag != null && etag.size() > 0) {
1011 headers.put(Headers.IF_MATCH, etag);
1013 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1018 // Write the entity to the search service.
1020 searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
1021 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
1022 // Write the entry to the search service.
1023 searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
1025 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1026 // Run the GET to retrieve the ETAG from the search service
1027 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
1029 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1030 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1032 if (etag != null && etag.size() > 0) {
1033 headers.put(Headers.IF_MATCH, etag);
1035 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1039 searchAgent.deleteDocument(index, eventEntity.getId(), headers);
1041 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1045 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1047 } catch (IOException e) {
1048 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1053 private void handleTopographicalData(String payload, String action, String entityType,
1054 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1055 String entityPrimaryKeyFieldValue) {
1057 Map<String, String> topoData = new HashMap<>();
1058 String entityLink = "";
1059 List<String> topographicalAttr =
1060 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1061 if (topographicalAttr == null) {
1062 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1063 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1064 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1065 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1066 payload.toString());
1068 entityLink = lookupValueUsingKey(payload, "entity-link");
1069 for (String topoAttr : topographicalAttr) {
1070 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1072 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1073 entityPrimaryKeyFieldValue, entityLink);
1078 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1079 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1081 TopographicalEntity topoEntity = new TopographicalEntity();
1082 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1083 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1084 topoEntity.setEntityType(entityType);
1085 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1086 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1087 topoEntity.setSelfLink(entityLink);
1089 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1090 entityPrimaryKeyValue));
1091 } catch (NoSuchAlgorithmException e) {
1092 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1093 "Cannot create unique SHA digest for topographical data.");
1096 this.handleSearchServiceOperation(topoEntity, action, this.topographicalSearchTarget);
1100 // put this here until we find a better spot
1102 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1104 * @param suburis the list of substrings to concatenate together
1105 * @return the concatenated list of substrings
1107 public static String concatSubUri(String... suburis) {
1108 String finalUri = "";
1110 for (String suburi : suburis) {
1112 if (suburi != null) {
1113 // Remove any leading / since we only want to append /
1114 suburi = suburi.replaceFirst("^/*", "");
1116 // Add a trailing / if one isn't already there
1117 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";