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.sun.jersey.core.util.MultivaluedMapImpl;
33 import java.io.BufferedReader;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.nio.charset.StandardCharsets;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collection;
41 import java.util.HashMap;
42 import java.util.Iterator;
43 import java.util.List;
45 import java.util.UUID;
47 import javax.ws.rs.core.MediaType;
48 import javax.ws.rs.core.MultivaluedMap;
50 import org.apache.camel.Exchange;
51 import org.apache.camel.Processor;
52 import org.eclipse.jetty.util.security.Password;
53 import org.eclipse.persistence.dynamic.DynamicType;
54 import org.eclipse.persistence.internal.helper.DatabaseField;
55 import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
56 import org.json.JSONException;
57 import org.json.JSONObject;
58 import org.openecomp.cl.api.Logger;
59 import org.openecomp.cl.eelf.LoggerFactory;
60 import org.openecomp.cl.mdc.MdcContext;
61 import org.openecomp.datarouter.entity.AaiEventEntity;
62 import org.openecomp.datarouter.entity.AggregationEntity;
63 import org.openecomp.datarouter.entity.DocumentStoreDataEntity;
64 import org.openecomp.datarouter.entity.OxmEntityDescriptor;
65 import org.openecomp.datarouter.entity.SuggestionSearchEntity;
66 import org.openecomp.datarouter.entity.TopographicalEntity;
67 import org.openecomp.datarouter.entity.UebEventHeader;
68 import org.openecomp.datarouter.logging.DataRouterMsgs;
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;
85 public class EntityEventPolicy implements Processor {
87 public static final String additionalInfo = "Response of AAIEntityEventPolicy";
88 private static final String entitySearchSchema = "entitysearch_schema.json";
89 private static final String topographicalSearchSchema = "topographysearch_schema.json";
90 private Collection<ExternalOxmModelProcessor> externalOxmModelProcessors;
91 RestClient searchClient = null;
93 private final String EVENT_HEADER = "event-header";
94 private final String ENTITY_HEADER = "entity";
95 private final String ACTION_CREATE = "create";
96 private final String ACTION_DELETE = "delete";
97 private final String ACTION_UPDATE = "update";
98 private final String PROCESS_AAI_EVENT = "Process AAI Event";
99 private final String TOPO_LAT = "latitude";
100 private final String TOPO_LONG = "longitude";
102 private final List<String> SUPPORTED_ACTIONS =
103 Arrays.asList(ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE);
105 Map<String, DynamicJAXBContext> oxmVersionContextMap = new HashMap<>();
106 private String oxmVersion = null;
108 private String entityIndexTarget = null;
109 private String entitySearchTarget = null;
110 private String topographicalIndexTarget = null;
111 private String topographicalSearchTarget = null;
112 private String autoSuggestSearchTarget = null;
113 private String aggregationSearchVnfTarget = null;
115 private String srcDomain;
117 private Logger logger;
118 private Logger metricsLogger;
119 private Logger auditLogger;
121 public enum ResponseType {
122 SUCCESS, PARTIAL_SUCCESS, FAILURE;
125 public EntityEventPolicy(EntityEventPolicyConfig config) {
126 LoggerFactory loggerFactoryInstance = LoggerFactory.getInstance();
127 logger = loggerFactoryInstance.getLogger(EntityEventPolicy.class.getName());
128 metricsLogger = loggerFactoryInstance.getMetricsLogger(EntityEventPolicy.class.getName());
129 auditLogger = loggerFactoryInstance.getAuditLogger(EntityEventPolicy.class.getName());
131 srcDomain = config.getSourceDomain();
134 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
135 config.getSearchEntitySearchIndex());
138 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
139 config.getSearchEntitySearchIndex(), config.getSearchEndpointDocuments());
141 topographicalIndexTarget =
142 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
143 config.getSearchTopographySearchIndex());
145 topographicalSearchTarget = EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(),
146 config.getSearchEndpoint(), config.getSearchTopographySearchIndex());
148 // Create REST client for search service
149 searchClient = new RestClient().validateServerHostname(false).validateServerCertChain(true)
150 .clientCertFile(DataRouterConstants.DR_HOME_AUTH + config.getSearchCertName())
151 .clientCertPassword(Password.deobfuscate(config.getSearchKeystorePwd()))
152 .trustStore(DataRouterConstants.DR_HOME_AUTH + config.getSearchKeystore());
154 autoSuggestSearchTarget =
155 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
156 config.getSearchEntityAutoSuggestIndex(), config.getSearchEndpointDocuments());
158 aggregationSearchVnfTarget =
159 EntityEventPolicy.concatSubUri(config.getSearchBaseUrl(), config.getSearchEndpoint(),
160 config.getSearchAggregationVnfIndex(), config.getSearchEndpointDocuments());
162 this.externalOxmModelProcessors = new ArrayList<ExternalOxmModelProcessor>();
163 this.externalOxmModelProcessors.add(EntityOxmReferenceHelper.getInstance());
164 OxmModelLoader.registerExternalOxmModelProcessors(externalOxmModelProcessors);
165 OxmModelLoader.loadModels();
166 oxmVersionContextMap = OxmModelLoader.getVersionContextMap();
167 parseLatestOxmVersion();
170 private void parseLatestOxmVersion() {
171 int latestVersion = -1;
172 if (oxmVersionContextMap != null) {
173 Iterator it = oxmVersionContextMap.entrySet().iterator();
174 while (it.hasNext()) {
175 Map.Entry pair = (Map.Entry) it.next();
177 String version = pair.getKey().toString();
178 int versionNum = Integer.parseInt(version.substring(1, 2));
180 if (versionNum > latestVersion) {
181 latestVersion = versionNum;
182 oxmVersion = pair.getKey().toString();
185 logger.info(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_FOUND, pair.getKey().toString());
188 logger.error(EntityEventPolicyMsgs.PROCESS_OXM_MODEL_MISSING, "");
192 public void startup() {
194 // Create the indexes in the search service if they do not already exist.
195 createSearchIndex(entityIndexTarget, entitySearchSchema);
196 createSearchIndex(topographicalIndexTarget, topographicalSearchSchema);
198 logger.info(EntityEventPolicyMsgs.ENTITY_EVENT_POLICY_REGISTERED);
202 * Creates an index through the search db abstraction
204 * @param searchRESTClient
205 * the REST client configured to contact the search db
207 * @param searchTarget
208 * the URL to attempt to create the search index
209 * @param schemaLocation
210 * the location of the mappings file for the index
212 private void createSearchIndex(String searchTarget, String schemaLocation) {
214 logger.debug("Creating search index, searchTarget = " + searchTarget + ", schemaLocation = " + schemaLocation);
216 MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
217 headers.put("Accept", Arrays.asList("application/json"));
218 headers.put(Headers.FROM_APP_ID, Arrays.asList("DL"));
219 headers.put(Headers.TRANSACTION_ID, Arrays.asList(UUID.randomUUID().toString()));
223 OperationResult result = searchClient.put(searchTarget, loadFileData(schemaLocation), headers,
224 MediaType.APPLICATION_JSON_TYPE, null);
226 if (!HttpUtil.isHttpResponseClassSuccess(result.getResultCode())) {
227 logger.error(EntityEventPolicyMsgs.FAIL_TO_CREATE_SEARCH_INDEX, searchTarget, result.getFailureCause());
229 logger.info(EntityEventPolicyMsgs.SEARCH_INDEX_CREATE_SUCCESS, searchTarget);
232 } catch (Exception e) {
233 logger.error(EntityEventPolicyMsgs.FAIL_TO_CREATE_SEARCH_INDEX, searchTarget, e.getLocalizedMessage());
238 * Convenience method to load up all the data from a file into a string
240 * @param filename the filename to read from disk
241 * @return the data contained within the file
244 protected String loadFileData(String filename) throws Exception {
245 StringBuilder data = new StringBuilder();
247 BufferedReader in = new BufferedReader(new InputStreamReader(
248 EntityEventPolicy.class.getClassLoader().getResourceAsStream("/" + filename),
249 StandardCharsets.UTF_8));
252 while ((line = in.readLine()) != null) {
255 } catch (Exception e) {
256 throw new Exception("Failed to read from file = " + filename + ".", e);
259 return data.toString();
264 * Convert object to json.
266 * @param object the object
267 * @param pretty the pretty
269 * @throws JsonProcessingException the json processing exception
271 public static String convertObjectToJson(Object object, boolean pretty)
272 throws JsonProcessingException {
273 ObjectWriter ow = null;
276 ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
279 ow = new ObjectMapper().writer();
282 return ow.writeValueAsString(object);
285 public void returnWithError(Exchange exchange, String payload, String errorMsg){
286 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE, errorMsg);
287 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, errorMsg, payload);
288 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
292 public void process(Exchange exchange) throws Exception {
294 long startTime = System.currentTimeMillis();
296 String uebPayload = exchange.getIn().getBody().toString();
298 JsonNode uebAsJson =null;
299 ObjectMapper mapper = new ObjectMapper();
301 uebAsJson = mapper.readTree(uebPayload);
302 } catch (IOException e){
303 returnWithError(exchange, uebPayload, "Invalid Payload");
307 // Load the UEB payload data, any errors will result in a failure and discard
308 JSONObject uebObjHeader = getUebHeaderAsJson(uebPayload);
309 if (uebObjHeader == null) {
310 returnWithError(exchange, uebPayload, "Payload is missing event-header");
314 UebEventHeader eventHeader = null;
315 eventHeader = initializeUebEventHeader(uebObjHeader.toString());
317 // Get src domain from header; discard event if not originated from same domain
318 String payloadSrcDomain = eventHeader.getDomain();
319 if (payloadSrcDomain == null || !payloadSrcDomain.equalsIgnoreCase(this.srcDomain)) {
320 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
321 "Unrecognized source domain '" + payloadSrcDomain + "'", uebPayload);
322 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
323 "Unrecognized source domain '" + payloadSrcDomain + "'");
325 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
329 DynamicJAXBContext oxmJaxbContext = loadOxmContext(oxmVersion.toLowerCase());
330 if (oxmJaxbContext == null) {
331 logger.error(EntityEventPolicyMsgs.OXM_VERSION_NOT_SUPPORTED, oxmVersion);
332 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE, "OXM version mismatch",
335 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
339 String action = eventHeader.getAction();
340 if (action == null || !SUPPORTED_ACTIONS.contains(action.toLowerCase())) {
341 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
342 "Unrecognized action '" + action + "'", uebPayload);
343 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
344 "Unrecognized action '" + action + "'");
346 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
350 String entityType = eventHeader.getEntityType();
351 if (entityType == null) {
352 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
353 "Payload header missing entity type", uebPayload);
354 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
355 "Payload header missing entity type");
357 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
361 String topEntityType = eventHeader.getTopEntityType();
362 if (topEntityType == null) {
363 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
364 "Payload header missing top entity type", uebPayload);
365 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
366 "Payload header top missing entity type");
368 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
372 String entityLink = eventHeader.getEntityLink();
373 if (entityLink == null) {
374 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
375 "Payload header missing entity link", uebPayload);
376 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
377 "Payload header missing entity link");
379 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
383 // log the fact that all data are in good shape
384 logger.info(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_NONVERBOSE, action,
386 logger.debug(EntityEventPolicyMsgs.PROCESS_AAI_ENTITY_EVENT_POLICY_VERBOSE, action, entityType,
390 // Process for building AaiEventEntity object
391 String[] entityTypeArr = entityType.split("-");
392 String oxmEntityType = "";
393 for (String entityWord : entityTypeArr) {
394 oxmEntityType += entityWord.substring(0, 1).toUpperCase() + entityWord.substring(1);
397 List<String> searchableAttr =
398 getOxmAttributes(uebPayload, oxmJaxbContext, oxmEntityType, entityType, "searchable");
399 if (searchableAttr == null) {
400 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
401 "Searchable attribute not found for payload entity type '" + entityType + "'");
402 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
403 "Searchable attribute not found for payload entity type '" + entityType + "'",
406 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
410 String entityPrimaryKeyFieldName =
411 getEntityPrimaryKeyFieldName(oxmJaxbContext, uebPayload, oxmEntityType, entityType);
412 String entityPrimaryKeyFieldValue = lookupValueUsingKey(uebPayload, entityPrimaryKeyFieldName);
413 if (entityPrimaryKeyFieldValue == null || entityPrimaryKeyFieldValue.isEmpty()) {
414 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
415 "Payload missing primary key attribute");
416 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
417 "Payload missing primary key attribute", uebPayload);
419 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
423 AaiEventEntity aaiEventEntity = new AaiEventEntity();
426 * Use the OXM Model to determine the primary key field name based on the entity-type
429 aaiEventEntity.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
430 aaiEventEntity.setEntityPrimaryKeyValue(entityPrimaryKeyFieldValue);
431 aaiEventEntity.setEntityType(entityType);
432 aaiEventEntity.setLink(entityLink);
434 if (!getSearchTags(aaiEventEntity, searchableAttr, uebPayload, action)) {
435 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
436 "Payload missing searchable attribute for entity type '" + entityType + "'");
437 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
438 "Payload missing searchable attribute for entity type '" + entityType + "'", uebPayload);
440 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
446 aaiEventEntity.deriveFields();
448 } catch (NoSuchAlgorithmException e) {
449 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
450 "Cannot create unique SHA digest");
451 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
452 "Cannot create unique SHA digest", uebPayload);
454 setResponse(exchange, ResponseType.FAILURE, additionalInfo);
458 handleSearchServiceOperation(aaiEventEntity, action, this.entitySearchTarget);
460 handleTopographicalData(uebPayload, action, entityType, oxmEntityType, oxmJaxbContext,
461 entityPrimaryKeyFieldName, entityPrimaryKeyFieldValue);
464 * Use the versioned OXM Entity class to get access to cross-entity reference helper collections
466 VersionedOxmEntities oxmEntities =
467 EntityOxmReferenceHelper.getInstance().getVersionedOxmEntities(Version.valueOf(oxmVersion));
470 * If the entity type is "customer", the below check will return true if any nested entityType
471 * in that model could contain a CER based on the OXM model version that has been loaded.
474 if (oxmEntities != null && oxmEntities.entityModelContainsCrossEntityReference(topEntityType)) {
476 // We know the model "can" contain a CER reference definition, let's process a bit more
478 HashMap<String, CrossEntityReference> crossEntityRefMap =
479 oxmEntities.getCrossEntityReferences();
481 JSONObject entityJsonObject = getUebEntity(uebPayload);
483 JsonNode entityJsonNode = convertToJsonNode(entityJsonObject.toString());
485 for (String key : crossEntityRefMap.keySet()) {
488 * if we know service-subscription is in the tree, then we can pull our all instances and
489 * process from there.
492 CrossEntityReference cerDescriptor = crossEntityRefMap.get(key);
494 ArrayList<JsonNode> foundNodes = new ArrayList<JsonNode>();
496 RouterServiceUtil.extractObjectsByKey(entityJsonNode, key, foundNodes);
498 if (foundNodes.size() > 0) {
500 for (JsonNode n : foundNodes) {
502 List<String> extractedParentEntityAttributeValues = new ArrayList<String>();
504 RouterServiceUtil.extractFieldValuesFromObject(n, cerDescriptor.getAttributeNames(),
505 extractedParentEntityAttributeValues);
507 List<JsonNode> nestedTargetEntityInstances = new ArrayList<JsonNode>();
508 RouterServiceUtil.extractObjectsByKey(n, cerDescriptor.getTargetEntityType(),
509 nestedTargetEntityInstances);
511 for (JsonNode targetEntityInstance : nestedTargetEntityInstances) {
514 * 1. build the AAIEntityType (IndexDocument) based on the extract entity
515 * 2. Get data from ES
517 * 4. Merge ES Doc + AAIEntityType + Extracted Parent Cross-Entity-Reference Values
518 * 5. Put data into ES with ETAG + updated doc
521 OxmEntityDescriptor searchableDescriptor =
522 oxmEntities.getSearchableEntityDescriptor(cerDescriptor.getTargetEntityType());
524 if (searchableDescriptor != null) {
526 if (!searchableDescriptor.getSearchableAttributes().isEmpty()) {
528 AaiEventEntity entityToSync = null;
532 entityToSync = getPopulatedEntity(targetEntityInstance, searchableDescriptor);
535 * Ready to do some ElasticSearch ops
538 for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) {
540 .addCrossEntityReferenceValue(parentCrossEntityReferenceAttributeValue);
543 entityToSync.setEntityPrimaryKeyName(entityPrimaryKeyFieldName);
544 entityToSync.setLink(entityLink);
545 entityToSync.deriveFields();
547 syncEntity(entityToSync);
549 } catch (NoSuchAlgorithmException e) {
554 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
555 "failure to find searchable descriptor for type "
556 + cerDescriptor.getTargetEntityType());
563 logger.debug(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC,
564 "failed to find 0 instances of cross-entity-reference with entity " + key);
570 logger.info(EntityEventPolicyMsgs.CROSS_ENTITY_REFERENCE_SYNC, "skipped due to OXM model for "
571 + topEntityType + " does not contain a cross-entity-reference entity");
575 * Process for autosuggestable entities
577 if (oxmEntities != null) {
578 Map<String, OxmEntityDescriptor> rootDescriptor =
579 oxmEntities.getSuggestableEntityDescriptors();
580 if (!rootDescriptor.isEmpty()) {
581 List<String> suggestibleAttributes = extractSuggestableAttr(oxmEntities, entityType);
583 if (suggestibleAttributes == null) {
587 List<String> suggestionAliases = extractAliasForSuggestableEntity(oxmEntities, entityType);
588 AggregationEntity ae = new AggregationEntity();
589 ae.setLink(entityLink);
590 ae.deriveFields(uebAsJson);
592 handleSuggestiveSearchData(ae, action, this.aggregationSearchVnfTarget);
595 * It was decided to silently ignore DELETE requests for resources we don't allow to be
596 * deleted. e.g. auto-suggestion deletion is not allowed while aggregation deletion is.
598 if (!ACTION_DELETE.equalsIgnoreCase(action)) {
599 SearchSuggestionPermutation searchSuggestionPermutation =
600 new SearchSuggestionPermutation();
601 List<ArrayList<String>> permutationsOfStatuses =
602 searchSuggestionPermutation.getSuggestionsPermutation(suggestibleAttributes);
604 // Now we have a list of all possible permutations for the status that are
605 // defined for this entity type. Try inserting a document for every combination.
606 for (ArrayList<String> permutation : permutationsOfStatuses) {
607 SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity();
608 suggestionSearchEntity.setEntityType(entityType);
609 suggestionSearchEntity.setSuggestableAttr(permutation);
610 suggestionSearchEntity.setPayloadFromResponse(uebAsJson);
611 suggestionSearchEntity.setEntityTypeAliases(suggestionAliases);
612 suggestionSearchEntity.setSuggestionInputPermutations(
613 suggestionSearchEntity.generateSuggestionInputPermutations());
615 if (suggestionSearchEntity.isSuggestableDoc()) {
617 suggestionSearchEntity.deriveFields();
618 } catch (NoSuchAlgorithmException e) {
619 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_SEARCH_SUGGESTION_DATA,
620 "Cannot create unique SHA digest for search suggestion data. Exception: "
621 + e.getLocalizedMessage());
624 handleSuggestiveSearchData(suggestionSearchEntity, action,
625 this.autoSuggestSearchTarget);
632 long stopTime = System.currentTimeMillis();
634 metricsLogger.info(EntityEventPolicyMsgs.OPERATION_RESULT_NO_ERRORS, PROCESS_AAI_EVENT,
635 String.valueOf(stopTime - startTime));
637 setResponse(exchange, ResponseType.SUCCESS, additionalInfo);
641 public List<String> extractSuggestableAttr(VersionedOxmEntities oxmEntities, String entityType) {
642 // Extract suggestable attributes
643 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
645 if (rootDescriptor == null) {
649 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
655 return desc.getSuggestableAttributes();
658 public List<String> extractAliasForSuggestableEntity(VersionedOxmEntities oxmEntities,
662 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getEntityAliasDescriptors();
664 if (rootDescriptor == null) {
668 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
669 return desc.getAlias();
672 private void setResponse(Exchange exchange, ResponseType responseType, String additionalInfo) {
674 exchange.getOut().setHeader("ResponseType", responseType.toString());
675 exchange.getOut().setBody(additionalInfo);
678 public void extractDetailsForAutosuggestion(VersionedOxmEntities oxmEntities, String entityType,
679 List<String> suggestableAttr, List<String> alias) {
681 // Extract suggestable attributes
682 Map<String, OxmEntityDescriptor> rootDescriptor = oxmEntities.getSuggestableEntityDescriptors();
684 OxmEntityDescriptor desc = rootDescriptor.get(entityType);
685 suggestableAttr = desc.getSuggestableAttributes();
688 rootDescriptor = oxmEntities.getEntityAliasDescriptors();
689 desc = rootDescriptor.get(entityType);
690 alias = desc.getAlias();
694 * Load the UEB JSON payload, any errors would result to a failure case response.
696 private JSONObject getUebHeaderAsJson(String payload) {
698 JSONObject uebJsonObj;
699 JSONObject uebObjHeader;
702 uebJsonObj = new JSONObject(payload);
703 } catch (JSONException e) {
704 logger.debug(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
705 logger.error(EntityEventPolicyMsgs.UEB_INVALID_PAYLOAD_JSON_FORMAT, payload);
709 if (uebJsonObj.has(EVENT_HEADER)) {
710 uebObjHeader = uebJsonObj.getJSONObject(EVENT_HEADER);
712 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
713 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
721 private UebEventHeader initializeUebEventHeader(String payload) {
723 UebEventHeader eventHeader = null;
724 ObjectMapper mapper = new ObjectMapper();
726 // Make sure that were were actually passed in a valid string.
727 if (payload == null || payload.isEmpty()) {
728 logger.debug(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
729 logger.error(EntityEventPolicyMsgs.UEB_FAILED_TO_PARSE_PAYLOAD, EVENT_HEADER);
734 // Marshal the supplied string into a UebEventHeader object.
736 eventHeader = mapper.readValue(payload, UebEventHeader.class);
737 } catch (JsonProcessingException e) {
738 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
739 } catch (Exception e) {
740 logger.error(EntityEventPolicyMsgs.UEB_FAILED_UEBEVENTHEADER_CONVERSION, e.toString());
743 if (eventHeader != null) {
744 logger.debug(EntityEventPolicyMsgs.UEB_EVENT_HEADER_PARSED, eventHeader.toString());
752 private String getEntityPrimaryKeyFieldName(DynamicJAXBContext oxmJaxbContext, String payload,
753 String oxmEntityType, String entityType) {
755 DynamicType entity = oxmJaxbContext.getDynamicType(oxmEntityType);
756 if (entity == null) {
760 List<DatabaseField> list = entity.getDescriptor().getPrimaryKeyFields();
761 if (list != null && !list.isEmpty()) {
762 String keyName = list.get(0).getName();
763 return keyName.substring(0, keyName.indexOf('/'));
769 private String lookupValueUsingKey(String payload, String key) throws JSONException {
770 JsonNode jsonNode = convertToJsonNode(payload);
771 return RouterServiceUtil.recursivelyLookupJsonPayload(jsonNode, key);
774 private JsonNode convertToJsonNode(String payload) {
776 ObjectMapper mapper = new ObjectMapper();
777 JsonNode jsonNode = null;
779 jsonNode = mapper.readTree(mapper.getJsonFactory().createJsonParser(payload));
780 } catch (IOException e) {
781 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
783 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
790 private boolean getSearchTags(AaiEventEntity aaiEventEntity, List<String> searchableAttr,
791 String payload, String action) {
793 boolean hasSearchableAttr = false;
794 for (String searchTagField : searchableAttr) {
795 String searchTagValue = null;
796 if (searchTagField.equalsIgnoreCase(aaiEventEntity.getEntityPrimaryKeyName())) {
797 searchTagValue = aaiEventEntity.getEntityPrimaryKeyValue();
799 searchTagValue = this.lookupValueUsingKey(payload, searchTagField);
802 if (searchTagValue != null && !searchTagValue.isEmpty()) {
803 hasSearchableAttr = true;
804 aaiEventEntity.addSearchTagWithKey(searchTagValue, searchTagField);
807 return hasSearchableAttr;
811 * Check if OXM version is available. If available, load it.
813 private DynamicJAXBContext loadOxmContext(String version) {
814 if (version == null) {
815 logger.error(EntityEventPolicyMsgs.FAILED_TO_FIND_OXM_VERSION, version);
819 return oxmVersionContextMap.get(version);
822 private List<String> getOxmAttributes(String payload, DynamicJAXBContext oxmJaxbContext,
823 String oxmEntityType, String entityType, String fieldName) {
825 DynamicType entity = (DynamicType) oxmJaxbContext.getDynamicType(oxmEntityType);
826 if (entity == null) {
831 * Check for searchable XML tag
833 List<String> fieldValues = null;
834 Map<String, String> properties = entity.getDescriptor().getProperties();
835 for (Map.Entry<String, String> entry : properties.entrySet()) {
836 if (entry.getKey().equalsIgnoreCase(fieldName)) {
837 fieldValues = Arrays.asList(entry.getValue().split(","));
845 private JSONObject getUebEntity(String payload) {
846 JSONObject uebJsonObj;
849 uebJsonObj = new JSONObject(payload);
850 } catch (JSONException e) {
851 logger.debug(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_VERBOSE,
852 "Payload has invalid JSON Format", payload.toString());
853 logger.error(EntityEventPolicyMsgs.DISCARD_AAI_EVENT_NONVERBOSE,
854 "Payload has invalid JSON Format");
858 if (uebJsonObj.has(ENTITY_HEADER)) {
859 return uebJsonObj.getJSONObject(ENTITY_HEADER);
861 logger.debug(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing",
863 logger.error(EntityEventPolicyMsgs.FAILED_TO_PARSE_UEB_PAYLOAD, ENTITY_HEADER + " missing");
868 protected AaiEventEntity getPopulatedEntity(JsonNode entityNode,
869 OxmEntityDescriptor resultDescriptor) {
870 AaiEventEntity d = new AaiEventEntity();
872 d.setEntityType(resultDescriptor.getEntityName());
874 List<String> primaryKeyValues = new ArrayList<String>();
875 String pkeyValue = null;
877 for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) {
878 pkeyValue = RouterServiceUtil.getNodeFieldAsText(entityNode, keyName);
879 if (pkeyValue != null) {
880 primaryKeyValues.add(pkeyValue);
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);
892 final List<String> searchTagFields = resultDescriptor.getSearchableAttributes();
895 * Based on configuration, use the configured field names for this entity-Type to build a
896 * multi-value collection of search tags for elastic search entity search criteria.
900 for (String searchTagField : searchTagFields) {
901 String searchTagValue = RouterServiceUtil.getNodeFieldAsText(entityNode, searchTagField);
902 if (searchTagValue != null && !searchTagValue.isEmpty()) {
903 d.addSearchTagWithKey(searchTagValue, searchTagField);
910 private void syncEntity(AaiEventEntity aaiEventEntity) {
912 Map<String, List<String>> headers = new HashMap<>();
913 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
914 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
916 String entityId = aaiEventEntity.getId();
918 // Run the GET to retrieve the ETAG from the search service
919 OperationResult storedEntity =
920 searchClient.get(entitySearchTarget + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
922 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
923 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
925 if (etag != null && etag.size() > 0) {
926 headers.put(Headers.IF_MATCH, etag);
928 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE,
929 entitySearchTarget + entityId, entityId);
932 searchClient.put(entitySearchTarget + entityId, aaiEventEntity.getAsJson(), headers,
933 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
936 if (storedEntity.getResultCode() == 404) {
937 // entity not found, so attempt to do a PUT
938 searchClient.put(entitySearchTarget + entityId, aaiEventEntity.getAsJson(), headers,
939 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
941 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
942 aaiEventEntity.getId(), "SYNC_ENTITY");
945 } catch (IOException e) {
946 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE,
947 aaiEventEntity.getId(), "SYNC_ENTITY");
952 * Perform create, read, update or delete (CRUD) operation on search engine's suggestive search
955 * @param eventEntity Entity/data to use in operation
956 * @param action The operation to perform
957 * @param target Resource to perform the operation on
958 * @param allowDeleteEvent Allow delete operation to be performed on resource
960 private void handleSuggestiveSearchData(DocumentStoreDataEntity eventEntity, String action,
963 Map<String, List<String>> headers = new HashMap<>();
964 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
965 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
967 String entityId = eventEntity.getId();
969 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
970 || action.equalsIgnoreCase(ACTION_UPDATE)) {
971 // Run the GET to retrieve the ETAG from the search service
972 OperationResult storedEntity =
973 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
975 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
976 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
978 if (etag != null && etag.size() > 0) {
979 headers.put(Headers.IF_MATCH, etag);
981 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
986 String eventEntityStr = eventEntity.getAsJson();
988 if (eventEntityStr != null) {
989 searchClient.put(target + entityId, eventEntity.getAsJson(), headers,
990 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
992 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
993 String eventEntityStr = eventEntity.getAsJson();
995 if (eventEntityStr != null) {
996 searchClient.post(target, eventEntityStr, headers, MediaType.APPLICATION_JSON_TYPE,
997 MediaType.APPLICATION_JSON_TYPE);
999 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1000 // Run the GET to retrieve the ETAG from the search service
1001 OperationResult storedEntity =
1002 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
1004 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1005 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1007 if (etag != null && etag.size() > 0) {
1008 headers.put(Headers.IF_MATCH, etag);
1010 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1014 searchClient.delete(target + eventEntity.getId(), headers, null);
1016 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1020 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1022 } catch (IOException e) {
1023 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1028 private void handleSearchServiceOperation(DocumentStoreDataEntity eventEntity, String action,
1032 Map<String, List<String>> headers = new HashMap<>();
1033 headers.put(Headers.FROM_APP_ID, Arrays.asList("DataLayer"));
1034 headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));
1036 String entityId = eventEntity.getId();
1038 // System.out.println("aaiEventEntity as json = " + aaiEventEntity.getAsJson());
1040 if ((action.equalsIgnoreCase(ACTION_CREATE) && entityId != null)
1041 || action.equalsIgnoreCase(ACTION_UPDATE)) {
1043 // Run the GET to retrieve the ETAG from the search service
1044 OperationResult storedEntity =
1045 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
1047 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1048 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1050 if (etag != null && etag.size() > 0) {
1051 headers.put(Headers.IF_MATCH, etag);
1053 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1058 searchClient.put(target + entityId, eventEntity.getAsJson(), headers,
1059 MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);
1060 } else if (action.equalsIgnoreCase(ACTION_CREATE)) {
1061 searchClient.post(target, eventEntity.getAsJson(), headers, MediaType.APPLICATION_JSON_TYPE,
1062 MediaType.APPLICATION_JSON_TYPE);
1063 } else if (action.equalsIgnoreCase(ACTION_DELETE)) {
1064 // Run the GET to retrieve the ETAG from the search service
1065 OperationResult storedEntity =
1066 searchClient.get(target + entityId, headers, MediaType.APPLICATION_JSON_TYPE);
1068 if (HttpUtil.isHttpResponseClassSuccess(storedEntity.getResultCode())) {
1069 List<String> etag = storedEntity.getHeaders().get(Headers.ETAG);
1071 if (etag != null && etag.size() > 0) {
1072 headers.put(Headers.IF_MATCH, etag);
1074 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1078 searchClient.delete(target + eventEntity.getId(), headers, null);
1080 logger.error(EntityEventPolicyMsgs.NO_ETAG_AVAILABLE_FAILURE, target + entityId,
1084 logger.error(EntityEventPolicyMsgs.ENTITY_OPERATION_NOT_SUPPORTED, action);
1086 } catch (IOException e) {
1087 logger.error(EntityEventPolicyMsgs.FAILED_TO_UPDATE_ENTITY_IN_DOCSTORE, eventEntity.getId(),
1092 private void handleTopographicalData(String payload, String action, String entityType,
1093 String oxmEntityType, DynamicJAXBContext oxmJaxbContext, String entityPrimaryKeyFieldName,
1094 String entityPrimaryKeyFieldValue) {
1096 Map<String, String> topoData = new HashMap<>();
1097 String entityLink = "";
1098 List<String> topographicalAttr =
1099 getOxmAttributes(payload, oxmJaxbContext, oxmEntityType, entityType, "geoProps");
1100 if (topographicalAttr == null) {
1101 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_NONVERBOSE,
1102 "Topograhical attribute not found for payload entity type '" + entityType + "'");
1103 logger.debug(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1104 "Topograhical attribute not found for payload entity type '" + entityType + "'",
1105 payload.toString());
1107 entityLink = lookupValueUsingKey(payload, "entity-link");
1108 for (String topoAttr : topographicalAttr) {
1109 topoData.put(topoAttr, lookupValueUsingKey(payload, topoAttr));
1111 updateTopographicalSearchDb(topoData, entityType, action, entityPrimaryKeyFieldName,
1112 entityPrimaryKeyFieldValue, entityLink);
1117 private void updateTopographicalSearchDb(Map<String, String> topoData, String entityType,
1118 String action, String entityPrimaryKeyName, String entityPrimaryKeyValue, String entityLink) {
1120 TopographicalEntity topoEntity = new TopographicalEntity();
1121 topoEntity.setEntityPrimaryKeyName(entityPrimaryKeyName);
1122 topoEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue);
1123 topoEntity.setEntityType(entityType);
1124 topoEntity.setLatitude(topoData.get(TOPO_LAT));
1125 topoEntity.setLongitude(topoData.get(TOPO_LONG));
1126 topoEntity.setSelfLink(entityLink);
1128 topoEntity.setId(TopographicalEntity.generateUniqueShaDigest(entityType, entityPrimaryKeyName,
1129 entityPrimaryKeyValue));
1130 } catch (NoSuchAlgorithmException e) {
1131 logger.error(EntityEventPolicyMsgs.DISCARD_UPDATING_TOPOGRAPHY_DATA_VERBOSE,
1132 "Cannot create unique SHA digest for topographical data.");
1135 this.handleSearchServiceOperation(topoEntity, action, this.topographicalSearchTarget);
1139 // put this here until we find a better spot
1141 * Helper utility to concatenate substrings of a URI together to form a proper URI.
1143 * @param suburis the list of substrings to concatenate together
1144 * @return the concatenated list of substrings
1146 public static String concatSubUri(String... suburis) {
1147 String finalUri = "";
1149 for (String suburi : suburis) {
1151 if (suburi != null) {
1152 // Remove any leading / since we only want to append /
1153 suburi = suburi.replaceFirst("^/*", "");
1155 // Add a trailing / if one isn't already there
1156 finalUri += suburi.endsWith("/") ? suburi : suburi + "/";