2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2023 Deutsche Telekom SA.
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=========================================================
22 package org.onap.aai.logging;
24 import java.io.FileInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
32 import java.util.Objects;
33 import java.util.stream.Collectors;
34 import java.util.Properties;
35 import java.util.Map.Entry;
37 import javax.ws.rs.core.MediaType;
39 import org.apache.commons.lang3.StringUtils;
40 import org.onap.aai.domain.errorResponse.ErrorMessage;
41 import org.onap.aai.domain.errorResponse.ExceptionType;
42 import org.onap.aai.domain.errorResponse.Fault;
43 import org.onap.aai.domain.errorResponse.Info;
44 import org.onap.aai.exceptions.AAIException;
45 import org.onap.aai.util.AAIConstants;
46 import org.onap.logging.filter.base.Constants;
47 import org.onap.logging.filter.base.MDCSetup;
48 import org.onap.logging.ref.slf4j.ONAPLogConstants;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
53 import com.fasterxml.jackson.core.JsonProcessingException;
54 import com.fasterxml.jackson.databind.ObjectMapper;
55 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
59 * This classes loads the application error properties file
60 * and provides a method that returns an ErrorObject
64 public class ErrorLogHelper {
65 private static final Logger LOGGER = LoggerFactory.getLogger(ErrorLogHelper.class);
66 private static final HashMap<String, ErrorObject> ERROR_OBJECTS = new HashMap<String, ErrorObject>();
67 private static final ObjectMapper objectMapper = new ObjectMapper();
68 private static final XmlMapper xmlMapper = new XmlMapper();
73 } catch (IOException e) {
74 throw new RuntimeException("Failed to load error.properties file", e);
75 } catch (ErrorObjectFormatException e) {
76 throw new RuntimeException("Failed to parse error.properties file", e);
83 * @throws IOException the exception
84 * @throws ErrorObjectFormatException
86 public static void loadProperties() throws IOException, ErrorObjectFormatException {
87 final String filePath = AAIConstants.AAI_HOME_ETC_APP_PROPERTIES + "error.properties";
88 final InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("error.properties");
89 final Properties properties = new Properties();
91 try (final FileInputStream fileInputStream = new FileInputStream(filePath)) {
92 LOGGER.info("Found the error.properties in the following location: {}",
93 AAIConstants.AAI_HOME_ETC_APP_PROPERTIES);
94 properties.load(fileInputStream);
95 } catch (Exception ex) {
96 LOGGER.info("Unable to find the error.properties from filesystem so using file in jar");
97 if (inputStream != null) {
98 properties.load(inputStream);
100 LOGGER.error("Expected to find the error.properties in the jar but unable to find it");
104 for (Entry<Object, Object> entry : properties.entrySet()) {
105 final String key = (String) entry.getKey();
106 final String value = (String) entry.getValue();
107 final String[] errorProperties = value.split(":");
109 if (errorProperties.length < 7)
110 throw new ErrorObjectFormatException();
112 final ErrorObject errorObject = new ErrorObject();
114 errorObject.setDisposition(errorProperties[0].trim());
115 errorObject.setCategory(errorProperties[1].trim());
116 errorObject.setSeverity(errorProperties[2].trim());
117 errorObject.setErrorCode(errorProperties[3].trim());
118 errorObject.setHTTPResponseCode(errorProperties[4].trim());
119 errorObject.setRESTErrorCode(errorProperties[5].trim());
120 errorObject.setErrorText(errorProperties[6].trim());
121 if (errorProperties.length > 7) {
122 errorObject.setAaiElsErrorCode(errorProperties[7].trim());
125 ERROR_OBJECTS.put(key, errorObject);
130 * Logs a known A&AI exception (i.e. one that can be found in error.properties)
132 * @param code for the error in the error.properties file
133 * @throws IOException
134 * @throws ErrorObjectNotFoundException
136 public static ErrorObject getErrorObject(String code) {
139 throw new IllegalArgumentException("Key cannot be null");
141 final ErrorObject errorObject = ERROR_OBJECTS.get(code);
143 if (errorObject == null) {
144 LOGGER.warn("Unknown AAIException with code=" + code + ". Using default AAIException");
145 return ERROR_OBJECTS.get(AAIException.DEFAULT_EXCEPTION_CODE);
152 * Determines whether category is policy or not. If policy (1), this is a POL error, else it's a SVC error.
153 * The AAIRESTException may contain a different ErrorObject than that created with the REST error key.
154 * This allows lower level exception detail to be returned to the client to help troubleshoot the problem.
155 * If no error object is embedded in the AAIException, one will be created using the error object from the
158 * @param aaiException must have a restError value whose numeric value must match what should be returned in the REST API
159 * @param variables optional list of variables to flesh out text in error string
160 * @return appropriately formatted JSON response per the REST API spec.
161 * @throws IOException
164 public static String getRESTAPIErrorResponse(AAIException aaiException, ArrayList<String> variables) {
165 List<MediaType> acceptHeaders = Collections.singletonList(MediaType.APPLICATION_JSON_TYPE);
167 return getRESTAPIErrorResponse(acceptHeaders, aaiException, variables);
171 * Determines whether category is policy or not. If policy (1), this is a POL error, else it's a SVC error.
172 * The AAIRESTException may contain a different ErrorObject than that created with the REST error key.
173 * This allows lower level exception detail to be returned to the client to help troubleshoot the problem.
174 * If no error object is embedded in the AAIException, one will be created using the error object from the
177 * @param aaiException
181 public static Fault getErrorResponse(AAIException aaiException,
182 ArrayList<String> variables) {
183 final ErrorObject restErrorObject = getRestErrorObject(aaiException);
184 final String text = createText(restErrorObject);
185 final int placeholderCount = StringUtils.countMatches(restErrorObject.getErrorText(), "%");
186 variables = checkAndEnrichVariables(aaiException, variables, placeholderCount);
188 if (aaiException.getErrorObject().getCategory().equals("1")) {
189 return createPolicyFault(aaiException, text, variables);
191 return createServiceFault(aaiException, text, variables);
197 * @param acceptHeaders the accept headers orig
198 * @param aaiException must have a restError value whose numeric value must match what should be returned in the REST API
199 * @param variables optional list of variables to flesh out text in error string
200 * @return appropriately formatted JSON response per the REST API spec.
201 * @deprecated in favor of {@link #getErrorResponse(AAIException, ArrayList)}
204 public static String getRESTAPIErrorResponse(List<MediaType> acceptHeaders, AAIException aaiException,
205 ArrayList<String> variables) {
207 List<MediaType> validAcceptHeaders = new ArrayList<MediaType>();
208 // we might have an exception but no accept header, so we'll set default to JSON
209 boolean foundValidAcceptHeader = false;
210 for (MediaType mediaType : acceptHeaders) {
211 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mediaType) || MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType)) {
212 validAcceptHeaders.add(mediaType);
213 foundValidAcceptHeader = true;
216 if (foundValidAcceptHeader == false) {
217 // override the exception, client needs to set an appropriate Accept header
218 aaiException = new AAIException("AAI_4014");
219 validAcceptHeaders.add(MediaType.APPLICATION_JSON_TYPE);
222 MediaType mediaType = validAcceptHeaders.stream()
223 .filter(MediaType.APPLICATION_JSON_TYPE::isCompatible)
225 .orElse(MediaType.APPLICATION_XML_TYPE);
227 Fault fault = getErrorResponse(aaiException, variables);
229 return MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType)
230 ? objectMapper.writeValueAsString(fault)
231 : xmlMapper.writeValueAsString(fault);
232 } catch (JsonProcessingException ex) {
234 "We were unable to create a rest exception to return on an API because of a parsing error "
240 private static String createText(ErrorObject restErrorObject) {
241 final StringBuilder text = new StringBuilder();
242 text.append(restErrorObject.getErrorText());
244 // We want to always append the (msg=%n) (ec=%n+1) to the text, but have to find value of n
245 // This assumes that the variables in the ArrayList, which might be more than are needed to flesh out the
246 // error, are ordered based on the error string.
247 int placeholderCount = StringUtils.countMatches(restErrorObject.getErrorText(), "%");
248 text.append(" (msg=%").append(placeholderCount + 1).append(") (ec=%").append(placeholderCount + 2).append(")");
249 return text.toString();
253 * Gets the RESTAPI error response with logging.
255 * @param acceptHeaders the accept headers orig
256 * @param aaiException the are
257 * @param variables the variables
259 public static String getRESTAPIErrorResponseWithLogging(List<MediaType> acceptHeaders, AAIException aaiException,
260 ArrayList<String> variables) {
261 logException(aaiException);
262 return ErrorLogHelper.getRESTAPIErrorResponse(acceptHeaders, aaiException, variables);
266 * Gets the RESTAPI info response.
268 * @param acceptHeaders this param is ignored
269 * @param aaiExceptionsMap the map of AAIException
270 * @return the RESTAPI info response
271 * @deprecated {@link ErrorLogHelper#}
272 * @deprecated in favor of {@link #getRestApiInfoResponse(HashMap)}
275 public static Object getRESTAPIInfoResponse(ArrayList<MediaType> acceptHeaders,
276 Map<AAIException, ArrayList<String>> aaiExceptionsMap) {
277 return (Object) getRestApiInfoResponse(aaiExceptionsMap);
281 * Gets the RESTAPI info response.
283 * @param acceptHeaders the accept headers
284 * @param aaiExceptionsMap the are list
285 * @return the RESTAPI info response
287 public static Info getRestApiInfoResponse(
288 Map<AAIException, ArrayList<String>> aaiExceptionsMap) {
289 List<ErrorMessage> errorMessages = aaiExceptionsMap.entrySet().stream()
290 .map(entry -> createResponseMessage(entry))
291 .filter(Objects::nonNull)
292 .collect(Collectors.toList());
293 return new Info(errorMessages);
296 private static ErrorMessage createResponseMessage(Entry<AAIException, ArrayList<String>> entry) {
297 AAIException aaiException = entry.getKey();
298 ArrayList<String> variables = entry.getValue();
300 ErrorObject restErrorObject = getRestErrorObject(aaiException);
301 final String text = createText(restErrorObject);
302 final int placeholderCount = StringUtils.countMatches(aaiException.getErrorObject().getErrorText(), "%");
303 variables = checkAndEnrichVariables(aaiException, variables, placeholderCount);
306 return ErrorMessage.builder()
307 .messageId("INF" + aaiException.getErrorObject().getRESTErrorCode())
309 .variables(variables)
311 } catch (Exception ex) {
312 LOGGER.error("We were unable to create a rest exception to return on an API because of a parsing error "
319 * Determines whether category is policy or not. If policy (1), this is a POL error, else it's a SVC error.
320 * The AAIRESTException may contain a different ErrorObject than that created with the REST error key.
321 * This allows lower level exception detail to be returned to the client to help troubleshoot the problem.
322 * If no error object is embedded in the AAIException, one will be created using the error object from the
325 * @param aaiException must have a restError value whose numeric value must match what should be returned in the REST API
326 * @param variables optional list of variables to flesh out text in error string
327 * @return appropriately formatted JSON response per the REST API spec.
329 public static String getRESTAPIPolicyErrorResponseXML(AAIException aaiException, ArrayList<String> variables) {
330 return getRESTAPIErrorResponse(Collections.singletonList(MediaType.APPLICATION_XML_TYPE), aaiException, variables);
333 public static void logException(AAIException aaiException) {
334 final ErrorObject errorObject = aaiException.getErrorObject();
336 * String severityCode = errorObject.getSeverityCode(errorObject.getSeverity());
338 * Severify should be left empty per Logging Specification 2019.11
339 * if (!StringUtils.isEmpty(severityCode)) {
340 * int sevCode = Integer.parseInt(severityCode);
341 * if (sevCode > 0 && sevCode <= 3) {
342 * LoggingContext.severity(sevCode);
346 String stackTrace = "";
348 stackTrace = LogFormatTools.getStackTop(aaiException);
349 } catch (Exception a) {
352 final String errorMessage = new StringBuilder().append(errorObject.getErrorText()).append(":")
353 .append(errorObject.getRESTErrorCode()).append(":").append(errorObject.getHTTPResponseCode())
354 .append(":").append(aaiException.getMessage()).toString().replaceAll("\\n", "^");
356 MDCSetup mdcSetup = new MDCSetup();
357 mdcSetup.setResponseStatusCode(errorObject.getHTTPResponseCode().getStatusCode());
358 mdcSetup.setErrorCode(Integer.parseInt(errorObject.getAaiElsErrorCode()));
359 String serviceName = MDC.get(ONAPLogConstants.MDCs.SERVICE_NAME);
360 if (serviceName == null || serviceName.isEmpty()) {
361 MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, Constants.DefaultValues.UNKNOWN);
363 MDC.put(ONAPLogConstants.MDCs.ERROR_DESC, errorMessage);
364 final String details =
365 new StringBuilder().append(errorObject.getErrorCodeString()).append(" ").append(stackTrace).toString();
367 if (errorObject.getSeverity().equalsIgnoreCase("WARN"))
368 LOGGER.warn(details);
369 else if (errorObject.getSeverity().equalsIgnoreCase("ERROR"))
370 LOGGER.error(details);
371 else if (errorObject.getSeverity().equalsIgnoreCase("FATAL"))
372 LOGGER.error(details);
373 else if (errorObject.getSeverity().equals("INFO"))
374 LOGGER.info(details);
377 public static void logError(String code) {
381 public static void logError(String code, String message) {
382 logException(new AAIException(code, message));
385 private static ErrorObject getRestErrorObject(AAIException aaiException) {
386 return ErrorLogHelper.getErrorObject("AAI_" + aaiException.getErrorObject().getRESTErrorCode());
389 public static Fault createPolicyFault(AAIException aaiException, String text, List<String> variables) {
390 return createFault(aaiException, text, variables, ExceptionType.POLICY);
392 public static Fault createServiceFault(AAIException aaiException, String text, List<String> variables) {
393 return createFault(aaiException, text, variables, ExceptionType.SERVICE);
395 private static Fault createFault(AAIException aaiException, String text, List<String> variables, ExceptionType exceptionType) {
396 String typePrefix = ExceptionType.POLICY.equals(exceptionType) ? "POL" : "SVC";
397 Map<ExceptionType, ErrorMessage> requestError = Collections.singletonMap(exceptionType,
398 ErrorMessage.builder()
399 .messageId(typePrefix+ aaiException.getErrorObject().getRESTErrorCode())
401 .variables(variables)
403 return new Fault(requestError);
406 private static ArrayList<String> checkAndEnrichVariables(AAIException aaiException, ArrayList<String> variables, int placeholderCount) {
407 final ErrorObject errorObject = aaiException.getErrorObject();
408 if (variables == null) {
409 variables = new ArrayList<String>();
412 // int placeholderCount = StringUtils.countMatches(errorObject.getErrorText(), "%");
413 if (variables.size() < placeholderCount) {
414 ErrorLogHelper.logError("AAI_4011", "data missing for rest error");
415 while (variables.size() < placeholderCount) {
416 variables.add("null");
420 // This will put the error code and error text into the right positions
421 if (aaiException.getMessage() == null || aaiException.getMessage().length() == 0) {
422 variables.add(placeholderCount++, errorObject.getErrorText());
424 variables.add(placeholderCount++, errorObject.getErrorText() + ":" + aaiException.getMessage());
426 variables.add(placeholderCount, errorObject.getErrorCodeString());