Upversion artifacts to 1.8.0-SNAPSHOT
[aai/data-router.git] / src / main / java / org / onap / aai / datarouter / util / SearchServiceAgent.java
1 /**\r
2  * ============LICENSE_START=======================================================\r
3  * org.onap.aai\r
4  * ================================================================================\r
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.\r
6  * Copyright © 2017-2018 Amdocs\r
7  * ================================================================================\r
8  * Licensed under the Apache License, Version 2.0 (the "License");\r
9  * you may not use this file except in compliance with the License.\r
10  * You may obtain a copy of the License at\r
11  *\r
12  *       http://www.apache.org/licenses/LICENSE-2.0\r
13  *\r
14  * Unless required by applicable law or agreed to in writing, software\r
15  * distributed under the License is distributed on an "AS IS" BASIS,\r
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
17  * See the License for the specific language governing permissions and\r
18  * limitations under the License.\r
19  * ============LICENSE_END=========================================================\r
20  */\r
21 package org.onap.aai.datarouter.util;\r
22 \r
23 import java.io.BufferedReader;\r
24 import java.io.InputStreamReader;\r
25 import java.nio.charset.StandardCharsets;\r
26 import java.util.Arrays;\r
27 import java.util.HashMap;\r
28 import java.util.List;\r
29 import java.util.Map;\r
30 import java.util.UUID;\r
31 \r
32 import javax.ws.rs.core.MediaType;\r
33 import javax.ws.rs.core.MultivaluedMap;\r
34 import javax.ws.rs.core.Response.Status;\r
35 \r
36 import org.eclipse.jetty.util.security.Password;\r
37 import org.onap.aai.cl.api.Logger;\r
38 import org.onap.aai.cl.mdc.MdcContext;\r
39 import org.onap.aai.datarouter.logging.DataRouterMsgs;\r
40 import org.onap.aai.restclient.client.Headers;\r
41 import org.onap.aai.restclient.client.OperationResult;\r
42 import org.onap.aai.restclient.client.RestClient;\r
43 import org.onap.aai.restclient.enums.RestAuthenticationMode;\r
44 import org.onap.aai.restclient.rest.HttpUtil;\r
45 import org.slf4j.MDC;\r
46 import org.springframework.core.io.ClassPathResource;\r
47 import org.springframework.core.io.FileSystemResource;\r
48 import org.springframework.core.io.Resource;\r
49 \r
50 import com.sun.jersey.core.util.MultivaluedMapImpl;\r
51 \r
52 public class SearchServiceAgent {\r
53 \r
54   private Logger logger;\r
55   \r
56   private RestClient searchClient = null;\r
57   private Map<String, String> indexSchemaMapping = new HashMap<>();\r
58   \r
59   private String searchUrl = null;\r
60   private String documentEndpoint = null;\r
61   private String schemaHomeDir = null;\r
62   \r
63   \r
64   /**\r
65    * Creates a new instance of the search service agent.\r
66    * \r
67    * @param certName         - Certificate to use for talking to the Search Service.\r
68    * @param keystore         - Keystore to use for talking to the Search Service.\r
69    * @param keystorePwd      - Keystore password for talking to the Search Service.\r
70    * @param searchUrl        - URL at which the Search Service can be reached.\r
71    * @param documentEndpoint - Endpoint for accessing document resources on the Search Service.\r
72    * @param logger           - Logger to use for system logs.\r
73    */\r
74   public SearchServiceAgent(String certName, \r
75                             String keystore, \r
76                             String keystorePwd,\r
77                             String searchUrl,\r
78                             String documentEndpoint,\r
79                             Logger logger) {\r
80     \r
81     initialize(certName, keystore, keystorePwd, searchUrl, documentEndpoint, logger);\r
82   }\r
83   \r
84   \r
85   /**\r
86    * Performs all one-time initialization required for the search agent.\r
87    * \r
88    * @param certName         - Certificate to use for talking to the Search Service.\r
89    * @param keystore         - Keystore to use for talking to the Search Service.\r
90    * @param keystorePwd      - Keystore password for talking to the Search Service.\r
91    * @param searchUrl        - URL at which the Search Service can be reached.\r
92    * @param documentEndpoint - Endpoint for accessing document resources on the Search Service.\r
93    * @param logger           - Logger to use for system logs.\r
94    */\r
95   private void initialize(String certName, \r
96                           String keystore, \r
97                           String keystorePwd, \r
98                           String searchUrl, \r
99                           String documentEndpoint, \r
100                           Logger logger) {\r
101     \r
102     String deobfuscatedCertPassword = keystorePwd.startsWith("OBF:")?Password.deobfuscate(keystorePwd):keystorePwd;\r
103     // Create REST client for search service\r
104     searchClient = new RestClient()\r
105                     .authenticationMode(RestAuthenticationMode.SSL_CERT)\r
106                     .validateServerHostname(false)\r
107                     .validateServerCertChain(false)\r
108                     .clientCertFile(DataRouterConstants.DR_HOME_AUTH + certName)\r
109                     .clientCertPassword(deobfuscatedCertPassword);                    \r
110     \r
111     this.searchUrl        = searchUrl;\r
112     this.documentEndpoint = documentEndpoint;\r
113     \r
114     this.logger           = logger;\r
115     this.schemaHomeDir = DataRouterConstants.DR_SEARCH_SCHEMA_HOME;\r
116   }\r
117   \r
118   \r
119   /**\r
120    * Creates an index through the search db abstraction\r
121    * \r
122    * @param index          - The name of the index to be created.\r
123    * @param schemaLocation - The name of the schema file for the index.\r
124    */\r
125   public void createSearchIndex(String index, String schemaLocation) {\r
126      \r
127     // Create a mapping of the index name to schema location \r
128     indexSchemaMapping.put(index, schemaLocation);\r
129     \r
130     // Now, create the index.\r
131     createIndex(index, schemaLocation);\r
132   }\r
133   \r
134   public void createSearchIndex(String index, String schemaLocation, String endUrl) {\r
135     \r
136     // Create a mapping of the index name to schema location \r
137     indexSchemaMapping.put(index, schemaLocation);\r
138     \r
139     // Now, create the index.\r
140     createIndex(index, schemaLocation, endUrl);\r
141   }\r
142   \r
143   /**\r
144    * This method performs the actual work of creating a search index.\r
145    * \r
146    * @param index          - The name of the index to be created.\r
147    * @param schemaLocation - The name of the schema file for the index.\r
148    */\r
149   private void createIndex(String index, String schemaLocation) {\r
150     \r
151     logger.debug("Creating search index, index name: = " + index + ", schemaLocation = " + schemaLocation);\r
152     \r
153     MultivaluedMap<String, String> headers = new MultivaluedMapImpl();\r
154     headers.put("Accept", Arrays.asList("application/json"));\r
155     headers.put(Headers.FROM_APP_ID, Arrays.asList("DL"));\r
156     headers.put(Headers.TRANSACTION_ID, Arrays.asList(UUID.randomUUID().toString()));\r
157       \r
158     String url = concatSubUri(searchUrl, index);\r
159     try {\r
160 \r
161       OperationResult result = searchClient.put(url, loadFileData(schemaLocation), headers,\r
162                                                 MediaType.APPLICATION_JSON_TYPE, null);\r
163 \r
164       if (!HttpUtil.isHttpResponseClassSuccess(result.getResultCode())) {\r
165         logger.error(DataRouterMsgs.FAIL_TO_CREATE_SEARCH_INDEX, index, result.getFailureCause());\r
166       } else {\r
167         logger.info(DataRouterMsgs.SEARCH_INDEX_CREATE_SUCCESS, index);\r
168       }\r
169 \r
170     } catch (Exception e) {\r
171       logger.error(DataRouterMsgs.FAIL_TO_CREATE_SEARCH_INDEX, index, e.getLocalizedMessage());\r
172     }\r
173   }\r
174   \r
175   private void createIndex(String index, String schemaLocation, String endUrl) {\r
176     \r
177     logger.debug("Creating search index, index name: = " + index + ", schemaLocation = " + schemaLocation);\r
178     \r
179     MultivaluedMap<String, String> headers = new MultivaluedMapImpl();\r
180     headers.put("Accept", Arrays.asList("application/json"));\r
181     headers.put(Headers.FROM_APP_ID, Arrays.asList("DL"));\r
182     headers.put(Headers.TRANSACTION_ID, Arrays.asList(UUID.randomUUID().toString()));\r
183       \r
184     String url = concatSubUri(searchUrl, endUrl, index);\r
185     try {\r
186 \r
187       OperationResult result = searchClient.put(url, loadFileData(schemaLocation), headers,\r
188                                                 MediaType.APPLICATION_JSON_TYPE, null);\r
189       if (!HttpUtil.isHttpResponseClassSuccess(result.getResultCode())) {\r
190         logger.error(DataRouterMsgs.FAIL_TO_CREATE_SEARCH_INDEX, index, result.getFailureCause());\r
191       } else {\r
192         logger.info(DataRouterMsgs.SEARCH_INDEX_CREATE_SUCCESS, index);\r
193       }\r
194 \r
195     } catch (Exception e) {\r
196       logger.error(DataRouterMsgs.FAIL_TO_CREATE_SEARCH_INDEX, index, e.getLocalizedMessage());\r
197     }\r
198   }\r
199   \r
200   /**\r
201    * Retrieves a document from the search service.\r
202    * \r
203    * @param index - The index to retrieve the document from.\r
204    * @param id    - The unique identifier for the document.\r
205    * \r
206    * @return - The REST response returned from the Search Service.\r
207    */\r
208   public OperationResult getDocument(String index, String id) {\r
209     \r
210     Map<String, List<String>> headers = new HashMap<>();\r
211     headers.put(Headers.FROM_APP_ID, Arrays.asList("Data Router"));\r
212     headers.put(Headers.TRANSACTION_ID, Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID)));\r
213     \r
214     String url = concatSubUri(searchUrl, index, documentEndpoint, id);\r
215     return searchClient.get(url, headers, MediaType.APPLICATION_JSON_TYPE);    \r
216   }\r
217   \r
218   \r
219   /**\r
220    * Creates or updates a document in the Search Service.\r
221    * \r
222    * @param index   - The index to create or update the document in.\r
223    * @param id      - The identifier for the document.\r
224    * @param payload - The document contents.\r
225    * @param headers - HTTP headers.\r
226    */\r
227   public void putDocument(String index, String id, String payload, Map<String, List<String>> headers) {\r
228         \r
229     // Try to post the document to the search service.\r
230     OperationResult result = doDocumentPut(index, id, payload, headers);\r
231     \r
232     // A 404 response from the Search Service may indicate that the index we are writing\r
233     // to does not actually exist.  We will try creating it now.\r
234     if(result.getResultCode() == Status.NOT_FOUND.getStatusCode()) {\r
235             \r
236       // Lookup the location of the schema that we want to create.\r
237       String indexSchemaLocation = indexSchemaMapping.get(index);\r
238       if(indexSchemaLocation != null) {\r
239         \r
240         // Try creating the index now...\r
241         logger.info(DataRouterMsgs.CREATE_MISSING_INDEX, index);\r
242         createIndex(index, indexSchemaLocation);\r
243         \r
244         // ...and retry the document post.\r
245         result = doDocumentPut(index, id, payload, headers);\r
246       }\r
247     }\r
248     \r
249     if(!resultSuccessful(result)) {\r
250       logger.error(DataRouterMsgs.FAIL_TO_CREATE_UPDATE_DOC, index, result.getFailureCause());\r
251     }\r
252   }\r
253   \r
254   \r
255   /**\r
256    * This method does the actual work of submitting a document PUT request to the Search Service.\r
257    * \r
258    * @param index   - The index to create or update the document in.\r
259    * @param id      - The identifier for the document.\r
260    * @param payload - The document contents.\r
261    * @param headers - HTTP headers.\r
262    * \r
263    * @return - The HTTP response returned by the Search Service.\r
264    */\r
265   private OperationResult doDocumentPut(String index, String id, String payload, Map<String, List<String>> headers) {\r
266     \r
267     String url = concatSubUri(searchUrl, index, documentEndpoint, id);\r
268     return searchClient.put(url, payload, headers, MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);\r
269   }\r
270   \r
271   \r
272   /**\r
273    * Creates a document in the Search Service.\r
274    * \r
275    * @param index   - The index to create the document in.\r
276    * @param payload - The document contents.\r
277    * @param headers - HTTP headers.\r
278    */\r
279   public void postDocument(String index, String payload, Map<String, List<String>> headers) {\r
280     \r
281     // Try to post the document to the search service.\r
282     OperationResult result = doDocumentPost(index, payload, headers);\r
283     \r
284     // A 404 response from the Search Service may indicate that the index we are writing\r
285     // to does not actually exist.  We will try creating it now.\r
286     if(result.getResultCode() == Status.NOT_FOUND.getStatusCode()) {\r
287       \r
288       // Lookup the location of the schema that we want to create.\r
289       String indexSchemaLocation = indexSchemaMapping.get(index);\r
290       if(indexSchemaLocation != null) {\r
291         \r
292         // Try creating the index now...\r
293         logger.info(DataRouterMsgs.CREATE_MISSING_INDEX, index);\r
294         createIndex(index, indexSchemaLocation);\r
295         \r
296         // ...and retry the document post.\r
297         result = doDocumentPost(index, payload, headers);\r
298       }\r
299     }\r
300     \r
301     if(!resultSuccessful(result)) {\r
302       logger.error(DataRouterMsgs.FAIL_TO_CREATE_UPDATE_DOC, index, result.getFailureCause());\r
303     }\r
304   }\r
305   \r
306   \r
307   /**\r
308    * This method does the actual work of submitting a document PUT request to the Search Service.\r
309    * \r
310    * @param index   - The index to create or update the document in.\r
311    * @param payload - The document contents.\r
312    * @param headers - HTTP headers.\r
313    * \r
314    * @return - The HTTP response returned by the Search Service.\r
315    */\r
316   private OperationResult doDocumentPost(String index, String payload, Map<String, List<String>> headers) {\r
317     \r
318     String url = concatSubUri(searchUrl, index, documentEndpoint);\r
319     return searchClient.post(url, payload, headers, MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE);\r
320   }\r
321   \r
322   \r
323   /**\r
324    * Removes a document from the Search Service.\r
325    * \r
326    * @param index   - The index to create the document in.\r
327    * @param documentId      - The identifier for the document.\r
328    * @param headers - HTTP headers.\r
329    */\r
330   public void deleteDocument(String index, String documentId, Map<String, List<String>> headers) {\r
331     \r
332     String url = concatSubUri(searchUrl, index, documentEndpoint, documentId);\r
333     searchClient.delete(url, headers, null);\r
334   }\r
335   \r
336   \r
337   /**\r
338    * Convenience method to load up all the data from a file into a string\r
339    * \r
340    * @param filename the filename to read from disk\r
341    * @return the data contained within the file\r
342    * @throws Exception\r
343    */\r
344   protected String loadFileData(String filename) throws Exception {\r
345     Resource fileResource = getSchemaResource(filename);\r
346     if (fileResource == null) {\r
347       throw new Exception("Could not find file = " + filename + ".");\r
348     }\r
349 \r
350     try (\r
351         InputStreamReader inputStreamReader = new InputStreamReader(fileResource.getInputStream(), StandardCharsets.UTF_8);\r
352         BufferedReader in = new BufferedReader(inputStreamReader);\r
353         ){\r
354       String line;\r
355       StringBuilder data = new StringBuilder();\r
356       while((line = in.readLine()) != null) {\r
357         data.append(line);\r
358       }\r
359       return data.toString();\r
360     } catch (Exception e) {\r
361       throw new Exception("Failed to read from file = " + filename + ".", e);\r
362     }\r
363   }\r
364 \r
365   private Resource getSchemaResource(String filename) {\r
366     Resource fileResource = null;\r
367 \r
368     if(filename == null) {\r
369       return null;\r
370     }\r
371     \r
372     if ((schemaHomeDir != null) && (fileResource = new FileSystemResource(schemaHomeDir + "/" + filename)).isReadable()) {\r
373       return fileResource;\r
374     }\r
375 \r
376     if ((fileResource = new ClassPathResource(filename)).isReadable()) {\r
377       return fileResource;\r
378     }\r
379 \r
380     return null;\r
381   }\r
382   \r
383   \r
384   /**\r
385    * Helper utility to concatenate substrings of a URI together to form a proper URI.\r
386    * \r
387    * @param suburis the list of substrings to concatenate together\r
388    * @return the concatenated list of substrings\r
389    */\r
390   public static String concatSubUri(String... suburis) {\r
391     String finalUri = "";\r
392 \r
393     for (String suburi : suburis) {\r
394 \r
395       if (suburi != null) {\r
396         // Remove any leading / since we only want to append /\r
397         suburi = suburi.replaceFirst("^/*", "");\r
398 \r
399         // Add a trailing / if one isn't already there\r
400         finalUri += suburi.endsWith("/") ? suburi : suburi + "/";\r
401       }\r
402     }\r
403 \r
404     return finalUri;\r
405   }\r
406   \r
407   \r
408   /**\r
409    * Helper utility to check the response code of an HTTP response.\r
410    * \r
411    * @param aResult - The response that we want to check.\r
412    * \r
413    * @return - true if the response contains a success code,\r
414    *           false otherwise.\r
415    */\r
416   private boolean resultSuccessful(OperationResult aResult) {\r
417     \r
418     return (aResult.getResultCode() >= 200) && (aResult.getResultCode() < 300);\r
419   }\r
420 }\r