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.SearchServiceAgent;
77 import org.openecomp.datarouter.util.SearchSuggestionPermutation;
78 import org.openecomp.datarouter.util.Version;
79 import org.openecomp.datarouter.util.VersionedOxmEntities;
80 import org.openecomp.restclient.client.Headers;
81 import org.openecomp.restclient.client.OperationResult;
82 import org.openecomp.restclient.client.RestClient;
83 import org.openecomp.restclient.rest.HttpUtil;
84 import org.openecomp.datarouter.util.NodeUtils;
87 public class EntityEventPolicy implements Processor {
89 public static final String additionalInfo = "Response of AAIEntityEventPolicy";
90 private static final String entitySearchSchema = "entitysearch_schema.json";
91 private static final String topographicalSearchSchema = "topographysearch_schema.json";
92 private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
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 /** Agent for communicating with the Search Service. */
110 private SearchServiceAgent searchAgent = null;
112 /** Search index name for storing AAI event entities. */
113 private String entitySearchIndex;
115 /** Search index name for storing topographical search data. */
116 private String topographicalSearchIndex;
118 /** Search index name for suggestive search data. */
119 private String aggregateGenericVnfIndex;
121 private String entitySearchTarget = null;
122 private String topographicalSearchTarget = null;
123 private String autoSuggestSearchTarget = null;
124 private String aggregationSearchVnfTarget = null;
126 private String srcDomain;
128 private Logger logger;
129 private Logger metricsLogger;
130 private Logger auditLogger;
132 public enum ResponseType {
133 SUCCESS, PARTIAL_SUCCESS, FAILURE;
136 public EntityEventPolicy(EntityEventPolicyConfig config) {
137 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
138 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
139 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
140 auditLogger = loggerFactoryInstance.getAuditLogger(EntityEventPolicy.class.getName());
142 srcDomain = config.getSourceDomain();
144 // Populate the index names.
145 entitySearchIndex = config.getSearchEntitySearchIndex();
146 topographicalSearchIndex = config.getSearchTopographySearchIndex();
147 aggregateGenericVnfIndex = config.getSearchAggregationVnfIndex();
149 // Instantiate the agent that we will use for interacting with the Search Service.
150 searchAgent = new SearchServiceAgent(config.getSearchCertName(),
151 config.getSearchKeystore(),
152 config.getSearchKeystorePwd(),
153 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
154 config.getSearchEndpoint()),
155 config.getSearchEndpointDocuments(),
159 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
160 config.getSearchEntitySearchIndex(), config.getSearchEndpointDocuments());
162 topographicalSearchTarget = EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
163 config.getSearchEndpoint(), config.getSearchTopographySearchIndex());
165 autoSuggestSearchTarget =
166 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
167 config.getSearchEntityAutoSuggestIndex(), config.getSearchEndpointDocuments());
169 aggregationSearchVnfTarget =
170 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
171 config.getSearchAggregationVnfIndex(), config.getSearchEndpointDocuments());
173 this.externalOxmModelProcessors = new ArrayList<ExternalOxmModelProcessor>();
174 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
175 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
176 OxmModelLoader.loadModels();
177 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
178 parseLatestOxmVersion();
181 private void parseLatestOxmVersion() {
182 int latestVersion = -1;
183 if (oxmVersionContextMap != null) {
184 Iterator it = oxmVersionContextMap.entrySet().iterator();
185 while (it.hasNext()) {
186 Map.Entry pair = (Map.Entry) it.next();
188 String version = pair.getKey().toString();
189 int versionNum = Integer.parseInt(version.substring(1, version.length()));
191 if (versionNum > latestVersion) {
192 latestVersion = versionNum;
193 oxmVersion = pair.getKey().toString();
196 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
199 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
203 public void startup() {
205 // Create the indexes in the search service if they do not already exist.
206 searchAgent.createSearchIndex(entitySearchIndex, entitySearchSchema);
207 searchAgent.createSearchIndex(topographicalSearchIndex, topographicalSearchSchema);
209 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
214 * Convert object to json.
216 * @param object the object
217 * @param pretty the pretty
219 * @throws JsonProcessingException the json processing exception
221 public static String convertObjectToJson(Object object, boolean pretty)
222 throws JsonProcessingException {
223 ObjectWriter ow = null;
226 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
229 ow = new ObjectMapper().writer();
232 return ow.writeValueAsString(object);
235 public void returnWithError(Exchange exchange, String payload, String errorMsg){
236 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE, errorMsg);
237 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, errorMsg, payload);
238 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
242 public void process(Exchange exchange) throws Exception {
244 long startTime = System.currentTimeMillis();
246 String uebPayload = exchange.getIn().getBody().toString();
248 JsonNode uebAsJson =null;
249 ObjectMapper mapper = new ObjectMapper();
251 uebAsJson = mapper.readTree(uebPayload);
252 } catch (IOException e){
253 returnWithError(exchange, uebPayload, "Invalid Payload");
257 // Load the UEB payload data, any errors will result in a failure and discard
258 JSONObject uebObjHeader = getUebHeaderAsJson(uebPayload);
259 if (uebObjHeader == null) {
260 returnWithError(exchange, uebPayload, "Payload is missing event-header");
264 UebEventHeader eventHeader = null;
265 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
267 // Get src domain from header; discard event if not originated from same domain
268 String payloadSrcDomain = eventHeader.getDomain();
269 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
270 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
271 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
272 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
273 "Unrecognized source domain '" + payloadSrcDomain + "'");
275 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
279 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
280 if (oxmJaxbContext == null) {
281 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
282 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, "OXM version mismatch",
285 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
289 String action = eventHeader.getAction();
290 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
291 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
292 "Unrecognized action '" + action + "'", uebPayload);
293 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
294 "Unrecognized action '" + action + "'");
296 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
300 String entityType = eventHeader.getEntityType();
301 if (entityType == null) {
302 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
303 "Payload header missing entity type", uebPayload);
304 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
305 "Payload header missing entity type");
307 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
311 String topEntityType = eventHeader.getTopEntityType();
312 if (topEntityType == null) {
313 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
314 "Payload header missing top entity type", uebPayload);
315 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
316 "Payload header top missing entity type");
318 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
322 String entityLink = eventHeader.getEntityLink();
323 if (entityLink == null) {
324 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
325 "Payload header missing entity link", uebPayload);
326 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
327 "Payload header missing entity link");
329 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
333 // log the fact that all data are in good shape
334 logger.info(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_NONVERBOSE, action,
336 logger.debug(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
340 // Process for building AaiEventEntity object
341 String[] entityTypeArr = entityType.split("-");
342 String oxmEntityType = "";
343 for (String entityWord : entityTypeArr) {
344 oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
347 List<String> searchableAttr =
348 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
349 if (searchableAttr == null) {
350 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
351 "Searchable attribute not found for payload entity type '" + entityType + "'");
352 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
353 "Searchable attribute not found for payload entity type '" + entityType + "'",
356 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
360 String entityPrimaryKeyFieldName =
361 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
362 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
363 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
364 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
365 "Payload missing primary key attribute");
366 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
367 "Payload missing primary key attribute", uebPayload);
369 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
373 AaiEventEntity aaiEventEntity = new AaiEventEntity();
376 * Use the OXM Model to determine the primary key field name based on the entity-type
379 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
380 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
381 aaiEventEntity.setEntityType(entityType);
382 aaiEventEntity.setLink(entityLink);
384 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
385 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
386 "Payload missing searchable attribute for entity type '" + entityType + "'");
387 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
388 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
390 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
396 aaiEventEntity.deriveFields();
398 } catch (NoSuchAlgorithmException e) {
399 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
400 "Cannot create unique SHA digest");
401 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
402 "Cannot create unique SHA digest", uebPayload);
404 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
408 handleSearchServiceOperation(aaiEventEntity, action, entitySearchIndex);
410 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
411 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
414 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
416 VersionedOxmEntities oxmEntities =
417 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion));
421 * 1. If the entity type is "customer", the below check will return true if any nested entityType
422 * in that model could contain a CER based on the OXM model version that has been loaded.
423 * 2. For a DELETE operation on outer/parent entity (handled by the regular flow:
424 * handleSearchServiceOperation()), ignore processing for cross-entity-reference under the
425 * assumption that AAI will push down all required cascade-deletes for nested entities as well
426 * 3. Handling the case where UEB events arrive out of order: CREATE customer is received before
427 * CREATE service-instance.
430 if (!action.equalsIgnoreCase(ACTION_DELETE) && oxmEntities != null
431 && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
433 // We know the model "can" contain a CER reference definition, let's process a bit more
435 HashMap<String, CrossEntityReference> crossEntityRefMap =
436 oxmEntities.getCrossEntityReferences();
438 JSONObject entityJsonObject = getUebEntity(uebPayload);
440 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
442 String parentEntityType = entityType;
444 String targetEntityUrl = entityLink;
446 for (String key : crossEntityRefMap.keySet()) {
449 * if we know service-subscription is in the tree, then we can pull our all instances and
450 * process from there.
453 CrossEntityReference cerDescriptor = crossEntityRefMap.get(key);
455 ArrayList<JsonNode> foundNodes = new ArrayList<JsonNode>();
457 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
459 if (foundNodes.size() > 0) {
461 for (JsonNode n : foundNodes) {
462 if (parentEntityType.equalsIgnoreCase("customer")){
465 * 1. prepare to hand-create url for service-instance
466 * 2. this will break if the URL structure for service-instance changes
468 if (n.has("service-type")){
469 targetEntityUrl += "/service-subscriptions/service-subscription/"
470 + RouterServiceUtil.getNodeFieldAsText(n, "service-type")
471 + "/service-instances/service-instance/";
476 List<String> extractedParentEntityAttributeValues = new ArrayList<String>();
478 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
479 extractedParentEntityAttributeValues);
481 List<JsonNode> nestedTargetEntityInstances = new ArrayList<JsonNode>();
482 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
483 nestedTargetEntityInstances);
485 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
488 * 1. build the AAIEntityType (IndexDocument) based on the extract entity
489 * 2. Get data from ES
491 * 4. Merge ES Doc + AAIEntityType + Extracted Parent Cross-Entity-Reference Values
492 * 5. Put data into ES with ETAG + updated doc
495 // Get the complete URL for target entity
496 if (targetEntityInstance.has("link")) { // nested SI has url mentioned
497 targetEntityUrl = RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
499 } else if (parentEntityType.equalsIgnoreCase("customer") &&
500 targetEntityInstance.has("service-instance-id")){
501 targetEntityUrl += "/" + RouterServiceUtil.getNodeFieldAsText(targetEntityInstance,
502 "service-instance-id");
505 OxmEntityDescriptor searchableDescriptor =
506 oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
508 if (searchableDescriptor != null) {
510 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
512 AaiEventEntity entityToSync = null;
516 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
519 * Ready to do some ElasticSearch ops
522 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
524 .addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
527 entityToSync.setLink(targetEntityUrl);
528 entityToSync.deriveFields();
530 updateCerInEntity(entityToSync);
532 } catch (NoSuchAlgorithmException e) {
537 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
538 "failure to find searchable descriptor for type "
539 + cerDescriptor.getTargetEntityType());
546 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
547 "failed to find 0 instances of cross-entity-reference with entity " + key);
553 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
554 + topEntityType + " does not contain a cross-entity-reference entity");
558 * Process for autosuggestable entities
560 if (oxmEntities != null) {
561 Map<String, OxmEntityDescriptor> rootDescriptor =
562 oxmEntities.getSuggestableEntityDescriptors();
563 if (!rootDescriptor.isEmpty()) {
564 List<String> suggestibleAttributes = extractSuggestableAttr(oxmEntities, entityType);
566 if (suggestibleAttributes == null) {
570 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
571 AggregationEntity ae = new AggregationEntity();
572 ae.setLink(entityLink);
573 ae.deriveFields(uebAsJson);
575 handleSuggestiveSearchData(ae, action, this.aggregationSearchVnfTarget);
578 * It was decided to silently ignore DELETE requests for resources we don't allow to be
579 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
581 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
582 SearchSuggestionPermutation searchSuggestionPermutation =
583 new SearchSuggestionPermutation();
584 List<ArrayList<String>> permutationsOfStatuses =
585 searchSuggestionPermutation.getSuggestionsPermutation(suggestibleAttributes);
587 // Now we have a list of all possible permutations for the status that are
588 // defined for this entity type. Try inserting a document for every combination.
589 for (ArrayList<String> permutation : permutationsOfStatuses) {
590 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
591 suggestionSearchEntity.setEntityType(entityType);
592 suggestionSearchEntity.setSuggestableAttr(permutation);
593 suggestionSearchEntity.setPayloadFromResponse(uebAsJson);
594 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
595 suggestionSearchEntity.setSuggestionInputPermutations(
596 suggestionSearchEntity.generateSuggestionInputPermutations());
598 if (suggestionSearchEntity.isSuggestableDoc()) {
600 suggestionSearchEntity.deriveFields();
601 } catch (NoSuchAlgorithmException e) {
602 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
603 "Cannot create unique SHA digest for search suggestion data. Exception: "
604 + e.getLocalizedMessage());
607 handleSuggestiveSearchData(suggestionSearchEntity, action,
608 this.autoSuggestSearchTarget);
615 long stopTime = System.currentTimeMillis();
617 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
618 String.valueOf(stopTime - startTime));
620 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
624 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
625 // Extract suggestable attributes
626 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
628 if (rootDescriptor == null) {
632 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
638 return desc.getSuggestableAttributes();
641 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
645 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
647 if (rootDescriptor == null) {
651 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
652 return desc.getAlias();
655 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
657 exchange.getOut().setHeader("ResponseType", responseType.toString());
658 exchange.getOut().setBody(additionalInfo);
661 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
662 List<String> suggestableAttr, List<String> alias) {
664 // Extract suggestable attributes
665 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
667 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
668 suggestableAttr = desc.getSuggestableAttributes();
671 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
672 desc = rootDescriptor.get(entityType);
673 alias = desc.getAlias();
677 * Load the UEB JSON payload, any errors would result to a failure case response.
679 private JSONObject getUebHeaderAsJson(String payload) {
681 JSONObject uebJsonObj;
682 JSONObject uebObjHeader;
685 uebJsonObj = new JSONObject(payload);
686 } catch (JSONException e) {
687 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
688 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
692 if (uebJsonObj.has(EVENT_HEADER)) {
693 uebObjHeader = uebJsonObj.getJSONObject(EVENT_HEADER);
695 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
696 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
704 private UebEventHeader initializeUebEventHeader(String payload) {
706 UebEventHeader eventHeader = null;
707 ObjectMapper mapper = new ObjectMapper();
709 // Make sure that were were actually passed in a valid string.
710 if (payload == null || payload.isEmpty()) {
711 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
712 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
717 // Marshal the supplied string into a UebEventHeader object.
719 eventHeader = mapper.readValue(payload, UebEventHeader.class);
720 } catch (JsonProcessingException e) {
721 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
722 } catch (Exception e) {
723 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
726 if (eventHeader != null) {
727 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
735 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
736 String oxmEntityType, String entityType) {
738 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
739 if (entity == null) {
743 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
744 if (list != null && !list.isEmpty()) {
745 String keyName = list.get(0).getName();
746 return keyName.substring(0, keyName.indexOf('/'));
752 private String lookupValueUsingKey(String payload, String key) throws JSONException {
753 JsonNode jsonNode = convertToJsonNode(payload);
754 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
757 private JsonNode convertToJsonNode(String payload) {
759 ObjectMapper mapper = new ObjectMapper();
760 JsonNode jsonNode = null;
762 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
763 } catch (IOException e) {
764 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
766 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
773 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
774 String payload, String action) {
776 boolean hasSearchableAttr = false;
777 for (String searchTagField : searchableAttr) {
778 String searchTagValue = null;
779 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
780 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
782 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
785 if (searchTagValue != null && !searchTagValue.isEmpty()) {
786 hasSearchableAttr = true;
787 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
790 return hasSearchableAttr;
794 * Check if OXM version is available. If available, load it.
796 private DynamicJAXBContext loadOxmContext(String version) {
797 if (version == null) {
798 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
802 return oxmVersionContextMap.get(version);
805 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
806 String oxmEntityType, String entityType, String fieldName) {
808 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
809 if (entity == null) {
814 * Check for searchable XML tag
816 List<String> fieldValues = null;
817 Map<String, String> properties = entity.getDescriptor().getProperties();
818 for (Map.Entry<String, String> entry : properties.entrySet()) {
819 if (entry.getKey().equalsIgnoreCase(fieldName)) {
820 fieldValues = Arrays.asList(entry.getValue().split(","));
828 private JSONObject getUebEntity(String payload) {
829 JSONObject uebJsonObj;
832 uebJsonObj = new JSONObject(payload);
833 } catch (JSONException e) {
834 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
835 "Payload has invalid JSON Format", payload.toString());
836 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
837 "Payload has invalid JSON Format");
841 if (uebJsonObj.has(ENTITY_HEADER)) {
842 return uebJsonObj.getJSONObject(ENTITY_HEADER);
844 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
846 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
851 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
852 OxmEntityDescriptor resultDescriptor) {
853 AaiEventEntity d = new AaiEventEntity();
855 d.setEntityType(resultDescriptor.getEntityName());
857 List<String> primaryKeyValues = new ArrayList<String>();
858 List<String> primaryKeyNames = new ArrayList<String>();
859 String pkeyValue = null;
861 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
862 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
863 if (pkeyValue != null) {
864 primaryKeyValues.add(pkeyValue);
865 primaryKeyNames.add(keyName);
867 // logger.warn("getPopulatedDocument(), pKeyValue is null for entityType = " +
868 // resultDescriptor.getEntityName());
869 logger.error(EntityEventPolicyMsgs.PRIMARY_KEY_NULL_FOR_ENTITY_TYPE,
870 resultDescriptor.getEntityName());
874 final String primaryCompositeKeyValue = RouterServiceUtil.concatArray(primaryKeyValues, "/");
875 d.setEntityPrimaryKeyValue(primaryCompositeKeyValue);
876 final String primaryCompositeKeyName = RouterServiceUtil.concatArray(primaryKeyNames, "/");
877 d.setEntityPrimaryKeyName(primaryCompositeKeyName);
879 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
882 * Based on configuration, use the configured field names for this entity-Type to build a
883 * multi-value collection of search tags for elastic search entity search criteria.
887 for (String searchTagField : searchTagFields) {
888 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
889 if (searchTagValue != null && !searchTagValue.isEmpty()) {
890 d.addSearchTagWithKey(searchTagValue, searchTagField);
897 private void updateCerInEntity(AaiEventEntity aaiEventEntity) {
899 Map<String, List<String>> headers = new HashMap<>();
900 headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));
901 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
903 String entityId = aaiEventEntity.getId();
904 String jsonPayload = aaiEventEntity.getAsJson();
906 // Run the GET to retrieve the ETAG from the search service
907 OperationResult storedEntity = searchAgent.getDocument(entitySearchIndex, entityId);
909 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
911 * NOTES: aaiEventEntity (ie the nested entity) may contain a subset of properties of
912 * the pre-existing object,
913 * so all we want to do is update the CER on the pre-existing object (if needed).
916 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
918 if (etag != null && etag.size() > 0) {
919 headers.put(Headers.IF_MATCH, etag);
921 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
922 entitySearchTarget + entityId, entityId);
925 ArrayList<JsonNode> sourceObject = new ArrayList<JsonNode>();
926 NodeUtils.extractObjectsByKey(
927 NodeUtils.convertJsonStrToJsonNode(storedEntity.getResult()),
928 "content", sourceObject);
930 if (!sourceObject.isEmpty()) {
931 JsonNode node = sourceObject.get(0);
932 final String sourceCer = NodeUtils.extractFieldValueFromObject(node,
933 "crossEntityReferenceValues");
934 String newCer = aaiEventEntity.getCrossReferenceEntityValues();
935 boolean hasNewCer = true;
936 if (sourceCer != null && sourceCer.length() > 0){ // already has CER
937 if ( !sourceCer.contains(newCer)){//don't re-add
938 newCer = sourceCer + ";" + newCer;
945 // Do the PUT with new CER
946 ((ObjectNode)node).put("crossEntityReferenceValues", newCer);
947 jsonPayload = NodeUtils.convertObjectToJson(node, false);
948 searchAgent.putDocument(entitySearchIndex, entityId, jsonPayload, headers);
953 if (storedEntity.getResultCode() == 404) {
954 // entity not found, so attempt to do a PUT
955 searchAgent.putDocument(entitySearchIndex, entityId, aaiEventEntity.getAsJson(), headers);
957 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
958 aaiEventEntity.getId(), "SYNC_ENTITY");
961 } catch (IOException e) {
962 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
963 aaiEventEntity.getId(), "SYNC_ENTITY");
968 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
971 * @param eventEntity Entity/data to use in operation
972 * @param action The operation to perform
973 * @param target Resource to perform the operation on
974 * @param allowDeleteEvent Allow delete operation to be performed on resource
976 private void handleSuggestiveSearchData(DocumentStoreDataEntity eventEntity, String action,
979 Map<String, List<String>> headers = new HashMap<>();
980 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
981 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
983 String entityId = eventEntity.getId();
985 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
986 || action.equalsIgnoreCase(ACTION_UPDATE)) {
987 // Run the GET to retrieve the ETAG from the search service
988 OperationResult storedEntity = searchAgent.getDocument(aggregateGenericVnfIndex, entityId);
990 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
991 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
993 if (etag != null && etag.size() > 0) {
994 headers.put(Headers.IF_MATCH, etag);
996 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1001 String eventEntityStr = eventEntity.getAsJson();
1003 if (eventEntityStr != null) {
1004 List<String> createIndex = new ArrayList<String>();
1005 createIndex.add("true");
1006 headers.put("X-CreateIndex", createIndex);
1007 searchAgent.putDocument(aggregateGenericVnfIndex, entityId, eventEntity.getAsJson(), headers);
1009 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
1010 String eventEntityStr = eventEntity.getAsJson();
1012 if (eventEntityStr != null) {
1013 List<String> createIndex = new ArrayList<String>();
1014 createIndex.add("true");
1015 headers.put("X-CreateIndex", createIndex);
1016 searchAgent.postDocument(aggregateGenericVnfIndex, eventEntityStr, headers);
1018 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1019 // Run the GET to retrieve the ETAG from the search service
1020 OperationResult storedEntity = searchAgent.getDocument(aggregateGenericVnfIndex, entityId);
1022 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1023 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1025 if (etag != null && etag.size() > 0) {
1026 headers.put(Headers.IF_MATCH, etag);
1028 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1032 searchAgent.deleteDocument(aggregateGenericVnfIndex, eventEntity.getId(), headers);
1034 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1038 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1040 } catch (IOException e) {
1041 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1046 private void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity,
1051 Map<String, List<String>> headers = new HashMap<>();
1052 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
1053 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
1055 String entityId = eventEntity.getId();
1057 // System.out.println("aaiEventEntity as json = " + aaiEventEntity.getAsJson());
1059 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
1060 || action.equalsIgnoreCase(ACTION_UPDATE)) {
1062 // Run the GET to retrieve the ETAG from the search service
1063 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
1065 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1066 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1068 if (etag != null && etag.size() > 0) {
1069 headers.put(Headers.IF_MATCH, etag);
1071 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1076 // Write the entity to the search service.
1078 searchAgent.putDocument(index, entityId, eventEntity.getAsJson(), headers);
1080 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
1081 // Write the entry to the search service.
1082 searchAgent.postDocument(index, eventEntity.getAsJson(), headers);
1084 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1085 // Run the GET to retrieve the ETAG from the search service
1086 OperationResult storedEntity = searchAgent.getDocument(index, entityId);
1088 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1089 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1091 if (etag != null && etag.size() > 0) {
1092 headers.put(Headers.IF_MATCH, etag);
1094 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1098 searchAgent.deleteDocument(index, eventEntity.getId(), headers);
1100 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, index,
1104 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1106 } catch (IOException e) {
1107 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1112 private void handleTopographicalData(String payload, String action, String entityType,
1113 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1114 String entityPrimaryKeyFieldValue) {
1116 Map<String, String> topoData = new HashMap<>();
1117 String entityLink = "";
1118 List<String> topographicalAttr =
1119 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1120 if (topographicalAttr == null) {
1121 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1122 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1123 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1124 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1125 payload.toString());
1127 entityLink = lookupValueUsingKey(payload, "entity-link");
1128 for (String topoAttr : topographicalAttr) {
1129 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1131 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1132 entityPrimaryKeyFieldValue, entityLink);
1137 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1138 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1140 TopographicalEntity topoEntity = new TopographicalEntity();
1141 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1142 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1143 topoEntity.setEntityType(entityType);
1144 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1145 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1146 topoEntity.setSelfLink(entityLink);
1148 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1149 entityPrimaryKeyValue));
1150 } catch (NoSuchAlgorithmException e) {
1151 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1152 "Cannot create unique SHA digest for topographical data.");
1155 this.handleSearchServiceOperation(topoEntity, action, this.topographicalSearchTarget);
1159 // put this here until we find a better spot
1161 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1163 * @param suburis the list of substrings to concatenate together
1164 * @return the concatenated list of substrings
1166 public static String concatSubUri(String... suburis) {
1167 String finalUri = "";
1169 for (String suburi : suburis) {
1171 if (suburi != null) {
1172 // Remove any leading / since we only want to append /
1173 suburi = suburi.replaceFirst("^/*", "");
1175 // Add a trailing / if one isn't already there
1176 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";