diff options
author | richarv <richard.vondadelszen@amdocs.com> | 2018-01-15 13:22:40 -0500 |
---|---|---|
committer | richarv <richard.vondadelszen@amdocs.com> | 2018-01-16 10:47:37 -0500 |
commit | a31c1cb7240b28f9ee5b0a4a847545ae3ea8039d (patch) | |
tree | 65d54b3e1bdcdc20873e4e744f7714f6d4afedff | |
parent | 74d351a550b50ba963fa0db6053ed4bd501a9868 (diff) |
Adding subscription api from separate repo
Change-Id: Ic4c0ef849501b95059c806beab16f2ccf3d16695
Signed-off-by: richarv <richard.vondadelszen@amdocs.com>
Issue-ID: AAI-660
Signed-off-by: richarv <richard.vondadelszen@amdocs.com>
16 files changed, 802 insertions, 176 deletions
diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java b/src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java index 288ce8e..faea602 100644 --- a/src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java +++ b/src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java @@ -81,7 +81,7 @@ public class SearchableEntityLookup implements OxmModelProcessor { if (oxmProperties.containsKey("searchableAttributes")) { searchableOxmModel.put(entityName, oxmProperties); } - + } for (Entry<String, HashMap<String, String>> searchableModel : searchableOxmModel.entrySet()) { diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java b/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java index 86262b2..6b0c309 100644 --- a/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java +++ b/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java @@ -156,7 +156,6 @@ public class SuggestionEntityLookup implements OxmModelProcessor { suggestionSearchEntityDescriptors.put(entityName, entity); } - } public Map<String, HashMap<String, String>> getSuggestionSearchEntityOxmModel() { diff --git a/src/main/java/org/onap/aai/sparky/dal/ActiveInventoryAdapter.java b/src/main/java/org/onap/aai/sparky/dal/ActiveInventoryAdapter.java index a60c853..e815a08 100644 --- a/src/main/java/org/onap/aai/sparky/dal/ActiveInventoryAdapter.java +++ b/src/main/java/org/onap/aai/sparky/dal/ActiveInventoryAdapter.java @@ -207,6 +207,7 @@ public class ActiveInventoryAdapter { link = getFullUrl("/search/nodes-query?search-node-type=" + entityType + "&filter=" + primaryKeyStr + ":EXISTS"); + return restClient.get(link, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE); } diff --git a/src/main/java/org/onap/aai/sparky/subscription/config/SubscriptionConfig.java b/src/main/java/org/onap/aai/sparky/subscription/config/SubscriptionConfig.java new file mode 100644 index 0000000..96228a5 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/subscription/config/SubscriptionConfig.java @@ -0,0 +1,204 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.subscription.config; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Properties; + +import org.onap.aai.sparky.util.ConfigHelper; +import org.onap.aai.sparky.viewandinspect.config.SparkyConstants; + + +/** + * The Class SubscriptionConfig. + */ +public class SubscriptionConfig { + + public static final String CONFIG_FILE = + SparkyConstants.DYNAMIC_CONFIG_APP_LOCATION + "subscription.properties"; + + private static SubscriptionConfig instance; + + private String subscriptionTarget; + + private String subscriptionOrigin; + + private String subscriptionMessageType; + + private String subscriptionTopic; + + private String launchOITarget; + + private String launchOIOrigin; + + private String launchOIMessageType; + + private String launchOITopic; + + private Boolean isLaunchOIEnabled; + + private Collection<String> annEntitiyTypes; + + private static final String TARGET = ""; + + private static final String ORIGIN = ""; + + private static final String MESSAGE_TYPE = ""; + + private static final String TOPIC = ""; + + + + + public static SubscriptionConfig getConfig(){ + + if (instance == null) { + instance = new SubscriptionConfig(); + instance.initializeProperties(); + } + return instance; + } + + public static void setConfig(SubscriptionConfig config) { + /* + * Explicitly allow setting the configuration singleton. This will be useful for automation. + */ + + SubscriptionConfig.instance = config; + } + + /** + * Instantiates a new Subscription config. + */ + public SubscriptionConfig() { + // test method + } + + + /** + * Initialize properties. + */ + private void initializeProperties() { + Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); + + if (props == null || props.isEmpty()) { + //Disable subscription launch if the file is missing + this.setIsLaunchOIEnabled(false); + return; + } + subscriptionTarget = props.getProperty("subscription.target", TARGET); + subscriptionOrigin = props.getProperty("subscription.origin", ORIGIN); + subscriptionMessageType = props.getProperty("subscription.messageType", MESSAGE_TYPE); + subscriptionTopic = props.getProperty("subscription.topic", TOPIC); + + this.setLaunchOITarget(props.getProperty("launchOI.target", TARGET)); + this.setLaunchOIOrigin(props.getProperty("launchOI.origin", ORIGIN)); + this.setLaunchOIMessageType(props.getProperty("launchOI.messageType", MESSAGE_TYPE)); + this.setLaunchOITopic(props.getProperty("launchOI.topic", TOPIC)); + + this.setAnnEntitiyTypes(Arrays.asList(props.getProperty("launchOI.entityTypes", "").split(","))); + this.setIsLaunchOIEnabled(Boolean.parseBoolean(props.getProperty("launchOI.enable", "false"))); + } + + + public String getSubscriptionTarget() { + return subscriptionTarget; + } + + public void setSubscriptionTarget(String target) { + this.subscriptionTarget = target; + } + + public String getSubscriptionOrigin() { + return subscriptionOrigin; + } + + public void setSubscriptionOrigin(String origin) { + this.subscriptionOrigin = origin; + } + + public String getSubscriptionMessageType() { + return subscriptionMessageType; + } + + public void setSubscriptionMessageType(String messageType) { + this.subscriptionMessageType = messageType; + } + + public String getSubscriptionTopic() { + return subscriptionTopic; + } + + public void setSubscriptionTopic(String topic) { + this.subscriptionTopic = topic; + } + +public String getLaunchOITarget() { + return launchOITarget; +} + +public void setLaunchOITarget(String launchOITarget) { + this.launchOITarget = launchOITarget; +} + +public String getLaunchOIOrigin() { + return launchOIOrigin; +} + +public void setLaunchOIOrigin(String launchOIOrigin) { + this.launchOIOrigin = launchOIOrigin; +} + +public String getLaunchOIMessageType() { + return launchOIMessageType; +} + +public void setLaunchOIMessageType(String launchOIMessageType) { + this.launchOIMessageType = launchOIMessageType; +} + +public String getLaunchOITopic() { + return launchOITopic; +} + +public void setLaunchOITopic(String launchOITopic) { + this.launchOITopic = launchOITopic; +} + +public Collection<String> getAnnEntitiyTypes() { + return annEntitiyTypes; +} + +public void setAnnEntitiyTypes(Collection<String> annEntitiyTypes) { + this.annEntitiyTypes = annEntitiyTypes; +} + +public Boolean getIsLaunchOIEnabled() { + return isLaunchOIEnabled; +} + +public void setIsLaunchOIEnabled(Boolean isLaunchOIEnabled) { + this.isLaunchOIEnabled = isLaunchOIEnabled; +} +} diff --git a/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Message.java b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Message.java new file mode 100644 index 0000000..908be73 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Message.java @@ -0,0 +1,58 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.subscription.payload.entity; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "applicationName", "payload" }) +public class Message { + + @JsonProperty("applicationName") + private String applicationName; + @JsonProperty("payload") + private Payload payload; + + @JsonProperty("applicationName") + public String getApplicationName() { + return applicationName; + } + + @JsonProperty("applicationName") + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + @JsonProperty("payload") + public Payload getPayload() { + return payload; + } + + @JsonProperty("payload") + public void setPayload(Payload payload) { + this.payload = payload; + } + +}
\ No newline at end of file diff --git a/src/main/java/org/onap/aai/sparky/subscription/payload/entity/ObjectInspectorPayload.java b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/ObjectInspectorPayload.java new file mode 100644 index 0000000..b4f2502 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/ObjectInspectorPayload.java @@ -0,0 +1,127 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.subscription.payload.entity; + +import java.io.File; +import java.io.IOException; + +import org.onap.aai.sparky.subscription.config.SubscriptionConfig; +import org.onap.aai.sparky.viewandinspect.config.SparkyConstants; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "target", "origin", "messageType", "topic", "message" }) +public class ObjectInspectorPayload { + + @JsonProperty("target") + private String target; + @JsonProperty("origin") + private String origin; + @JsonProperty("messageType") + private String messageType; + @JsonProperty("topic") + private String topic; + @JsonProperty("message") + private Message message; + + @JsonProperty("target") + public String getTarget() { + return target; + } + + @JsonProperty("target") + public void setTarget(String target) { + this.target = target; + } + + @JsonProperty("origin") + public String getOrigin() { + return origin; + } + + @JsonProperty("origin") + public void setOrigin(String origin) { + this.origin = origin; + } + + @JsonProperty("messageType") + public String getMessageType() { + return messageType; + } + + @JsonProperty("messageType") + public void setMessageType(String messageType) { + this.messageType = messageType; + } + + @JsonProperty("topic") + public String getTopic() { + return topic; + } + + @JsonProperty("topic") + public void setTopic(String topic) { + this.topic = topic; + } + + @JsonProperty("message") + public Message getMessage() { + return message; + } + + @JsonProperty("message") + public void setMessage(Message message) { + this.message = message; + } + + private static ObjectInspectorPayload lic; + public static ObjectInspectorPayload getOIPayload() throws JsonParseException, JsonMappingException, IOException{ + if(lic == null){ + ObjectMapper mapper = new ObjectMapper(); + lic = mapper.readValue(new File(SparkyConstants.SUBSCRIPTION_OI_MAPPING), ObjectInspectorPayload.class); + lic.intitializeOIPayload(); + } + + return lic; + } + + private void intitializeOIPayload(){ + try { + SubscriptionConfig subscriptionConf = SubscriptionConfig.getConfig(); + lic.setOrigin(subscriptionConf.getLaunchOIOrigin()); + lic.setTarget(subscriptionConf.getLaunchOITarget()); + lic.setTopic(subscriptionConf.getLaunchOITopic()); + lic.setMessageType(subscriptionConf.getLaunchOIMessageType()); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } +} diff --git a/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Params.java b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Params.java new file mode 100644 index 0000000..07870c7 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Params.java @@ -0,0 +1,58 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.subscription.payload.entity; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "contexts", "objectName", "externalClassId"}) +public class Params { + + @JsonProperty("objectName") + private String objectName; + @JsonProperty("externalClassId") + private String externalClassId; + + @JsonProperty("objectName") + public String getObjectName() { + return objectName; + } + + @JsonProperty("objectName") + public void setObjectName(String objectName) { + this.objectName = objectName; + } + + @JsonProperty("externalClassId") + public String getExternalClassId() { + return externalClassId; + } + + @JsonProperty("externalClassId") + public void setExternalClassId(String externalClassId) { + this.externalClassId = externalClassId; + } + +}
\ No newline at end of file diff --git a/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Payload.java b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Payload.java new file mode 100644 index 0000000..eebc59c --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/subscription/payload/entity/Payload.java @@ -0,0 +1,58 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.subscription.payload.entity; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "action", "params" }) +public class Payload { + + @JsonProperty("action") + private String action; + @JsonProperty("params") + private Params params; + + @JsonProperty("action") + public String getAction() { + return action; + } + + @JsonProperty("action") + public void setAction(String action) { + this.action = action; + } + + @JsonProperty("params") + public Params getParams() { + return params; + } + + @JsonProperty("params") + public void setParams(Params params) { + this.params = params; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/subscription/services/SubscriptionService.java b/src/main/java/org/onap/aai/sparky/subscription/services/SubscriptionService.java new file mode 100644 index 0000000..f0b0733 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/subscription/services/SubscriptionService.java @@ -0,0 +1,57 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.subscription.services; + +import org.json.JSONObject; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.subscription.config.SubscriptionConfig; + + +public class SubscriptionService { + + public OperationResult buildSubscriptionPayload() throws Exception { + OperationResult returnValue = new OperationResult(); + returnValue.setResultCode(200); + SubscriptionConfig subscriptionConf = getSubscriptionPayload(); + JSONObject subscriptionRequest = new JSONObject(); + + + if(subscriptionConf.getSubscriptionTarget().isEmpty() && subscriptionConf.getSubscriptionTopic().isEmpty() && + subscriptionConf.getSubscriptionMessageType().isEmpty() && subscriptionConf.getSubscriptionOrigin().isEmpty()) { + returnValue.setResult(500,"{}"); + } else { + subscriptionRequest.put("target", subscriptionConf.getSubscriptionTarget()); + subscriptionRequest.put("topic", subscriptionConf.getSubscriptionTopic()); + subscriptionRequest.put("messageType", subscriptionConf.getSubscriptionMessageType()); + subscriptionRequest.put("origin", subscriptionConf.getSubscriptionOrigin()); + returnValue.setResult(subscriptionRequest.toString()); + returnValue.setResultCode(200); + } + return returnValue; + + + } + public SubscriptionConfig getSubscriptionPayload() throws Exception { + return SubscriptionConfig.getConfig(); + } +} diff --git a/src/main/java/org/onap/aai/sparky/sync/entity/SuggestionSearchEntity.java b/src/main/java/org/onap/aai/sparky/sync/entity/SuggestionSearchEntity.java index 5d7b55d..9324cef 100644 --- a/src/main/java/org/onap/aai/sparky/sync/entity/SuggestionSearchEntity.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/SuggestionSearchEntity.java @@ -89,7 +89,6 @@ public class SuggestionSearchEntity extends IndexableEntity implements IndexDocu this.entityLookup = entityLookup; FiltersDetailsConfig filterConfigList = filtersConfig.getFiltersConfig(); - // Populate the map with keys that will match the suggestableAttr values for(UiFilterConfig filter : filterConfigList.getFilters()) { if(filter.getDataSource() != null) { diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/config/SparkyConstants.java b/src/main/java/org/onap/aai/sparky/viewandinspect/config/SparkyConstants.java index 5d62646..37b6909 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/config/SparkyConstants.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/config/SparkyConstants.java @@ -86,6 +86,9 @@ public class SparkyConstants { public static final String FILTER_MAPPING_FILE_DEFAULT = CONFIG_FILTERS_BASE_LOCATION + "filters" + FILESEP + "aaiui_views.json"; + public static final String SUBSCRIPTION_OI_MAPPING = + CONFIG_FILTERS_BASE_LOCATION + "subscription_object_inspector_mapping.json"; + public static final String SUGGESTION_TEXT_SEPARATOR = " -- "; // Injected Attributes diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SparkyGraphNode.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SparkyGraphNode.java index 66c49c4..1fb5272 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SparkyGraphNode.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SparkyGraphNode.java @@ -22,11 +22,18 @@ */ package org.onap.aai.sparky.viewandinspect.entity; +import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import org.apache.log4j.Logger; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.aggregatevnf.search.AggregateSummaryProcessor; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.subscription.config.SubscriptionConfig; +import org.onap.aai.sparky.subscription.payload.entity.ObjectInspectorPayload; +import org.onap.aai.sparky.viewandinspect.config.SparkyConstants; import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -61,6 +68,7 @@ public class SparkyGraphNode { private String itemNameValue; private Map<String, String> itemProperties; private NodeMeta nodeMeta; + private ObjectInspectorPayload externalResourcePayload; @JsonIgnore private boolean isRootNode; @@ -76,8 +84,8 @@ public class SparkyGraphNode { @JsonIgnore - private static final Logger LOG = Logger.getLogger(SparkyGraphNode.class); - + private static final Logger LOG = LoggerFactory.getInstance().getLogger(SparkyGraphNode.class); + private VisualizationConfigs visualizationConfigs; @@ -86,7 +94,7 @@ public class SparkyGraphNode { * * @param ain the ain */ - public SparkyGraphNode(ActiveInventoryNode ain,VisualizationConfigs visualizationConfigs ) { + public SparkyGraphNode(ActiveInventoryNode ain, VisualizationConfigs visualizationConfigs) { this.resourceKey = ain.getNodeId(); this.itemProperties = ain.getProperties(); this.setItemType(ain.getEntityType()); @@ -106,7 +114,7 @@ public class SparkyGraphNode { outboundNeighbors = ain.getOutboundNeighbors(); nodeMeta = new NodeMeta(this.visualizationConfigs); - + nodeMeta.setNodeIssue(ain.isNodeIssue()); nodeMeta.setNodeValidated(ain.isNodeValidated()); nodeMeta.setNodeDepth(ain.getNodeDepth()); @@ -119,6 +127,24 @@ public class SparkyGraphNode { nodeMeta.setProcessingErrorOccurred(ain.isProcessingErrorOccurred()); nodeMeta.setHasNeighbors( ain.getOutboundNeighbors().size() > 0 || ain.getInboundNeighbors().size() > 0); + SubscriptionConfig subscriptionConf = SubscriptionConfig.getConfig(); + if (subscriptionConf.getIsLaunchOIEnabled()) { + try { + Collection<String> entityTypes = subscriptionConf.getAnnEntitiyTypes(); + for (String entityType : entityTypes) { + if (entityType.equals(this.getItemType())) { + ObjectInspectorPayload lic = ObjectInspectorPayload.getOIPayload(); + lic.getMessage().getPayload().getParams().setObjectName(this.getItemNameValue()); + this.setExternalResourcePayload(lic); + break; + } + } + } catch (IOException e) { + String message = "Could not map JSON to object " + "Attempted to convert: " + + SparkyConstants.SUBSCRIPTION_OI_MAPPING + ". Error: " + e.getLocalizedMessage(); + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, message); + } + } nodeMeta.setProcessingState(ain.getState()); } @@ -183,7 +209,17 @@ public class SparkyGraphNode { return isRootNode; } - /* (non-Javadoc) + public ObjectInspectorPayload getExternalResourcePayload() { + return externalResourcePayload; + } + + public void setExternalResourcePayload(ObjectInspectorPayload externalResourcePayload) { + this.externalResourcePayload = externalResourcePayload; + } + + /* + * (non-Javadoc) + * * @see java.lang.Object#toString() */ @Override diff --git a/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectEntitySynchronizer.java b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectEntitySynchronizer.java index 9de2973..ee8a7ef 100644 --- a/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectEntitySynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectEntitySynchronizer.java @@ -148,9 +148,6 @@ public class ViewInspectEntitySynchronizer extends AbstractEntitySynchronizer * @return the operation state */ private OperationState collectAllTheWork() { - - - final Map<String, String> contextMap = MDC.getCopyOfContextMap(); Map<String, SearchableOxmEntityDescriptor> descriptorMap = searchableEntityLookup.getSearchableEntityDescriptors(); diff --git a/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java index 8d53d16..b804fc9 100644 --- a/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java +++ b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java @@ -57,6 +57,7 @@ public class ViewInspectSyncController extends SyncControllerImpl SearchableEntityLookup searchableEntityLookup) throws Exception { super(syncControllerConfig); + // final String controllerName = "View and Inspect Entity Synchronizer"; this.aaiAdapter = aaiAdapter; @@ -68,29 +69,29 @@ public class ViewInspectSyncController extends SyncControllerImpl registerIndexValidator(indexValidator); + ViewInspectEntitySynchronizer ses = new ViewInspectEntitySynchronizer(schemaConfig, syncControllerConfig.getNumInternalSyncWorkers(), syncControllerConfig.getNumSyncActiveInventoryWorkers(), syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig, oxmEntityLookup, searchableEntityLookup); - ses.setAaiAdapter(aaiAdapter); ses.setElasticSearchAdapter(esAdapter); registerEntitySynchronizer(ses); - + CrossEntityReferenceSynchronizer cers = new CrossEntityReferenceSynchronizer(schemaConfig, syncControllerConfig.getNumInternalSyncWorkers(), syncControllerConfig.getNumSyncActiveInventoryWorkers(), syncControllerConfig.getNumSyncElasticWorkers(),aaiStatConfig,esStatConfig, crossEntityReferenceLookup, oxmEntityLookup, searchableEntityLookup); - + cers.setAaiAdapter(aaiAdapter); cers.setElasticSearchAdapter(esAdapter); registerEntitySynchronizer(cers); - + IndexCleaner indexCleaner = new ElasticSearchIndexCleaner(esAdapter, endpointConfig, schemaConfig); diff --git a/src/test/java/org/onap/aai/sparky/search/UnifiedSearchProcessorTest.java b/src/test/java/org/onap/aai/sparky/search/UnifiedSearchProcessorTest.java index e123c5d..8dd3543 100644 --- a/src/test/java/org/onap/aai/sparky/search/UnifiedSearchProcessorTest.java +++ b/src/test/java/org/onap/aai/sparky/search/UnifiedSearchProcessorTest.java @@ -44,6 +44,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.onap.aai.sparky.common.search.CommonSearchSuggestion; import org.onap.aai.sparky.search.api.SearchProvider; +import org.onap.aai.sparky.search.entity.MockSearchResponse; import org.onap.aai.sparky.search.entity.QuerySearchEntity; import org.onap.aai.sparky.search.entity.SearchSuggestion; import org.onap.aai.sparky.search.registry.SearchProviderRegistry; @@ -54,19 +55,18 @@ import org.restlet.data.MediaType; import org.restlet.data.Status; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; public class UnifiedSearchProcessorTest { - + public interface Suggester { public void addSuggestion( SearchSuggestion suggestion ); } - + private abstract class AbstractDummySearchProvider implements SearchProvider, Suggester { - private List<SearchSuggestion> suggestions; + protected List<SearchSuggestion> suggestions; protected AbstractDummySearchProvider() { suggestions = new ArrayList<SearchSuggestion>(); @@ -76,44 +76,53 @@ public class UnifiedSearchProcessorTest { return suggestions; } - public void addSuggestion(SearchSuggestion suggestion) { - if (suggestion != null) { - suggestions.add(suggestion); - } - } - + @Override public List<SearchSuggestion> search(QuerySearchEntity queryRequest) { return getSuggestions(); } - } private class AlphaSearchProvider extends AbstractDummySearchProvider { - public AlphaSearchProvider() { super(); } + @Override + public void addSuggestion(SearchSuggestion suggestion) { + if (suggestion != null) { + suggestions.add(suggestion); + } + } } private class BravoSearchProvider extends AbstractDummySearchProvider { - public BravoSearchProvider() { super(); } + @Override + public void addSuggestion(SearchSuggestion suggestion) { + if (suggestion != null) { + suggestions.add(suggestion); + } + } } private class GammaSearchProvider extends AbstractDummySearchProvider { - public GammaSearchProvider() { super(); } + @Override + public void addSuggestion(SearchSuggestion suggestion) { + if (suggestion != null) { + suggestions.add(suggestion); + } + } } - + private SearchServiceAdapter mockSearchAdapter; - + private UnifiedSearchProcessor unifiedSearchProcessor; private Exchange mockExchange; private Message mockRequestMessage; @@ -127,7 +136,7 @@ public class UnifiedSearchProcessorTest { public void init() { requestClientInfo = new ClientInfo(); - + mockExchange = Mockito.mock(Exchange.class); mockRequestMessage = Mockito.mock(Message.class); mockResponseMessage = Mockito.mock(Message.class); @@ -138,8 +147,7 @@ public class UnifiedSearchProcessorTest { unifiedSearchProcessor.setUseOrderedSearchProviderKeys(true); mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - + mockSearchAdapter = Mockito.mock(SearchServiceAdapter.class); } @@ -151,20 +159,20 @@ public class UnifiedSearchProcessorTest { assertNull(unifiedSearchProcessor.getSearchProviderRegistry()); } - - + + @Test public void validateAccessors() { SearchProviderRegistry searchProviderRegistry = new SearchProviderRegistry(); unifiedSearchProcessor.setSearchProviderRegistry(searchProviderRegistry); - + // initially it should be null until the bean wiring initializes it assertNotNull(unifiedSearchProcessor.getSearchProviderRegistry()); assertEquals(0, searchProviderRegistry.getSearchProviders().size()); } - + private void initializeSearchMocks(String requestPayload) { Mockito.when(mockRestletRequest.getClientInfo()).thenReturn(requestClientInfo); @@ -175,47 +183,33 @@ public class UnifiedSearchProcessorTest { Mockito.when(mockRequestMessage.getHeader(RestletConstants.RESTLET_RESPONSE, Response.class)) .thenReturn(mockRestletResponse); - + Mockito.when(mockExchange.getIn()).thenReturn(mockRequestMessage); Mockito.when(mockExchange.getOut()).thenReturn(mockResponseMessage); } - - private void initializePerspectiveMocks(String requestPayload) throws JsonProcessingException { - Mockito.when(mockRestletRequest.getClientInfo()).thenReturn(requestClientInfo); - - Mockito.when(mockRequestMessage.getBody(String.class)).thenReturn(requestPayload); - Mockito.when(mockRequestMessage.getHeader(RestletConstants.RESTLET_REQUEST, Request.class)) - .thenReturn(mockRestletRequest); - - Mockito.when(mockRequestMessage.getHeader(RestletConstants.RESTLET_RESPONSE, Response.class)) - .thenReturn(mockRestletResponse); - - Mockito.when(mockExchange.getIn()).thenReturn(mockRequestMessage); - Mockito.when(mockExchange.getOut()).thenReturn(mockResponseMessage); - } - + private String getSearchRequestJson(String queryString, int maxResults) { - + JSONObject root = new JSONObject(); root.put("queryStr", queryString); root.put("maxResults", maxResults); - + return root.toString(); } - + private String getExternalSearchRequestJson() { JSONObject root = new JSONObject(); - + root.put("view", "testView"); root.put("entityId", "thisIsAnId"); root.put("entityType", "pserver"); - + return root.toString(); } - - + + @Test public void testSearch_search_when_noSearchProviders() throws IOException { @@ -225,7 +219,7 @@ public class UnifiedSearchProcessorTest { SearchProviderRegistry searchProviderRegistry = new SearchProviderRegistry(); unifiedSearchProcessor.setSearchProviderRegistry(searchProviderRegistry); - + // method under test unifiedSearchProcessor.search(mockExchange); @@ -247,9 +241,9 @@ public class UnifiedSearchProcessorTest { assertEquals(0, searchResponse.getTotalFound()); assertEquals(0, searchResponse.getSuggestions().size()); - + } - + @Test public void testSearch_search_when_ThreeSearchProviders_no_suggestions() throws IOException { @@ -258,21 +252,21 @@ public class UnifiedSearchProcessorTest { initializeSearchMocks(getSearchRequestJson("vnfs",10)); SearchProviderRegistry searchProviderRegistry = new SearchProviderRegistry(); - + AlphaSearchProvider alpha = new AlphaSearchProvider(); BravoSearchProvider bravo = new BravoSearchProvider(); GammaSearchProvider gamma = new GammaSearchProvider(); - + searchProviderRegistry.addSearchProvider(alpha); searchProviderRegistry.addSearchProvider(bravo); searchProviderRegistry.addSearchProvider(gamma); - + unifiedSearchProcessor.setSearchProviderRegistry(searchProviderRegistry); - - + + // method under test unifiedSearchProcessor.search(mockExchange); - + ArgumentCaptor<Status> responseCodeCaptor = ArgumentCaptor.forClass(Status.class); Mockito.verify(mockRestletResponse, Mockito.atLeast(1)).setStatus(responseCodeCaptor.capture()); assertEquals(Status.SUCCESS_OK, responseCodeCaptor.getValue()); @@ -286,68 +280,41 @@ public class UnifiedSearchProcessorTest { ArgumentCaptor<Response> responseObject = ArgumentCaptor.forClass(Response.class); Mockito.verify(mockResponseMessage, Mockito.atLeast(1)).setBody(responseObject.capture()); assertEquals(MediaType.APPLICATION_JSON, payloadMediaType.getValue()); - + /* * With a null view name, an empty filter set should be returned - there should be 0 filters */ - + SearchResponse searchResponse = mapper.readValue(entityPayload.getValue(), SearchResponse.class); assertEquals(0, searchResponse.getTotalFound()); assertEquals(0, searchResponse.getSuggestions().size()); - + } - + private void addSuggestions(int numSuggestions, String suggestionPrefix, Suggester suggester) { - SearchSuggestion suggestion = null; for ( int x = 0; x < numSuggestions; x++ ){ - suggestion = new CommonSearchSuggestion(); + CommonSearchSuggestion suggestion = new CommonSearchSuggestion(); suggestion.setText(suggestionPrefix + "-" + x); suggester.addSuggestion(suggestion); } } - - private void addSuggestion(String perspective, String text, String hashId, Suggester suggester) { - SearchSuggestion suggestion = new CommonSearchSuggestion(); - suggestion.setText(text); - suggestion.setHashId(hashId); - suggester.addSuggestion(suggestion); - } - - private int countSuggestions(String suggestionPrefix, SearchResponse response) { - + + private int countSuggestions(String suggestionPrefix, MockSearchResponse response) { + int totalFound = 0; - + for ( SearchSuggestion suggestion : response.getSuggestions()) { - + if ( suggestion.getText() != null && suggestion.getText().startsWith(suggestionPrefix)) { totalFound++; } } - + return totalFound; - + } - - private int countSuggestions(String suggestionPrefix, JSONArray suggestions) { - - int totalFound = 0; - - for ( int x = 0; x < suggestions.length(); x++ ) { - - JSONObject suggestion = (JSONObject)suggestions.get(x); - - String text = suggestion.getString("text"); - if ( String.valueOf(text).startsWith(suggestionPrefix)) { - totalFound++; - } - - } - - return totalFound; - - } - + @Test public void testSearch_search_when_ThreeSearchProviders_5suggestions_each() throws IOException { @@ -356,21 +323,21 @@ public class UnifiedSearchProcessorTest { initializeSearchMocks(getSearchRequestJson("vnfs",10)); SearchProviderRegistry searchProviderRegistry = new SearchProviderRegistry(); - + AlphaSearchProvider alpha = new AlphaSearchProvider(); BravoSearchProvider bravo = new BravoSearchProvider(); GammaSearchProvider gamma = new GammaSearchProvider(); - + + addSuggestions(5, "alpha", alpha); + addSuggestions(5, "bravo", bravo); + addSuggestions(5, "gamma", gamma); + searchProviderRegistry.addSearchProvider(alpha); searchProviderRegistry.addSearchProvider(bravo); searchProviderRegistry.addSearchProvider(gamma); - + unifiedSearchProcessor.setSearchProviderRegistry(searchProviderRegistry); - - addSuggestions(5,"alpha",alpha); - addSuggestions(5,"bravo",bravo); - addSuggestions(5,"gamma",gamma); - + // method under test unifiedSearchProcessor.search(mockExchange); @@ -387,21 +354,16 @@ public class UnifiedSearchProcessorTest { ArgumentCaptor<Response> responseObject = ArgumentCaptor.forClass(Response.class); Mockito.verify(mockResponseMessage, Mockito.atLeast(1)).setBody(responseObject.capture()); assertEquals(MediaType.APPLICATION_JSON, payloadMediaType.getValue()); - - - JSONObject response = new JSONObject(entityPayload.getValue()); - - assertEquals(response.getInt("totalFound"),10); - - JSONArray suggestions = response.getJSONArray("suggestions"); - assertNotNull(suggestions); - - assertEquals(suggestions.length(),10); - - assertEquals( 4, countSuggestions("alpha", suggestions)); - assertEquals( 3, countSuggestions("bravo", suggestions)); - assertEquals( 3, countSuggestions("gamma", suggestions)); - + + MockSearchResponse searchResponse = mapper.readValue(entityPayload.getValue(), MockSearchResponse.class); + + assertEquals(10, searchResponse.getTotalFound()); + assertEquals(10, searchResponse.getSuggestions().size()); + + assertEquals( 4, countSuggestions("alpha", searchResponse)); + assertEquals( 3, countSuggestions("bravo", searchResponse)); + assertEquals( 3, countSuggestions("gamma", searchResponse)); + } @Test @@ -412,21 +374,21 @@ public class UnifiedSearchProcessorTest { initializeSearchMocks(getSearchRequestJson("vnfs",13)); SearchProviderRegistry searchProviderRegistry = new SearchProviderRegistry(); - + AlphaSearchProvider alpha = new AlphaSearchProvider(); BravoSearchProvider bravo = new BravoSearchProvider(); GammaSearchProvider gamma = new GammaSearchProvider(); - + searchProviderRegistry.addSearchProvider(alpha); searchProviderRegistry.addSearchProvider(bravo); searchProviderRegistry.addSearchProvider(gamma); - + unifiedSearchProcessor.setSearchProviderRegistry(searchProviderRegistry); - + addSuggestions(45,"alpha",alpha); addSuggestions(1,"bravo",bravo); addSuggestions(99,"gamma",gamma); - + // method under test unifiedSearchProcessor.search(mockExchange); @@ -443,38 +405,34 @@ public class UnifiedSearchProcessorTest { ArgumentCaptor<Response> responseObject = ArgumentCaptor.forClass(Response.class); Mockito.verify(mockResponseMessage, Mockito.atLeast(1)).setBody(responseObject.capture()); assertEquals(MediaType.APPLICATION_JSON, payloadMediaType.getValue()); + + MockSearchResponse searchResponse = mapper.readValue(entityPayload.getValue(), MockSearchResponse.class); - JSONObject response = new JSONObject(entityPayload.getValue()); - - assertEquals(response.getInt("totalFound"),13); - - JSONArray suggestions = response.getJSONArray("suggestions"); - assertNotNull(suggestions); - - assertEquals(suggestions.length(),13); - + assertEquals(13, searchResponse.getTotalFound()); + assertEquals(13, searchResponse.getSuggestions().size()); + /** * There should be an even divide of suggestions per search provider relative * to the suggestions available per search provider. * Alpha has 45 suggestions * Bravo has 1 suggestion * Gamma has 99 suggestions - * + * * We only asked for 13 suggestions to be returned, so based on the suggestion * distribution algorithm we will get a fair distribution of suggestions per provider * relative to what each provider has available. Resulting in: * 6 from Alpha * 1 from Bravo * 6 from Gamma - * + * */ - - assertEquals( 6, countSuggestions("alpha", suggestions)); - assertEquals( 1, countSuggestions("bravo", suggestions)); - assertEquals( 6, countSuggestions("gamma", suggestions)); - + + assertEquals( 6, countSuggestions("alpha", searchResponse)); + assertEquals( 1, countSuggestions("bravo", searchResponse)); + assertEquals( 6, countSuggestions("gamma", searchResponse)); + } - + @Test public void testSearch_search_when_ThreeSearchProviders_wantedMoreSuggestionsThanAvailable() throws IOException { @@ -483,21 +441,21 @@ public class UnifiedSearchProcessorTest { initializeSearchMocks(getSearchRequestJson("vnfs",13)); SearchProviderRegistry searchProviderRegistry = new SearchProviderRegistry(); - + AlphaSearchProvider alpha = new AlphaSearchProvider(); BravoSearchProvider bravo = new BravoSearchProvider(); GammaSearchProvider gamma = new GammaSearchProvider(); - + searchProviderRegistry.addSearchProvider(alpha); searchProviderRegistry.addSearchProvider(bravo); searchProviderRegistry.addSearchProvider(gamma); - + unifiedSearchProcessor.setSearchProviderRegistry(searchProviderRegistry); - + addSuggestions(1,"alpha",alpha); addSuggestions(4,"bravo",bravo); addSuggestions(0,"gamma",gamma); - + // method under test unifiedSearchProcessor.search(mockExchange); @@ -511,19 +469,18 @@ public class UnifiedSearchProcessorTest { payloadMediaType.capture()); assertNotNull(entityPayload.getValue()); - JSONObject response = new JSONObject(entityPayload.getValue()); - - assertEquals(response.getInt("totalFound"),5); - - JSONArray suggestions = response.getJSONArray("suggestions"); - assertNotNull(suggestions); - - assertEquals(suggestions.length(),5); - - assertEquals( 1, countSuggestions("alpha", suggestions)); - assertEquals( 4, countSuggestions("bravo", suggestions)); - assertEquals( 0, countSuggestions("gamma", suggestions)); - + ArgumentCaptor<Response> responseObject = ArgumentCaptor.forClass(Response.class); + Mockito.verify(mockResponseMessage, Mockito.atLeast(1)).setBody(responseObject.capture()); + assertEquals(MediaType.APPLICATION_JSON, payloadMediaType.getValue()); + + MockSearchResponse searchResponse = mapper.readValue(entityPayload.getValue(), MockSearchResponse.class); + + assertEquals(5, searchResponse.getTotalFound()); + assertEquals(5, searchResponse.getSuggestions().size()); + + assertEquals( 1, countSuggestions("alpha", searchResponse)); + assertEquals( 4, countSuggestions("bravo", searchResponse)); + assertEquals( 0, countSuggestions("gamma", searchResponse)); + } - -} +}
\ No newline at end of file diff --git a/src/test/java/org/onap/aai/sparky/search/entity/MockSearchResponse.java b/src/test/java/org/onap/aai/sparky/search/entity/MockSearchResponse.java new file mode 100644 index 0000000..02a1aee --- /dev/null +++ b/src/test/java/org/onap/aai/sparky/search/entity/MockSearchResponse.java @@ -0,0 +1,71 @@ +package org.onap.aai.sparky.search.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.onap.aai.sparky.common.search.CommonSearchSuggestion; + +public class MockSearchResponse { + private long processingTimeInMs; + private int totalFound; + + private List<CommonSearchSuggestion> suggestions; + + /** + * Instantiates a new search response. + */ + public MockSearchResponse() { + this.suggestions = new ArrayList<CommonSearchSuggestion>(); + this.processingTimeInMs = 0; + this.totalFound = 0; + } + + public long getProcessingTimeInMs() { + return processingTimeInMs; + } + + public void setProcessingTimeInMs(long processingTimeInMs) { + this.processingTimeInMs = processingTimeInMs; + } + + public int getTotalFound() { + return totalFound; + } + + public void setTotalFound(int totalFound) { + this.totalFound = totalFound; + } + + public List<CommonSearchSuggestion> getSuggestions() { + return suggestions; + } + + public void setSuggestions(List<CommonSearchSuggestion> suggestions) { + this.suggestions = suggestions; + } + + /** + * Adds the entity entry. + * + * @param suggestionEntry that will be converted to JSON + */ + public void addSuggestion(CommonSearchSuggestion suggestionEntity){ + suggestions.add(suggestionEntity); + } + + /** + * Increments the total number of hits for this SearchResponse by + * the value passed in. + * + * @param additionalCount - Count to increment the total found + */ + public void addToTotalFound(int additionalCount) { + totalFound += additionalCount; + } + + @Override + public String toString() { + return "DummySearchResponse [processingTimeInMs=" + processingTimeInMs + ", totalFound=" + + totalFound + ", suggestions=" + suggestions + "]"; + } +} |