Adding UI extensibility
[aai/sparky-be.git] / src / main / java / org / onap / aai / sparky / search / UnifiedSearchProcessor.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.onap.aai.sparky.search;
24
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Set;
30 import java.util.TreeMap;
31
32 import org.apache.camel.Exchange;
33 import org.apache.camel.component.restlet.RestletConstants;
34 import org.onap.aai.cl.api.Logger;
35 import org.onap.aai.cl.eelf.LoggerFactory;
36 import org.onap.aai.cl.mdc.MdcContext;
37 import org.onap.aai.sparky.logging.AaiUiMsgs;
38 import org.onap.aai.sparky.search.api.SearchProvider;
39 import org.onap.aai.sparky.search.entity.QuerySearchEntity;
40 import org.onap.aai.sparky.search.entity.SearchSuggestion;
41 import org.onap.aai.sparky.search.registry.SearchProviderRegistry;
42 import org.onap.aai.sparky.util.NodeUtils;
43 import org.restlet.Request;
44 import org.restlet.Response;
45 import org.restlet.data.ClientInfo;
46 import org.restlet.data.MediaType;
47 import org.restlet.data.Status;
48
49 import com.fasterxml.jackson.databind.ObjectMapper;
50
51 public class UnifiedSearchProcessor {
52
53   protected static final String HASH_ID_KEY = "hashId";
54
55   private static final Logger LOG =
56       LoggerFactory.getInstance().getLogger(UnifiedSearchProcessor.class);
57
58   protected SearchProviderRegistry searchProviderRegistry;
59   protected ObjectMapper mapper;
60   protected boolean useOrderedSearchProviderKeys;
61
62   public UnifiedSearchProcessor() {
63     mapper = new ObjectMapper();
64     this.useOrderedSearchProviderKeys = false;
65   }
66
67   public boolean isUseOrderedSearchProviderKeys() {
68     return useOrderedSearchProviderKeys;
69   }
70
71   public void setUseOrderedSearchProviderKeys(boolean useOrderedSearchProviderKeys) {
72     this.useOrderedSearchProviderKeys = useOrderedSearchProviderKeys;
73   }
74
75   public void search(Exchange exchange) {
76
77     Object xTransactionId = exchange.getIn().getHeader("X-TransactionId");
78     if (xTransactionId == null) {
79       xTransactionId = NodeUtils.getRandomTxnId();
80     }
81
82     Object partnerName = exchange.getIn().getHeader("X-FromAppId");
83     if (partnerName == null) {
84       partnerName = "Browser";
85     }
86
87     Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class);
88
89     /*
90      * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log
91      * entry which includes client (e.g. browser) information
92      */
93     request.setLoggable(false);
94
95     ClientInfo clientInfo = request.getClientInfo();
96     MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName,
97         clientInfo.getAddress() + ":" + clientInfo.getPort());
98
99     SearchResponse searchResponse = new SearchResponse();
100     long processTime = System.currentTimeMillis();
101     int totalAdded = 0;
102
103     try {
104       String payload = exchange.getIn().getBody(String.class);
105
106       if (payload == null || payload.isEmpty()) {
107
108         LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, "Request Payload is empty");
109
110         /*
111          * Don't throw back an error, just return an empty set
112          */
113
114       } else {
115
116         QuerySearchEntity searchRequest = mapper.readValue(payload, QuerySearchEntity.class);
117         int maxResultsPerSearch = Integer.valueOf(searchRequest.getMaxResults());
118
119         Map<String, List<SearchSuggestion>> searchProviderSuggestions =
120             new HashMap<String, List<SearchSuggestion>>();
121
122         int totalSuggestionsFromProviders = 0;
123         List<SearchSuggestion> suggestions = null;
124         for (SearchProvider searchProvider : searchProviderRegistry.getSearchProviders()) {
125           suggestions = searchProvider.search(searchRequest);
126           totalSuggestionsFromProviders += suggestions.size();
127           searchProviderSuggestions.put(searchProvider.getClass().getCanonicalName(), suggestions);
128         }
129
130         /*
131          * Using ordered search provider keys allows us to deterministically calculate how many
132          * results from each provider should be returned. At the moment, this behavior is primarily
133          * only beneficial to test classes. As there is a cost to sorted-collections in the call
134          * processing path, this behavior has been made optional.
135          */
136
137         if (useOrderedSearchProviderKeys) {
138           searchProviderSuggestions =
139               new TreeMap<String, List<SearchSuggestion>>(searchProviderSuggestions);
140         }
141
142         if (totalSuggestionsFromProviders > 0) {
143
144           int suggestionIndex = 0;
145
146           Set<Entry<String, List<SearchSuggestion>>> searchProviderResults =
147               searchProviderSuggestions.entrySet();
148
149           while (totalAdded < maxResultsPerSearch && (totalAdded < totalSuggestionsFromProviders)) {
150
151             for (Entry<String, List<SearchSuggestion>> searchProviderResultList : searchProviderResults) {
152
153               if ((suggestionIndex <= (searchProviderResultList.getValue().size() - 1))) {
154
155                 if (totalAdded < maxResultsPerSearch) {
156                   searchResponse
157                       .addSuggestion(searchProviderResultList.getValue().get(suggestionIndex));
158                   totalAdded++;
159                 }
160               }
161
162             }
163
164             suggestionIndex++;
165
166           }
167
168         }
169
170       }
171
172       searchResponse.addToTotalFound(totalAdded);
173       String searchResponseJson = NodeUtils.convertObjectToJson(searchResponse, true);
174
175       processTime = System.currentTimeMillis() - processTime;
176       searchResponse.setProcessingTimeInMs(processTime);
177
178       Response response =
179           exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class);
180       response.setStatus(Status.SUCCESS_OK);
181       response.setEntity(searchResponseJson, MediaType.APPLICATION_JSON);
182       exchange.getOut().setBody(response);
183
184     } catch (Exception exc) {
185       LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR,
186           "Query search failed with error = " + exc.getMessage());
187       exchange.getOut().setBody(
188           generateJsonErrorResponse("Error while building response.  Error = " + exc.getMessage()),
189           String.class);
190     }
191   }
192
193   public SearchProviderRegistry getSearchProviderRegistry() {
194     return searchProviderRegistry;
195   }
196
197   public void setSearchProviderRegistry(SearchProviderRegistry searchProviderRegistry) {
198     this.searchProviderRegistry = searchProviderRegistry;
199   }
200
201
202   /*
203    * This is the manual approach, however we could also create an object container for the error
204    * then use the Jackson ObjectWrite to dump the object to json instead. If it gets any more
205    * complicated we could do that approach so we don't have to manually trip over the JSON
206    * formatting.
207    */
208   protected String generateJsonErrorResponse(String message) {
209     return String.format("{ \"errorMessage\" : %s }", message);
210   }
211
212 }