2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.sa.rest;
23 import com.fasterxml.jackson.databind.ObjectMapper;
25 import org.onap.aai.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreInterface;
26 import org.onap.aai.sa.searchdbabstraction.elasticsearch.exception.DocumentStoreOperationException;
27 import org.onap.aai.sa.searchdbabstraction.entity.OperationResult;
28 import org.onap.aai.sa.searchdbabstraction.logging.SearchDbMsgs;
29 import org.onap.aai.cl.api.LogFields;
30 import org.onap.aai.cl.api.LogLine;
31 import org.onap.aai.cl.api.Logger;
32 import org.onap.aai.cl.eelf.LoggerFactory;
33 import org.onap.aai.sa.rest.DocumentFieldSchema;
34 import org.onap.aai.sa.rest.DocumentSchema;
37 import java.io.FileNotFoundException;
38 import java.io.IOException;
39 import javax.servlet.http.HttpServletRequest;
42 import org.springframework.http.HttpHeaders;
43 import org.springframework.http.MediaType;
44 import org.springframework.http.ResponseEntity;
45 import org.springframework.http.HttpStatus;
46 // import org.springframework.http.server.HttpServletRequest;
50 * This class encapsulates the REST end points associated with manipulating
51 * indexes in the document store.
53 public class IndexApi {
56 private static final String HEADER_VALIDATION_SUCCESS = "SUCCESS";
57 protected SearchServiceApi searchService = null;
60 * Configuration for the custom analyzers that will be used for indexing.
62 protected AnalysisConfiguration analysisConfig;
64 // Set up the loggers.
65 private static Logger logger = LoggerFactory.getInstance()
66 .getLogger(IndexApi.class.getName());
67 private static Logger auditLogger = LoggerFactory.getInstance()
68 .getAuditLogger(IndexApi.class.getName());
71 public IndexApi(SearchServiceApi searchService) {
72 this.searchService = searchService;
78 * Initializes the end point.
80 * @throws FileNotFoundException
82 * @throws DocumentStoreOperationException
86 // Instantiate our analysis configuration object.
87 analysisConfig = new AnalysisConfiguration();
92 * Processes client requests to create a new index and document type in the
95 * @param documentSchema - The contents of the request body which is expected
96 * to be a JSON structure which corresponds to the
97 * schema defined in document.schema.json
98 * @param index - The name of the index to create.
99 * @return - A Standard REST response
101 public ResponseEntity<String> processCreateIndex (String documentSchema,
102 HttpServletRequest request,
105 DocumentStoreInterface documentStore) {
107 int resultCode = 500;
108 String resultString = "Unexpected error";
110 // Initialize the MDC Context for logging purposes.
111 ApiUtils.initMdcContext(request, headers);
113 // Validate that the request is correctly authenticated before going
117 if (!searchService.validateRequest(headers, request,
118 ApiUtils.Action.POST, ApiUtils.SEARCH_AUTH_POLICY_NAME)) {
119 logger.warn(SearchDbMsgs.INDEX_CREATE_FAILURE, index, "Authentication failure.");
120 return errorResponse(HttpStatus.FORBIDDEN, "Authentication failure.", request);
123 } catch (Exception e) {
125 logger.warn(SearchDbMsgs.INDEX_CREATE_FAILURE, index,
126 "Unexpected authentication failure - cause: " + e.getMessage());
127 return errorResponse(HttpStatus.FORBIDDEN, "Authentication failure.", request);
131 // We expect a payload containing the document schema. Make sure
133 if (documentSchema == null) {
134 logger.warn(SearchDbMsgs.INDEX_CREATE_FAILURE, index, "Missing document schema payload");
135 return errorResponse(HttpStatus.valueOf(resultCode), "Missing payload", request);
140 // Marshal the supplied json string into a document schema object.
141 ObjectMapper mapper = new ObjectMapper();
142 DocumentSchema schema = mapper.readValue(documentSchema, DocumentSchema.class);
144 // Now, ask the DAO to create the index.
145 OperationResult result = documentStore.createIndex(index, schema);
147 // Extract the result code and string from the OperationResult
148 // object so that we can use them to generate a standard REST
150 // Note that we want to return a 201 result code on a successful
151 // create, so if we get back a 200 from the document store,
152 // translate that int a 201.
153 resultCode = (result.getResultCode() == 200) ? 201 : result.getResultCode();
154 resultString = (result.getFailureCause() == null)
155 ? result.getResult() : result.getFailureCause();
157 } catch (com.fasterxml.jackson.core.JsonParseException
158 | com.fasterxml.jackson.databind.JsonMappingException e) {
160 // We were unable to marshal the supplied json string into a valid
161 // document schema, so return an appropriate error response.
162 resultCode = HttpStatus.BAD_REQUEST.value();
163 resultString = "Malformed schema: " + e.getMessage();
165 } catch (IOException e) {
167 // We'll treat this is a general internal error.
168 resultCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
169 resultString = "IO Failure: " + e.getMessage();
172 ResponseEntity<String> response = ResponseEntity.status(resultCode).contentType ( MediaType.APPLICATION_JSON ).body(resultString);
176 if ((response.getStatusCodeValue() >= 200) && (response.getStatusCodeValue() < 300)) {
177 logger.info(SearchDbMsgs.CREATED_INDEX, index);
179 logger.warn(SearchDbMsgs.INDEX_CREATE_FAILURE, index, resultString);
182 // Generate our audit log.
183 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
185 .setField(LogLine.DefinedFields.RESPONSE_CODE, resultCode)
186 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION,
187 HttpStatus.valueOf(resultCode).toString()),
188 (request != null) ? request.getMethod().toString () : "Unknown",
189 (request != null) ? request.getRequestURL ().toString () : "Unknown",
190 (request != null) ? request.getRemoteHost () : "Unknown",
191 Integer.toString(response.getStatusCodeValue ()));
195 // Clear the MDC context so that no other transaction inadvertently
196 // uses our transaction id.
197 ApiUtils.clearMdcContext();
199 // Finally, return the response.
204 * This function accepts any JSON and will "blindly" write it to the
207 * Note, eventually this "dynamic" flow should follow the same JSON-Schema
208 * validation procedure as the normal create index flow.
210 * @param dynamicSchema - The JSON string that will be sent to the document store.
211 * @param index - The name of the index to be created.
212 * @param documentStore - The document store specific interface.
213 * @return The result of the document store interface's operation.
215 public ResponseEntity<String> processCreateDynamicIndex(String dynamicSchema, HttpServletRequest request,
216 HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
218 ResponseEntity<String> response = null;
220 ResponseEntity<String> validationResponse = validateRequest(request, headers, index, SearchDbMsgs.INDEX_CREATE_FAILURE);
223 if (validationResponse.getStatusCodeValue () != HttpStatus.OK.value ()) {
224 response = validationResponse;
226 OperationResult result = documentStore.createDynamicIndex(index, dynamicSchema);
228 int resultCode = (result.getResultCode() == 200) ? 201 : result.getResultCode();
229 String resultString = (result.getFailureCause() == null) ? result.getResult() : result.getFailureCause();
231 response = ResponseEntity.status(resultCode).body(resultString);
238 * Processes a client request to remove an index from the document store.
239 * Note that this implicitly deletes all documents contained within that index.
241 * @param index - The index to be deleted.
242 * @return - A standard REST response.
244 public ResponseEntity<String> processDelete(String index,
245 HttpServletRequest request,
247 DocumentStoreInterface documentStore) {
249 // Initialize the MDC Context for logging purposes.
250 ApiUtils.initMdcContext(request, headers);
252 // Set a default response in case something unexpected goes wrong.
253 ResponseEntity<String> response = ResponseEntity.status ( HttpStatus.INTERNAL_SERVER_ERROR ).body ( "Unknown" );
255 // Validate that the request is correctly authenticated before going
259 if (!searchService.validateRequest(headers, request, ApiUtils.Action.POST,
260 ApiUtils.SEARCH_AUTH_POLICY_NAME)) {
261 logger.warn(SearchDbMsgs.INDEX_CREATE_FAILURE, index, "Authentication failure.");
262 return errorResponse(HttpStatus.FORBIDDEN, "Authentication failure.", request);
265 } catch (Exception e) {
267 logger.warn(SearchDbMsgs.INDEX_CREATE_FAILURE, index,
268 "Unexpected authentication failure - cause: " + e.getMessage());
269 return errorResponse(HttpStatus.FORBIDDEN, "Authentication failure.", request);
274 // Send the request to the document store.
275 response = responseFromOperationResult(documentStore.deleteIndex(index));
277 } catch (DocumentStoreOperationException e) {
278 response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType ( MediaType.APPLICATION_JSON ).body(e.getMessage());
282 if ((response.getStatusCodeValue() >= 200) && (response.getStatusCodeValue() < 300)) {
283 logger.info(SearchDbMsgs.DELETED_INDEX, index);
285 logger.warn(SearchDbMsgs.INDEX_DELETE_FAILURE, index, (String) response.getBody ());
288 // Generate our audit log.
289 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
291 .setField(LogLine.DefinedFields.RESPONSE_CODE, response.getStatusCodeValue())
292 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION,
293 response.getStatusCode ().getReasonPhrase()),
294 (request != null) ? request.getMethod().toString () : "Unknown",
295 (request != null) ? request.getRequestURL ().toString () : "Unknown",
296 (request != null) ? request.getRemoteHost () : "Unknown",
297 Integer.toString(response.getStatusCodeValue()));
299 // Clear the MDC context so that no other transaction inadvertently
300 // uses our transaction id.
301 ApiUtils.clearMdcContext();
308 * This method takes a JSON format document schema and produces a set of
309 * field mappings in the form that Elastic Search expects.
311 * @param documentSchema - A document schema expressed as a JSON string.
312 * @return - A JSON string expressing an Elastic Search mapping configuration.
313 * @throws com.fasterxml.jackson.core.JsonParseException
314 * @throws com.fasterxml.jackson.databind.JsonMappingException
315 * @throws IOException
317 public String generateDocumentMappings(String documentSchema)
318 throws com.fasterxml.jackson.core.JsonParseException,
319 com.fasterxml.jackson.databind.JsonMappingException, IOException {
321 // Unmarshal the json content into a document schema object.
322 ObjectMapper mapper = new ObjectMapper();
323 DocumentSchema schema = mapper.readValue(documentSchema, DocumentSchema.class);
325 // Now, generate the Elastic Search mapping json and return it.
326 StringBuilder sb = new StringBuilder();
328 sb.append("\"properties\": {");
330 boolean first = true;
331 for (DocumentFieldSchema field : schema.getFields()) {
339 sb.append("\"").append(field.getName()).append("\": {");
341 // The field type is mandatory.
342 sb.append("\"type\": \"").append(field.getDataType()).append("\"");
344 // If the index field was specified, then append it.
345 if (field.getSearchable() != null) {
346 sb.append(", \"index\": \"").append(field.getSearchable()
347 ? "analyzed" : "not_analyzed").append("\"");
350 // If a search analyzer was specified, then append it.
351 if (field.getSearchAnalyzer() != null) {
352 sb.append(", \"search_analyzer\": \"").append(field.getSearchAnalyzer()).append("\"");
355 // If an indexing analyzer was specified, then append it.
356 if (field.getIndexAnalyzer() != null) {
357 sb.append(", \"analyzer\": \"").append(field.getIndexAnalyzer()).append("\"");
359 sb.append(", \"analyzer\": \"").append("whitespace").append("\"");
368 logger.debug("Generated document mappings: " + sb.toString());
370 return sb.toString();
375 * Converts an {@link OperationResult} to a standard REST {@link ResponseEntity}
378 * @param result - The {@link OperationResult} to be converted.
379 * @return - The equivalent {@link ResponseEntity} object.
381 public ResponseEntity<String> responseFromOperationResult(OperationResult result) {
383 if ((result.getResultCode() >= 200) && (result.getResultCode() < 300)) {
384 return ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(result.getResult());
386 if (result.getFailureCause() != null) {
387 return ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(result.getFailureCause());
389 return ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(result.getResult());
394 public ResponseEntity<String> errorResponse(HttpStatus status, String msg, HttpServletRequest request) {
396 // Generate our audit log.
397 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
399 .setField(LogLine.DefinedFields.RESPONSE_CODE, status.value ())
400 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, status.getReasonPhrase()),
401 (request != null) ? request.getMethod().toString () : "Unknown",
402 (request != null) ? request.getRequestURL ().toString () : "Unknown",
403 (request != null) ? request.getRemoteHost () : "Unknown",
404 Integer.toString(status.value ()));
406 // Clear the MDC context so that no other transaction inadvertently
407 // uses our transaction id.
408 ApiUtils.clearMdcContext();
410 return ResponseEntity.status(status).contentType ( MediaType.APPLICATION_JSON ).body(msg);
415 * A helper method used for validating/authenticating an incoming request.
417 * @param request - The http request that will be validated.
418 * @param headers - The http headers that will be validated.
419 * @param index - The name of the index that the document store request is being made against.
420 * @param failureMsgEnum - The logging message to be used upon validation failure.
421 * @return A success or failure response
423 private ResponseEntity<String> validateRequest(HttpServletRequest request, HttpHeaders headers, String index, SearchDbMsgs failureMsgEnum) {
425 if (!searchService.validateRequest(headers, request, ApiUtils.Action.POST, ApiUtils.SEARCH_AUTH_POLICY_NAME)) {
426 logger.warn(failureMsgEnum, index, "Authentication failure.");
427 return errorResponse(HttpStatus.FORBIDDEN, "Authentication failure.", request);
429 } catch (Exception e) {
430 logger.warn(failureMsgEnum, index, "Unexpected authentication failure - cause: " + e.getMessage());
431 return errorResponse(HttpStatus.FORBIDDEN, "Authentication failure.", request);
433 return ResponseEntity.status(HttpStatus.OK).body(HEADER_VALIDATION_SUCCESS);