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 com.fasterxml.jackson.core.JsonProcessingException;
28 import com.fasterxml.jackson.databind.JsonNode;
29 import com.fasterxml.jackson.databind.ObjectMapper;
30 import com.fasterxml.jackson.databind.ObjectWriter;
31 import com.fasterxml.jackson.databind.node.ObjectNode;
32 import com.sun.jersey.core.util.MultivaluedMapImpl;
34 import java.io.BufferedReader;
35 import java.io.IOException;
36 import java.io.InputStreamReader;
37 import java.nio.charset.StandardCharsets;
38 import java.security.NoSuchAlgorithmException;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.List;
46 import java.util.UUID;
48 import javax.ws.rs.core.MediaType;
49 import javax.ws.rs.core.MultivaluedMap;
51 import org.apache.camel.Exchange;
52 import org.apache.camel.Processor;
53 import org.eclipse.jetty.util.security.Password;
54 import org.eclipse.persistence.dynamic.DynamicType;
55 import org.eclipse.persistence.internal.helper.DatabaseField;
56 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
57 import org.json.JSONException;
58 import org.json.JSONObject;
59 import org.openecomp.cl.api.Logger;
60 import org.openecomp.cl.eelf.LoggerFactory;
61 import org.openecomp.cl.mdc.MdcContext;
62 import org.openecomp.datarouter.entity.AaiEventEntity;
63 import org.openecomp.datarouter.entity.AggregationEntity;
64 import org.openecomp.datarouter.entity.DocumentStoreDataEntity;
65 import org.openecomp.datarouter.entity.OxmEntityDescriptor;
66 import org.openecomp.datarouter.entity.SuggestionSearchEntity;
67 import org.openecomp.datarouter.entity.TopographicalEntity;
68 import org.openecomp.datarouter.entity.UebEventHeader;
69 import org.openecomp.datarouter.logging.EntityEventPolicyMsgs;
70 import org.openecomp.datarouter.util.CrossEntityReference;
71 import org.openecomp.datarouter.util.DataRouterConstants;
72 import org.openecomp.datarouter.util.EntityOxmReferenceHelper;
73 import org.openecomp.datarouter.util.ExternalOxmModelProcessor;
74 import org.openecomp.datarouter.util.OxmModelLoader;
75 import org.openecomp.datarouter.util.RouterServiceUtil;
76 import org.openecomp.datarouter.util.SearchSuggestionPermutation;
77 import org.openecomp.datarouter.util.Version;
78 import org.openecomp.datarouter.util.VersionedOxmEntities;
79 import org.openecomp.restclient.client.Headers;
80 import org.openecomp.restclient.client.OperationResult;
81 import org.openecomp.restclient.client.RestClient;
82 import org.openecomp.restclient.rest.HttpUtil;
83 import org.openecomp.datarouter.util.NodeUtils;
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;
92 RestClient searchClient = null;
94 private final String EVENT_HEADER = "event-header";
95 private final String ENTITY_HEADER = "entity";
96 private final String ACTION_CREATE = "create";
97 private final String ACTION_DELETE = "delete";
98 private final String ACTION_UPDATE = "update";
99 private final String PROCESS_AAI_EVENT = "Process AAI Event";
100 private final String TOPO_LAT = "latitude";
101 private final String TOPO_LONG = "longitude";
103 private final List<String> SUPPORTED_ACTIONS =
104 Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
106 Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
107 private String oxmVersion = null;
109 private String entityIndexTarget = null;
110 private String entitySearchTarget = null;
111 private String topographicalIndexTarget = null;
112 private String topographicalSearchTarget = null;
113 private String autoSuggestSearchTarget = null;
114 private String aggregationSearchVnfTarget = null;
116 private String srcDomain;
118 private Logger logger;
119 private Logger metricsLogger;
120 private Logger auditLogger;
122 public enum ResponseType {
123 SUCCESS, PARTIAL_SUCCESS, FAILURE;
126 public EntityEventPolicy(EntityEventPolicyConfig config) {
127 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
128 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
129 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
130 auditLogger = loggerFactoryInstance.getAuditLogger(EntityEventPolicy.class.getName());
132 srcDomain = config.getSourceDomain();
135 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
136 config.getSearchEntitySearchIndex());
139 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
140 config.getSearchEntitySearchIndex(), config.getSearchEndpointDocuments());
142 topographicalIndexTarget =
143 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
144 config.getSearchTopographySearchIndex());
146 topographicalSearchTarget = EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
147 config.getSearchEndpoint(), config.getSearchTopographySearchIndex());
149 // Create REST client for search service
150 searchClient = new RestClient().validateServerHostname(false).validateServerCertChain(true)
151 .clientCertFile(DataRouterConstants.DR_HOME_AUTH + config.getSearchCertName())
152 .clientCertPassword(Password.deobfuscate(config.getSearchKeystorePwd()))
153 .trustStore(DataRouterConstants.DR_HOME_AUTH + config.getSearchKeystore());
155 autoSuggestSearchTarget =
156 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
157 config.getSearchEntityAutoSuggestIndex(), config.getSearchEndpointDocuments());
159 aggregationSearchVnfTarget =
160 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
161 config.getSearchAggregationVnfIndex(), config.getSearchEndpointDocuments());
163 this.externalOxmModelProcessors = new ArrayList<ExternalOxmModelProcessor>();
164 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
165 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
166 OxmModelLoader.loadModels();
167 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
168 parseLatestOxmVersion();
171 private void parseLatestOxmVersion() {
172 int latestVersion = -1;
173 if (oxmVersionContextMap != null) {
174 Iterator it = oxmVersionContextMap.entrySet().iterator();
175 while (it.hasNext()) {
176 Map.Entry pair = (Map.Entry) it.next();
178 String version = pair.getKey().toString();
179 int versionNum = Integer.parseInt(version.substring(1, version.length()));
181 if (versionNum > latestVersion) {
182 latestVersion = versionNum;
183 oxmVersion = pair.getKey().toString();
186 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
189 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
193 public void startup() {
195 // Create the indexes in the search service if they do not already exist.
196 createSearchIndex(entityIndexTarget, entitySearchSchema);
197 createSearchIndex(topographicalIndexTarget, topographicalSearchSchema);
199 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
203 * Creates an index through the search db abstraction
205 * @param searchRESTClient
206 * the REST client configured to contact the search db
208 * @param searchTarget
209 * the URL to attempt to create the search index
210 * @param schemaLocation
211 * the location of the mappings file for the index
213 private void createSearchIndex(String searchTarget, String schemaLocation) {
215 logger.debug("Creating search index, searchTarget = " + searchTarget + ", schemaLocation = " + schemaLocation);
217 MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
218 headers.put("Accept", Arrays.asList("application/json"));
219 headers.put(Headers.FROM_APP_ID, Arrays.asList("DL"));
220 headers.put(Headers.TRANSACTION_ID, Arrays.asList(UUID.randomUUID().toString()));
224 OperationResult result = searchClient.put(searchTarget, loadFileData(schemaLocation), headers,
225 MediaType.APPLICATION_JSON_TYPE, null);
227 if (!HttpUtil.isHttpResponseClassSuccess(result.getResultCode())) {
228 logger.error(EntityEventPolicyMsgs.FAIL_TO_CREATE_SEARCH_INDEX, searchTarget, result.getFailureCause());
230 logger.info(EntityEventPolicyMsgs.SEARCH_INDEX_CREATE_SUCCESS, searchTarget);
233 } catch (Exception e) {
234 logger.error(EntityEventPolicyMsgs.FAIL_TO_CREATE_SEARCH_INDEX, searchTarget, e.getLocalizedMessage());
239 * Convenience method to load up all the data from a file into a string
241 * @param filename the filename to read from disk
242 * @return the data contained within the file
245 protected String loadFileData(String filename) throws Exception {
246 StringBuilder data = new StringBuilder();
248 BufferedReader in = new BufferedReader(new InputStreamReader(
249 EntityEventPolicy.class.getClassLoader().getResourceAsStream("/" + filename),
250 StandardCharsets.UTF_8));
253 while ((line = in.readLine()) != null) {
256 } catch (Exception e) {
257 throw new Exception("Failed to read from file = " + filename + ".", e);
260 return data.toString();
265 * Convert object to json.
267 * @param object the object
268 * @param pretty the pretty
270 * @throws JsonProcessingException the json processing exception
272 public static String convertObjectToJson(Object object, boolean pretty)
273 throws JsonProcessingException {
274 ObjectWriter ow = null;
277 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
280 ow = new ObjectMapper().writer();
283 return ow.writeValueAsString(object);
286 public void returnWithError(Exchange exchange, String payload, String errorMsg){
287 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE, errorMsg);
288 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, errorMsg, payload);
289 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
293 public void process(Exchange exchange) throws Exception {
295 long startTime = System.currentTimeMillis();
297 String uebPayload = exchange.getIn().getBody().toString();
299 JsonNode uebAsJson =null;
300 ObjectMapper mapper = new ObjectMapper();
302 uebAsJson = mapper.readTree(uebPayload);
303 } catch (IOException e){
304 returnWithError(exchange, uebPayload, "Invalid Payload");
308 // Load the UEB payload data, any errors will result in a failure and discard
309 JSONObject uebObjHeader = getUebHeaderAsJson(uebPayload);
310 if (uebObjHeader == null) {
311 returnWithError(exchange, uebPayload, "Payload is missing event-header");
315 UebEventHeader eventHeader = null;
316 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
318 // Get src domain from header; discard event if not originated from same domain
319 String payloadSrcDomain = eventHeader.getDomain();
320 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
321 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
322 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
323 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
324 "Unrecognized source domain '" + payloadSrcDomain + "'");
326 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
330 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
331 if (oxmJaxbContext == null) {
332 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
333 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, "OXM version mismatch",
336 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
340 String action = eventHeader.getAction();
341 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
342 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
343 "Unrecognized action '" + action + "'", uebPayload);
344 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
345 "Unrecognized action '" + action + "'");
347 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
351 String entityType = eventHeader.getEntityType();
352 if (entityType == null) {
353 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
354 "Payload header missing entity type", uebPayload);
355 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
356 "Payload header missing entity type");
358 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
362 String topEntityType = eventHeader.getTopEntityType();
363 if (topEntityType == null) {
364 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
365 "Payload header missing top entity type", uebPayload);
366 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
367 "Payload header top missing entity type");
369 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
373 String entityLink = eventHeader.getEntityLink();
374 if (entityLink == null) {
375 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
376 "Payload header missing entity link", uebPayload);
377 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
378 "Payload header missing entity link");
380 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
384 // log the fact that all data are in good shape
385 logger.info(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_NONVERBOSE, action,
387 logger.debug(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
391 // Process for building AaiEventEntity object
392 String[] entityTypeArr = entityType.split("-");
393 String oxmEntityType = "";
394 for (String entityWord : entityTypeArr) {
395 oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
398 List<String> searchableAttr =
399 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
400 if (searchableAttr == null) {
401 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
402 "Searchable attribute not found for payload entity type '" + entityType + "'");
403 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
404 "Searchable attribute not found for payload entity type '" + entityType + "'",
407 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
411 String entityPrimaryKeyFieldName =
412 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
413 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
414 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
415 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
416 "Payload missing primary key attribute");
417 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
418 "Payload missing primary key attribute", uebPayload);
420 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
424 AaiEventEntity aaiEventEntity = new AaiEventEntity();
427 * Use the OXM Model to determine the primary key field name based on the entity-type
430 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
431 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
432 aaiEventEntity.setEntityType(entityType);
433 aaiEventEntity.setLink(entityLink);
435 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
436 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
437 "Payload missing searchable attribute for entity type '" + entityType + "'");
438 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
439 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
441 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
447 aaiEventEntity.deriveFields();
449 } catch (NoSuchAlgorithmException e) {
450 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
451 "Cannot create unique SHA digest");
452 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
453 "Cannot create unique SHA digest", uebPayload);
455 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
459 handleSearchServiceOperation(aaiEventEntity, action, this.entitySearchTarget);
461 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
462 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
465 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
467 VersionedOxmEntities oxmEntities =
468 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion));
472 * 1. If the entity type is "customer", the below check will return true if any nested entityType
473 * in that model could contain a CER based on the OXM model version that has been loaded.
474 * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
475 * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
476 * assumption that AAI will push down all required cascade-deletes for nested entities as well
477 * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
478 * CREATE service-instance.
481 if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
482 && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
484 // We know the model "can" contain a CER reference definition, let's process a bit more
486 HashMap<String, CrossEntityReference> crossEntityRefMap =
487 oxmEntities.getCrossEntityReferences();
489 JSONObject entityJsonObject = getUebEntity(uebPayload);
491 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
493 String parentEntityType = entityType;
495 String targetEntityUrl = entityLink;
497 for (String key : crossEntityRefMap.keySet()) {
500 * if we know service-subscription is in the tree, then we can pull our all instances and
501 * process from there.
504 CrossEntityReference cerDescriptor = crossEntityRefMap.get(key);
506 ArrayList<JsonNode> foundNodes = new ArrayList<JsonNode>();
508 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
510 if (foundNodes.size() > 0) {
512 for (JsonNode n : foundNodes) {
513 if (parentEntityType.equalsIgnoreCase("customer")){
516 * 1. prepare to hand-create url for service-instance
517 * 2. this will break if the URL structure for service-instance changes
519 if (n.has("service-type")){
520 targetEntityUrl += "/service-subscriptions/service-subscription/"
521 + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
522 + "/service-instances/service-instance/";
527 List<String> extractedParentEntityAttributeValues = new ArrayList<String>();
529 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
530 extractedParentEntityAttributeValues);
532 List<JsonNode> nestedTargetEntityInstances = new ArrayList<JsonNode>();
533 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
534 nestedTargetEntityInstances);
536 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
539 * 1. build the AAIEntityType (IndexDocument) based on the extract entity
540 * 2. Get data from ES
542 * 4. Merge ES Doc + AAIEntityType + Extracted Parent Cross-Entity-Reference Values
543 * 5. Put data into ES with ETAG + updated doc
546 // Get the complete URL for target entity
547 if (targetEntityInstance.has("link")) { // nested SI has url mentioned
548 targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
550 } else if (parentEntityType.equalsIgnoreCase("customer") &&
551 targetEntityInstance.has("service-instance-id")){
552 targetEntityUrl += "/" + RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
553 "service-instance-id");
556 OxmEntityDescriptor searchableDescriptor =
557 oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
559 if (searchableDescriptor != null) {
561 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
563 AaiEventEntity entityToSync = null;
567 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
570 * Ready to do some ElasticSearch ops
573 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
575 .addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
578 entityToSync.setLink(targetEntityUrl);
579 entityToSync.deriveFields();
581 updateCerInEntity(entityToSync);
583 } catch (NoSuchAlgorithmException e) {
588 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
589 "failure to find searchable descriptor for type "
590 + cerDescriptor.getTargetEntityType());
597 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
598 "failed to find 0 instances of cross-entity-reference with entity " + key);
604 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
605 + topEntityType + " does not contain a cross-entity-reference entity");
609 * Process for autosuggestable entities
611 if (oxmEntities != null) {
612 Map<String, OxmEntityDescriptor> rootDescriptor =
613 oxmEntities.getSuggestableEntityDescriptors();
614 if (!rootDescriptor.isEmpty()) {
615 List<String> suggestibleAttributes = extractSuggestableAttr(oxmEntities, entityType);
617 if (suggestibleAttributes == null) {
621 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
622 AggregationEntity ae = new AggregationEntity();
623 ae.setLink(entityLink);
624 ae.deriveFields(uebAsJson);
626 handleSuggestiveSearchData(ae, action, this.aggregationSearchVnfTarget);
629 * It was decided to silently ignore DELETE requests for resources we don't allow to be
630 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
632 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
633 SearchSuggestionPermutation searchSuggestionPermutation =
634 new SearchSuggestionPermutation();
635 List<ArrayList<String>> permutationsOfStatuses =
636 searchSuggestionPermutation.getSuggestionsPermutation(suggestibleAttributes);
638 // Now we have a list of all possible permutations for the status that are
639 // defined for this entity type. Try inserting a document for every combination.
640 for (ArrayList<String> permutation : permutationsOfStatuses) {
641 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
642 suggestionSearchEntity.setEntityType(entityType);
643 suggestionSearchEntity.setSuggestableAttr(permutation);
644 suggestionSearchEntity.setPayloadFromResponse(uebAsJson);
645 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
646 suggestionSearchEntity.setSuggestionInputPermutations(
647 suggestionSearchEntity.generateSuggestionInputPermutations());
649 if (suggestionSearchEntity.isSuggestableDoc()) {
651 suggestionSearchEntity.deriveFields();
652 } catch (NoSuchAlgorithmException e) {
653 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
654 "Cannot create unique SHA digest for search suggestion data. Exception: "
655 + e.getLocalizedMessage());
658 handleSuggestiveSearchData(suggestionSearchEntity, action,
659 this.autoSuggestSearchTarget);
666 long stopTime = System.currentTimeMillis();
668 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
669 String.valueOf(stopTime - startTime));
671 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
675 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
676 // Extract suggestable attributes
677 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
679 if (rootDescriptor == null) {
683 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
689 return desc.getSuggestableAttributes();
692 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
696 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
698 if (rootDescriptor == null) {
702 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
703 return desc.getAlias();
706 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
708 exchange.getOut().setHeader("ResponseType", responseType.toString());
709 exchange.getOut().setBody(additionalInfo);
712 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
713 List<String> suggestableAttr, List<String> alias) {
715 // Extract suggestable attributes
716 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
718 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
719 suggestableAttr = desc.getSuggestableAttributes();
722 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
723 desc = rootDescriptor.get(entityType);
724 alias = desc.getAlias();
728 * Load the UEB JSON payload, any errors would result to a failure case response.
730 private JSONObject getUebHeaderAsJson(String payload) {
732 JSONObject uebJsonObj;
733 JSONObject uebObjHeader;
736 uebJsonObj = new JSONObject(payload);
737 } catch (JSONException e) {
738 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
739 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
743 if (uebJsonObj.has(EVENT_HEADER)) {
744 uebObjHeader = uebJsonObj.getJSONObject(EVENT_HEADER);
746 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
747 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
755 private UebEventHeader initializeUebEventHeader(String payload) {
757 UebEventHeader eventHeader = null;
758 ObjectMapper mapper = new ObjectMapper();
760 // Make sure that were were actually passed in a valid string.
761 if (payload == null || payload.isEmpty()) {
762 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
763 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
768 // Marshal the supplied string into a UebEventHeader object.
770 eventHeader = mapper.readValue(payload, UebEventHeader.class);
771 } catch (JsonProcessingException e) {
772 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
773 } catch (Exception e) {
774 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
777 if (eventHeader != null) {
778 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
786 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
787 String oxmEntityType, String entityType) {
789 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
790 if (entity == null) {
794 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
795 if (list != null && !list.isEmpty()) {
796 String keyName = list.get(0).getName();
797 return keyName.substring(0, keyName.indexOf('/'));
803 private String lookupValueUsingKey(String payload, String key) throws JSONException {
804 JsonNode jsonNode = convertToJsonNode(payload);
805 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
808 private JsonNode convertToJsonNode(String payload) {
810 ObjectMapper mapper = new ObjectMapper();
811 JsonNode jsonNode = null;
813 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
814 } catch (IOException e) {
815 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
817 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
824 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
825 String payload, String action) {
827 boolean hasSearchableAttr = false;
828 for (String searchTagField : searchableAttr) {
829 String searchTagValue = null;
830 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
831 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
833 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
836 if (searchTagValue != null && !searchTagValue.isEmpty()) {
837 hasSearchableAttr = true;
838 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
841 return hasSearchableAttr;
845 * Check if OXM version is available. If available, load it.
847 private DynamicJAXBContext loadOxmContext(String version) {
848 if (version == null) {
849 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
853 return oxmVersionContextMap.get(version);
856 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
857 String oxmEntityType, String entityType, String fieldName) {
859 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
860 if (entity == null) {
865 * Check for searchable XML tag
867 List<String> fieldValues = null;
868 Map<String, String> properties = entity.getDescriptor().getProperties();
869 for (Map.Entry<String, String> entry : properties.entrySet()) {
870 if (entry.getKey().equalsIgnoreCase(fieldName)) {
871 fieldValues = Arrays.asList(entry.getValue().split(","));
879 private JSONObject getUebEntity(String payload) {
880 JSONObject uebJsonObj;
883 uebJsonObj = new JSONObject(payload);
884 } catch (JSONException e) {
885 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
886 "Payload has invalid JSON Format", payload.toString());
887 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
888 "Payload has invalid JSON Format");
892 if (uebJsonObj.has(ENTITY_HEADER)) {
893 return uebJsonObj.getJSONObject(ENTITY_HEADER);
895 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
897 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
902 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
903 OxmEntityDescriptor resultDescriptor) {
904 AaiEventEntity d = new AaiEventEntity();
906 d.setEntityType(resultDescriptor.getEntityName());
908 List<String> primaryKeyValues = new ArrayList<String>();
909 List<String> primaryKeyNames = new ArrayList<String>();
910 String pkeyValue = null;
912 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
913 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
914 if (pkeyValue != null) {
915 primaryKeyValues.add(pkeyValue);
916 primaryKeyNames.add(keyName);
918 // logger.warn("getPopulatedDocument(), pKeyValue is null for entityType = " +
919 // resultDescriptor.getEntityName());
920 logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
921 resultDescriptor.getEntityName());
925 final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
926 d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
927 final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
928 d.setEntityPrimaryKeyName(primaryCompositeKeyName);
930 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
933 * Based on configuration, use the configured field names for this entity-Type to build a
934 * multi-value collection of search tags for elastic search entity search criteria.
938 for (String searchTagField : searchTagFields) {
939 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
940 if (searchTagValue != null && !searchTagValue.isEmpty()) {
941 d.addSearchTagWithKey(searchTagValue, searchTagField);
948 private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
950 Map<String, List<String>> headers = new HashMap<>();
951 headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
952 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
954 String entityId = aaiEventEntity.getId();
955 String jsonPayload = aaiEventEntity.getAsJson();
957 // Run the GET to retrieve the ETAG from the search service
958 OperationResult storedEntity =
959 searchClient.get(entitySearchTarget+entityId, headers, MediaType.APPLICATION_JSON_TYPE);
961 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
963 * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
964 * the pre-existing object,
965 * so all we want to do is update the CER on the pre-existing object (if needed).
968 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
970 if (etag != null && etag.size() > 0) {
971 headers.put(Headers.IF_MATCH, etag);
973 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
974 entitySearchTarget + entityId, entityId);
977 ArrayList<JsonNode> sourceObject = new ArrayList<JsonNode>();
978 NodeUtils.extractObjectsByKey(
979 NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
980 "content", sourceObject);
982 if (!sourceObject.isEmpty()) {
983 JsonNode node = sourceObject.get(0);
984 final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
985 "crossEntityReferenceValues");
986 String newCer = aaiEventEntity.getCrossReferenceEntityValues();
987 boolean hasNewCer = true;
988 if (sourceCer != null && sourceCer.length() > 0){ // already has CER
989 if ( !sourceCer.contains(newCer)){//don't re-add
990 newCer = sourceCer + ";" + newCer;
997 // Do the PUT with new CER
998 ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
999 jsonPayload = NodeUtils.convertObjectToJson(node, false);
1000 searchClient.put(entitySearchTarget + entityId, jsonPayload, headers,
1001 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
1006 if (storedEntity.getResultCode() == 404) {
1007 // entity not found, so attempt to do a PUT
1008 searchClient.put(entitySearchTarget + entityId, aaiEventEntity.getAsJson(), headers,
1009 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
1011 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
1012 aaiEventEntity.getId(), "SYNC_ENTITY");
1015 } catch (IOException e) {
1016 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
1017 aaiEventEntity.getId(), "SYNC_ENTITY");
1022 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
1025 * @param eventEntity Entity/data to use in operation
1026 * @param action The operation to perform
1027 * @param target Resource to perform the operation on
1028 * @param allowDeleteEvent Allow delete operation to be performed on resource
1030 private void handleSuggestiveSearchData(DocumentStoreDataEntity eventEntity, String action,
1033 Map<String, List<String>> headers = new HashMap<>();
1034 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
1035 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
1037 String entityId = eventEntity.getId();
1039 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
1040 || action.equalsIgnoreCase(ACTION_UPDATE)) {
1041 // Run the GET to retrieve the ETAG from the search service
1042 OperationResult storedEntity =
1043 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
1045 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1046 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1048 if (etag != null && etag.size() > 0) {
1049 headers.put(Headers.IF_MATCH, etag);
1051 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1056 String eventEntityStr = eventEntity.getAsJson();
1058 if (eventEntityStr != null) {
1059 searchClient.put(target + entityId, eventEntity.getAsJson(), headers,
1060 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
1062 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
1063 String eventEntityStr = eventEntity.getAsJson();
1065 if (eventEntityStr != null) {
1066 searchClient.post(target, eventEntityStr, headers, MediaType.APPLICATION_JSON_TYPE,
1067 MediaType.APPLICATION_JSON_TYPE);
1069 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1070 // Run the GET to retrieve the ETAG from the search service
1071 OperationResult storedEntity =
1072 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
1074 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1075 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1077 if (etag != null && etag.size() > 0) {
1078 headers.put(Headers.IF_MATCH, etag);
1080 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1084 searchClient.delete(target + eventEntity.getId(), headers, null);
1086 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1090 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1092 } catch (IOException e) {
1093 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1098 private void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity, String action,
1102 Map<String, List<String>> headers = new HashMap<>();
1103 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
1104 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
1106 String entityId = eventEntity.getId();
1108 // System.out.println("aaiEventEntity as json = " + aaiEventEntity.getAsJson());
1110 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
1111 || action.equalsIgnoreCase(ACTION_UPDATE)) {
1113 // Run the GET to retrieve the ETAG from the search service
1114 OperationResult storedEntity =
1115 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
1117 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1118 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1120 if (etag != null && etag.size() > 0) {
1121 headers.put(Headers.IF_MATCH, etag);
1123 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1128 searchClient.put(target + entityId, eventEntity.getAsJson(), headers,
1129 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
1130 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
1131 searchClient.post(target, eventEntity.getAsJson(), headers, MediaType.APPLICATION_JSON_TYPE,
1132 MediaType.APPLICATION_JSON_TYPE);
1133 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1134 // Run the GET to retrieve the ETAG from the search service
1135 OperationResult storedEntity =
1136 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
1138 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1139 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1141 if (etag != null && etag.size() > 0) {
1142 headers.put(Headers.IF_MATCH, etag);
1144 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1148 searchClient.delete(target + eventEntity.getId(), headers, null);
1150 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1154 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1156 } catch (IOException e) {
1157 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1162 private void handleTopographicalData(String payload, String action, String entityType,
1163 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1164 String entityPrimaryKeyFieldValue) {
1166 Map<String, String> topoData = new HashMap<>();
1167 String entityLink = "";
1168 List<String> topographicalAttr =
1169 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1170 if (topographicalAttr == null) {
1171 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1172 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1173 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1174 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1175 payload.toString());
1177 entityLink = lookupValueUsingKey(payload, "entity-link");
1178 for (String topoAttr : topographicalAttr) {
1179 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1181 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1182 entityPrimaryKeyFieldValue, entityLink);
1187 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1188 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1190 TopographicalEntity topoEntity = new TopographicalEntity();
1191 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1192 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1193 topoEntity.setEntityType(entityType);
1194 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1195 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1196 topoEntity.setSelfLink(entityLink);
1198 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1199 entityPrimaryKeyValue));
1200 } catch (NoSuchAlgorithmException e) {
1201 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1202 "Cannot create unique SHA digest for topographical data.");
1205 this.handleSearchServiceOperation(topoEntity, action, this.topographicalSearchTarget);
1209 // put this here until we find a better spot
1211 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1213 * @param suburis the list of substrings to concatenate together
1214 * @return the concatenated list of substrings
1216 public static String concatSubUri(String... suburis) {
1217 String finalUri = "";
1219 for (String suburi : suburis) {
1221 if (suburi != null) {
1222 // Remove any leading / since we only want to append /
1223 suburi = suburi.replaceFirst("^/*", "");
1225 // Add a trailing / if one isn't already there
1226 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";