2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017 AT&T Intellectual Property.
6 * Copyright © 2017 Amdocs
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
22 * ECOMP and OpenECOMP are trademarks
23 * and service marks of AT&T Intellectual Property.
25 package org.openecomp.datarouter.policy;
27 import java.io.BufferedReader;
28 import java.io.FileNotFoundException;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.nio.charset.StandardCharsets;
32 import java.security.NoSuchAlgorithmException;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
40 import java.util.UUID;
42 import javax.ws.rs.core.MediaType;
43 import javax.ws.rs.core.MultivaluedMap;
45 import org.apache.camel.Exchange;
46 import org.apache.camel.Processor;
47 import org.eclipse.jetty.util.security.Password;
48 import org.eclipse.persistence.dynamic.DynamicType;
49 import org.eclipse.persistence.internal.helper.DatabaseField;
50 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
51 import org.json.JSONException;
52 import org.json.JSONObject;
53 import org.openecomp.cl.api.Logger;
54 import org.openecomp.cl.eelf.LoggerFactory;
55 import org.openecomp.cl.mdc.MdcContext;
56 import org.openecomp.datarouter.entity.AaiEventEntity;
57 import org.openecomp.datarouter.entity.AggregationEntity;
58 import org.openecomp.datarouter.entity.DocumentStoreDataEntity;
59 import org.openecomp.datarouter.entity.OxmEntityDescriptor;
60 import org.openecomp.datarouter.entity.SuggestionSearchEntity;
61 import org.openecomp.datarouter.entity.TopographicalEntity;
62 import org.openecomp.datarouter.entity.UebEventHeader;
63 import org.openecomp.datarouter.logging.EntityEventPolicyMsgs;
64 import org.openecomp.datarouter.util.CrossEntityReference;
65 import org.openecomp.datarouter.util.DataRouterConstants;
66 import org.openecomp.datarouter.util.EntityOxmReferenceHelper;
67 import org.openecomp.datarouter.util.ExternalOxmModelProcessor;
68 import org.openecomp.datarouter.util.NodeUtils;
69 import org.openecomp.datarouter.util.OxmModelLoader;
70 import org.openecomp.datarouter.util.RouterServiceUtil;
71 import org.openecomp.datarouter.util.SearchServiceAgent;
72 import org.openecomp.datarouter.util.SearchSuggestionPermutation;
73 import org.openecomp.datarouter.util.Version;
74 import org.openecomp.datarouter.util.VersionedOxmEntities;
75 import org.openecomp.restclient.client.Headers;
76 import org.openecomp.restclient.client.OperationResult;
77 import org.openecomp.restclient.client.RestClient;
78 import org.openecomp.restclient.enums.RestAuthenticationMode;
79 import org.openecomp.restclient.rest.HttpUtil;
82 import com.fasterxml.jackson.core.JsonProcessingException;
83 import com.fasterxml.jackson.databind.JsonNode;
84 import com.fasterxml.jackson.databind.ObjectMapper;
85 import com.fasterxml.jackson.databind.ObjectWriter;
86 import com.fasterxml.jackson.databind.node.ObjectNode;
87 import com.sun.jersey.core.util.MultivaluedMapImpl;
89 public class EntityEventPolicy implements Processor {
91 public static final String additionalInfo = "Response of AAIEntityEventPolicy";
92 private static final String entitySearchSchema = "entitysearch_schema.json";
93 private static final String topographicalSearchSchema = "topographysearch_schema.json";
94 private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
96 private final String EVENT_HEADER = "event-header";
97 private final String ENTITY_HEADER = "entity";
98 private final String ACTION_CREATE = "create";
99 private final String ACTION_DELETE = "delete";
100 private final String ACTION_UPDATE = "update";
101 private final String PROCESS_AAI_EVENT = "Process AAI Event";
102 private final String TOPO_LAT = "latitude";
103 private final String TOPO_LONG = "longitude";
105 private final List<String> SUPPORTED_ACTIONS =
106 Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
108 Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
109 private String oxmVersion = null;
111 /** Agent for communicating with the Search Service. */
112 private SearchServiceAgent searchAgent = null;
114 /** Search index name for storing AAI event entities. */
115 private String entitySearchIndex;
117 /** Search index name for storing topographical search data. */
118 private String topographicalSearchIndex;
120 /** Search index name for suggestive search data. */
121 private String aggregateGenericVnfIndex;
123 private String entitySearchTarget = null;
124 private String topographicalSearchTarget = null;
125 private String autoSuggestSearchTarget = null;
126 private String aggregationSearchVnfTarget = null;
128 private String srcDomain;
130 private Logger logger;
131 private Logger metricsLogger;
132 private Logger auditLogger;
134 public enum ResponseType {
135 SUCCESS, PARTIAL_SUCCESS, FAILURE;
138 public EntityEventPolicy(EntityEventPolicyConfig config) throws FileNotFoundException {
139 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
140 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
141 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
142 auditLogger = loggerFactoryInstance.getAuditLogger(EntityEventPolicy.class.getName());
144 srcDomain = config.getSourceDomain();
146 // Populate the index names.
147 entitySearchIndex = config.getSearchEntitySearchIndex();
148 topographicalSearchIndex = config.getSearchTopographySearchIndex();
149 aggregateGenericVnfIndex = config.getSearchAggregationVnfIndex();
151 // Instantiate the agent that we will use for interacting with the Search Service.
152 searchAgent = new SearchServiceAgent(config.getSearchCertName(),
153 config.getSearchKeystore(),
154 config.getSearchKeystorePwd(),
155 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
156 config.getSearchEndpoint()),
157 config.getSearchEndpointDocuments(),
161 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
162 config.getSearchEntitySearchIndex(), config.getSearchEndpointDocuments());
164 topographicalSearchTarget = EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
165 config.getSearchEndpoint(), config.getSearchTopographySearchIndex());
167 autoSuggestSearchTarget =
168 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
169 config.getSearchEntityAutoSuggestIndex(), config.getSearchEndpointDocuments());
171 aggregationSearchVnfTarget =
172 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
173 config.getSearchAggregationVnfIndex(), config.getSearchEndpointDocuments());
175 this.externalOxmModelProcessors = new ArrayList<ExternalOxmModelProcessor>();
176 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
177 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
178 OxmModelLoader.loadModels();
179 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
180 parseLatestOxmVersion();
183 private void parseLatestOxmVersion() {
184 int latestVersion = -1;
185 if (oxmVersionContextMap != null) {
186 Iterator it = oxmVersionContextMap.entrySet().iterator();
187 while (it.hasNext()) {
188 Map.Entry pair = (Map.Entry) it.next();
190 String version = pair.getKey().toString();
191 int versionNum = Integer.parseInt(version.substring(1, version.length()));
193 if (versionNum > latestVersion) {
194 latestVersion = versionNum;
195 oxmVersion = pair.getKey().toString();
198 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
201 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
205 public void startup() {
207 // Create the indexes in the search service if they do not already exist.
208 searchAgent.createSearchIndex(entitySearchIndex, entitySearchSchema);
209 searchAgent.createSearchIndex(topographicalSearchIndex, topographicalSearchSchema);
211 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
216 * Convert object to json.
218 * @param object the object
219 * @param pretty the pretty
221 * @throws JsonProcessingException the json processing exception
223 public static String convertObjectToJson(Object object, boolean pretty)
224 throws JsonProcessingException {
225 ObjectWriter ow = null;
228 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
231 ow = new ObjectMapper().writer();
234 return ow.writeValueAsString(object);
237 public void returnWithError(Exchange exchange, String payload, String errorMsg){
238 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE, errorMsg);
239 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, errorMsg, payload);
240 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
244 public void process(Exchange exchange) throws Exception {
246 long startTime = System.currentTimeMillis();
248 String uebPayload = exchange.getIn().getBody().toString();
250 JsonNode uebAsJson =null;
251 ObjectMapper mapper = new ObjectMapper();
253 uebAsJson = mapper.readTree(uebPayload);
254 } catch (IOException e){
255 returnWithError(exchange, uebPayload, "Invalid Payload");
259 // Load the UEB payload data, any errors will result in a failure and discard
260 JSONObject uebObjHeader = getUebContentAsJson(uebPayload, EVENT_HEADER);
261 if (uebObjHeader == null) {
262 returnWithError(exchange, uebPayload, "Payload is missing " + EVENT_HEADER);
266 JSONObject uebObjEntity = getUebContentAsJson(uebPayload, ENTITY_HEADER);
267 if (uebObjEntity == null) {
268 returnWithError(exchange, uebPayload, "Payload is missing " + ENTITY_HEADER);
272 UebEventHeader eventHeader = null;
273 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
275 // Get src domain from header; discard event if not originated from same domain
276 String payloadSrcDomain = eventHeader.getDomain();
277 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
278 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
279 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
280 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
281 "Unrecognized source domain '" + payloadSrcDomain + "'");
283 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
287 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
288 if (oxmJaxbContext == null) {
289 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
290 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, "OXM version mismatch",
293 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
297 String action = eventHeader.getAction();
298 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
299 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
300 "Unrecognized action '" + action + "'", uebPayload);
301 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
302 "Unrecognized action '" + action + "'");
304 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
308 String entityType = eventHeader.getEntityType();
309 if (entityType == null) {
310 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
311 "Payload header missing entity type", uebPayload);
312 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
313 "Payload header missing entity type");
315 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
319 String topEntityType = eventHeader.getTopEntityType();
320 if (topEntityType == null) {
321 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
322 "Payload header missing top entity type", uebPayload);
323 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
324 "Payload header top missing entity type");
326 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
330 String entityLink = eventHeader.getEntityLink();
331 if (entityLink == null) {
332 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
333 "Payload header missing entity link", uebPayload);
334 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
335 "Payload header missing entity link");
337 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
341 // log the fact that all data are in good shape
342 logger.info(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_NONVERBOSE, action,
344 logger.debug(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
348 // Process for building AaiEventEntity object
349 String[] entityTypeArr = entityType.split("-");
350 String oxmEntityType = "";
351 for (String entityWord : entityTypeArr) {
352 oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
355 List<String> searchableAttr =
356 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
357 if (searchableAttr == null) {
358 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
359 "Searchable attribute not found for payload entity type '" + entityType + "'");
360 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
361 "Searchable attribute not found for payload entity type '" + entityType + "'",
364 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
368 String entityPrimaryKeyFieldName =
369 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
370 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
371 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
372 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
373 "Payload missing primary key attribute");
374 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
375 "Payload missing primary key attribute", uebPayload);
377 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
381 AaiEventEntity aaiEventEntity = new AaiEventEntity();
384 * Use the OXM Model to determine the primary key field name based on the entity-type
387 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
388 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
389 aaiEventEntity.setEntityType(entityType);
390 aaiEventEntity.setLink(entityLink);
392 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
393 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
394 "Payload missing searchable attribute for entity type '" + entityType + "'");
395 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
396 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
398 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
404 aaiEventEntity.deriveFields();
406 } catch (NoSuchAlgorithmException e) {
407 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
408 "Cannot create unique SHA digest");
409 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
410 "Cannot create unique SHA digest", uebPayload);
412 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
416 handleSearchServiceOperation(aaiEventEntity, action, entitySearchIndex);
418 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
419 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
422 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
424 VersionedOxmEntities oxmEntities =
425 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion));
429 * 1. If the entity type is "customer", the below check will return true if any nested entityType
430 * in that model could contain a CER based on the OXM model version that has been loaded.
431 * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
432 * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
433 * assumption that AAI will push down all required cascade-deletes for nested entities as well
434 * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
435 * CREATE service-instance.
438 if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
439 && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
441 // We know the model "can" contain a CER reference definition, let's process a bit more
443 HashMap<String, CrossEntityReference> crossEntityRefMap =
444 oxmEntities.getCrossEntityReferences();
446 JSONObject entityJsonObject = getUebEntity(uebPayload);
448 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
450 String parentEntityType = entityType;
452 String targetEntityUrl = entityLink;
454 for (String key : crossEntityRefMap.keySet()) {
457 * if we know service-subscription is in the tree, then we can pull our all instances and
458 * process from there.
461 CrossEntityReference cerDescriptor = crossEntityRefMap.get(key);
463 ArrayList<JsonNode> foundNodes = new ArrayList<JsonNode>();
465 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
467 if (foundNodes.size() > 0) {
469 for (JsonNode n : foundNodes) {
470 if (parentEntityType.equalsIgnoreCase("customer")){
473 * 1. prepare to hand-create url for service-instance
474 * 2. this will break if the URL structure for service-instance changes
476 if (n.has("service-type")){
477 targetEntityUrl += "/service-subscriptions/service-subscription/"
478 + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
479 + "/service-instances/service-instance/";
484 List<String> extractedParentEntityAttributeValues = new ArrayList<String>();
486 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
487 extractedParentEntityAttributeValues);
489 List<JsonNode> nestedTargetEntityInstances = new ArrayList<JsonNode>();
490 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
491 nestedTargetEntityInstances);
493 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
496 * 1. build the AAIEntityType (IndexDocument) based on the extract entity
497 * 2. Get data from ES
499 * 4. Merge ES Doc + AAIEntityType + Extracted Parent Cross-Entity-Reference Values
500 * 5. Put data into ES with ETAG + updated doc
503 // Get the complete URL for target entity
504 if (targetEntityInstance.has("link")) { // nested SI has url mentioned
505 targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
507 } else if (parentEntityType.equalsIgnoreCase("customer") &&
508 targetEntityInstance.has("service-instance-id")){
509 targetEntityUrl += "/" + RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
510 "service-instance-id");
513 OxmEntityDescriptor searchableDescriptor =
514 oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
516 if (searchableDescriptor != null) {
518 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
520 AaiEventEntity entityToSync = null;
524 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
527 * Ready to do some ElasticSearch ops
530 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
532 .addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
535 entityToSync.setLink(targetEntityUrl);
536 entityToSync.deriveFields();
538 updateCerInEntity(entityToSync);
540 } catch (NoSuchAlgorithmException e) {
545 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
546 "failure to find searchable descriptor for type "
547 + cerDescriptor.getTargetEntityType());
554 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
555 "failed to find 0 instances of cross-entity-reference with entity " + key);
561 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
562 + topEntityType + " does not contain a cross-entity-reference entity");
566 * Process for autosuggestable entities
568 if (oxmEntities != null) {
569 Map<String, OxmEntityDescriptor> rootDescriptor =
570 oxmEntities.getSuggestableEntityDescriptors();
571 if (!rootDescriptor.isEmpty()) {
572 List<String> suggestibleAttrInPayload = new ArrayList<String>();
573 List<String> suggestibleAttrInOxm = extractSuggestableAttr(oxmEntities, entityType);
574 if (suggestibleAttrInOxm != null) {
575 for (String attr: suggestibleAttrInOxm){
576 if ( uebObjEntity.has(attr) ){
577 suggestibleAttrInPayload.add(attr);
582 if (suggestibleAttrInPayload.isEmpty()) {
586 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
587 AggregationEntity ae = new AggregationEntity();
588 ae.setLink(entityLink);
589 ae.deriveFields(uebAsJson);
591 handleSearchServiceOperation(ae, action, this.aggregationSearchVnfTarget);
594 * It was decided to silently ignore DELETE requests for resources we don't allow to be
595 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
597 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
598 List<ArrayList<String>> listOfValidPowerSetElements =
599 SearchSuggestionPermutation.getNonEmptyUniqueLists(suggestibleAttrInPayload);
601 // Now we have a list containing the power-set (minus empty element) for the status that are
602 // available in the payload. Try inserting a document for every combination.
603 for (ArrayList<String> list : listOfValidPowerSetElements) {
604 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
605 suggestionSearchEntity.setEntityType(entityType);
606 suggestionSearchEntity.setSuggestableAttr(list);
607 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
608 suggestionSearchEntity.setFilterBasedPayloadFromResponse(uebAsJson.get("entity"),
609 suggestibleAttrInOxm, list);
610 suggestionSearchEntity.setSuggestionInputPermutations(
611 suggestionSearchEntity.generateSuggestionInputPermutations());
613 if (suggestionSearchEntity.isSuggestableDoc()) {
615 suggestionSearchEntity.generateSearchSuggestionDisplayStringAndId();
616 } catch (NoSuchAlgorithmException e) {
617 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
618 "Cannot create unique SHA digest for search suggestion data. Exception: "
619 + e.getLocalizedMessage());
622 handleSearchServiceOperation(suggestionSearchEntity, action,
623 this.autoSuggestSearchTarget);
630 long stopTime = System.currentTimeMillis();
632 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
633 String.valueOf(stopTime - startTime));
635 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
639 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
640 // Extract suggestable attributes
641 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
643 if (rootDescriptor == null) {
647 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
653 return desc.getSuggestableAttributes();
656 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
660 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
662 if (rootDescriptor == null) {
666 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
667 return desc.getAlias();
670 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
672 exchange.getOut().setHeader("ResponseType", responseType.toString());
673 exchange.getOut().setBody(additionalInfo);
676 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
677 List<String> suggestableAttr, List<String> alias) {
679 // Extract suggestable attributes
680 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
682 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
683 suggestableAttr = desc.getSuggestableAttributes();
686 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
687 desc = rootDescriptor.get(entityType);
688 alias = desc.getAlias();
692 * Load the UEB JSON payload, any errors would result to a failure case response.
694 private JSONObject getUebContentAsJson(String payload, String contentKey) {
696 JSONObject uebJsonObj;
697 JSONObject uebObjContent;
700 uebJsonObj = new JSONObject(payload);
701 } catch (JSONException e) {
702 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
703 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
707 if (uebJsonObj.has(contentKey)) {
708 uebObjContent = uebJsonObj.getJSONObject(contentKey);
710 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
711 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, contentKey);
715 return uebObjContent;
719 private UebEventHeader initializeUebEventHeader(String payload) {
721 UebEventHeader eventHeader = null;
722 ObjectMapper mapper = new ObjectMapper();
724 // Make sure that were were actually passed in a valid string.
725 if (payload == null || payload.isEmpty()) {
726 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
727 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
732 // Marshal the supplied string into a UebEventHeader object.
734 eventHeader = mapper.readValue(payload, UebEventHeader.class);
735 } catch (JsonProcessingException e) {
736 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
737 } catch (Exception e) {
738 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
741 if (eventHeader != null) {
742 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
750 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
751 String oxmEntityType, String entityType) {
753 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
754 if (entity == null) {
758 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
759 if (list != null && !list.isEmpty()) {
760 String keyName = list.get(0).getName();
761 return keyName.substring(0, keyName.indexOf('/'));
767 private String lookupValueUsingKey(String payload, String key) throws JSONException {
768 JsonNode jsonNode = convertToJsonNode(payload);
769 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
772 private JsonNode convertToJsonNode(String payload) {
774 ObjectMapper mapper = new ObjectMapper();
775 JsonNode jsonNode = null;
777 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
778 } catch (IOException e) {
779 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
781 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
788 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
789 String payload, String action) {
791 boolean hasSearchableAttr = false;
792 for (String searchTagField : searchableAttr) {
793 String searchTagValue = null;
794 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
795 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
797 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
800 if (searchTagValue != null && !searchTagValue.isEmpty()) {
801 hasSearchableAttr = true;
802 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
805 return hasSearchableAttr;
809 * Check if OXM version is available. If available, load it.
811 private DynamicJAXBContext loadOxmContext(String version) {
812 if (version == null) {
813 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
817 return oxmVersionContextMap.get(version);
820 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
821 String oxmEntityType, String entityType, String fieldName) {
823 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
824 if (entity == null) {
829 * Check for searchable XML tag
831 List<String> fieldValues = null;
832 Map<String, String> properties = entity.getDescriptor().getProperties();
833 for (Map.Entry<String, String> entry : properties.entrySet()) {
834 if (entry.getKey().equalsIgnoreCase(fieldName)) {
835 fieldValues = Arrays.asList(entry.getValue().split(","));
843 private JSONObject getUebEntity(String payload) {
844 JSONObject uebJsonObj;
847 uebJsonObj = new JSONObject(payload);
848 } catch (JSONException e) {
849 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
850 "Payload has invalid JSON Format", payload.toString());
851 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
852 "Payload has invalid JSON Format");
856 if (uebJsonObj.has(ENTITY_HEADER)) {
857 return uebJsonObj.getJSONObject(ENTITY_HEADER);
859 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
861 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
866 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
867 OxmEntityDescriptor resultDescriptor) {
868 AaiEventEntity d = new AaiEventEntity();
870 d.setEntityType(resultDescriptor.getEntityName());
872 List<String> primaryKeyValues = new ArrayList<String>();
873 List<String> primaryKeyNames = new ArrayList<String>();
874 String pkeyValue = null;
876 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
877 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
878 if (pkeyValue != null) {
879 primaryKeyValues.add(pkeyValue);
880 primaryKeyNames.add(keyName);
882 // logger.warn("getPopulatedDocument(), pKeyValue is null for entityType = " +
883 // resultDescriptor.getEntityName());
884 logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
885 resultDescriptor.getEntityName());
889 final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
890 d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
891 final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
892 d.setEntityPrimaryKeyName(primaryCompositeKeyName);
894 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
897 * Based on configuration, use the configured field names for this entity-Type to build a
898 * multi-value collection of search tags for elastic search entity search criteria.
902 for (String searchTagField : searchTagFields) {
903 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
904 if (searchTagValue != null && !searchTagValue.isEmpty()) {
905 d.addSearchTagWithKey(searchTagValue, searchTagField);
912 private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
914 Map<String, List<String>> headers = new HashMap<>();
915 headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
916 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
918 String entityId = aaiEventEntity.getId();
919 String jsonPayload = aaiEventEntity.getAsJson();
921 // Run the GET to retrieve the ETAG from the search service
922 OperationResult storedEntity = searchAgent.getDocument(entitySearchIndex, entityId);
924 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
926 * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
927 * the pre-existing object,
928 * so all we want to do is update the CER on the pre-existing object (if needed).
931 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
933 if (etag != null && etag.size() > 0) {
934 headers.put(Headers.IF_MATCH, etag);
936 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
937 entitySearchTarget + entityId, entityId);
940 ArrayList<JsonNode> sourceObject = new ArrayList<JsonNode>();
941 NodeUtils.extractObjectsByKey(
942 NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
943 "content", sourceObject);
945 if (!sourceObject.isEmpty()) {
946 JsonNode node = sourceObject.get(0);
947 final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
948 "crossEntityReferenceValues");
949 String newCer = aaiEventEntity.getCrossReferenceEntityValues();
950 boolean hasNewCer = true;
951 if (sourceCer != null && sourceCer.length() > 0){ // already has CER
952 if ( !sourceCer.contains(newCer)){//don't re-add
953 newCer = sourceCer + ";" + newCer;
960 // Do the PUT with new CER
961 ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
962 jsonPayload = NodeUtils.convertObjectToJson(node, false);
963 searchAgent.putDocument(entitySearchIndex, entityId, jsonPayload, headers);
968 if (storedEntity.getResultCode() == 404) {
969 // entity not found, so attempt to do a PUT
970 searchAgent.putDocument(entitySearchIndex, entityId, aaiEventEntity.getAsJson(), headers);
972 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
973 aaiEventEntity.getId(), "SYNC_ENTITY");
976 } catch (IOException e) {
977 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
978 aaiEventEntity.getId(), "SYNC_ENTITY");
983 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
986 * @param eventEntity Entity/data to use in operation
987 * @param action The operation to perform
988 * @param target Resource to perform the operation on
989 * @param allowDeleteEvent Allow delete operation to be performed on resource
991 private void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity,
996 Map<String, List<String>> headers = new HashMap<>();
997 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
998 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
1000 String entityId = eventEntity.getId();
1002 // System.out.println("aaiEventEntity as json = " + aaiEventEntity.getAsJson());
1004 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
1005 || action.equalsIgnoreCase(ACTION_UPDATE)) {
1007 // Run the GET to retrieve the ETAG from the search service
1008 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
1010 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1011 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1013 if (etag != null && etag.size() > 0) {
1014 headers.put(Headers.IF_MATCH, etag);
1016 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1021 // Write the entity to the search service.
1023 searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
1024 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
1025 // Write the entry to the search service.
1026 searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
1028 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1029 // Run the GET to retrieve the ETAG from the search service
1030 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
1032 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1033 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1035 if (etag != null && etag.size() > 0) {
1036 headers.put(Headers.IF_MATCH, etag);
1038 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1042 searchAgent.deleteDocument(index, eventEntity.getId(), headers);
1044 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1048 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1050 } catch (IOException e) {
1051 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1056 private void handleTopographicalData(String payload, String action, String entityType,
1057 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1058 String entityPrimaryKeyFieldValue) {
1060 Map<String, String> topoData = new HashMap<>();
1061 String entityLink = "";
1062 List<String> topographicalAttr =
1063 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1064 if (topographicalAttr == null) {
1065 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1066 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1067 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1068 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1069 payload.toString());
1071 entityLink = lookupValueUsingKey(payload, "entity-link");
1072 for (String topoAttr : topographicalAttr) {
1073 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1075 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1076 entityPrimaryKeyFieldValue, entityLink);
1081 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1082 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1084 TopographicalEntity topoEntity = new TopographicalEntity();
1085 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1086 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1087 topoEntity.setEntityType(entityType);
1088 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1089 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1090 topoEntity.setSelfLink(entityLink);
1092 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1093 entityPrimaryKeyValue));
1094 } catch (NoSuchAlgorithmException e) {
1095 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1096 "Cannot create unique SHA digest for topographical data.");
1099 this.handleSearchServiceOperation(topoEntity, action, this.topographicalSearchTarget);
1103 // put this here until we find a better spot
1105 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1107 * @param suburis the list of substrings to concatenate together
1108 * @return the concatenated list of substrings
1110 public static String concatSubUri(String... suburis) {
1111 String finalUri = "";
1113 for (String suburi : suburis) {
1115 if (suburi != null) {
1116 // Remove any leading / since we only want to append /
1117 suburi = suburi.replaceFirst("^/*", "");
1119 // Add a trailing / if one isn't already there
1120 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";