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