/******************************************************************************* * Copyright (c) 2006-2010 eBay Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 *******************************************************************************/ package org.ebayopensource.turmeric.runtime.sif.impl.pipeline; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.ebayopensource.turmeric.runtime.common.errors.ErrorSubcategory; import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceExceptionInterface; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceInvocationException; import org.ebayopensource.turmeric.runtime.common.exceptions.TransportException; import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager; import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants; import org.ebayopensource.turmeric.runtime.sif.pipeline.ClientMessageContext; import org.ebayopensource.turmeric.runtime.sif.pipeline.ErrorResponseAdapter; import org.ebayopensource.turmeric.runtime.sif.service.ClientServiceContext; import org.ebayopensource.turmeric.runtime.sif.service.ClientServiceId; import org.ebayopensource.turmeric.common.v1.types.CommonErrorData; import org.ebayopensource.turmeric.common.v1.types.ErrorMessage; /** * @author rmurphy, ichernyshev */ public class ExceptionMatcher { private final ClientServiceId m_svcId; private final String m_compName; private final Set<String> m_transportCodes; private final Set<String> m_exceptions; private final Set<Long> m_errorIds; private final Set<ErrorSubcategory> m_errorSubcategories; public ExceptionMatcher(ClientServiceId svcId, String compName, Collection<String> transportCodes, Collection<String> exceptions, Collection<String> errorIds, Collection<String> excludedTransportCodes, Collection<String> excludedExceptions, Collection<String> excludedErrorIds) throws ServiceException { if (svcId == null || compName == null) { throw new NullPointerException(); } m_svcId = svcId; m_compName = compName; m_transportCodes = new HashSet<String>(); if (transportCodes != null) { for (String name: transportCodes) { if (name.length() == 0) { continue; } name = name.toUpperCase(); if (!checkExcludedId(name, excludedTransportCodes)) { continue; } m_transportCodes.add(name); } } m_exceptions = new HashSet<String>(); if (exceptions != null) { for (String name: exceptions) { if (name.length() == 0) { continue; } if (!checkExcludedId(name, excludedExceptions)) { continue; } m_exceptions.add(name); } } m_errorIds = new HashSet<Long>(); m_errorSubcategories = new HashSet<ErrorSubcategory>(); if (errorIds != null) { for (String name: errorIds) { if (name.length() == 0) { continue; } name = name.toUpperCase(); if (!checkExcludedId(name, excludedErrorIds)) { continue; } if (addSpecialErrorId(name)) { continue; } long errorId; try { errorId = Long.parseLong(name); } catch (NumberFormatException e) { throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_FACTORY_INVALID_ERROR_LIST, ErrorConstants.ERRORDOMAIN, new Object[] {svcId, name})); } m_errorIds.add(Long.valueOf(errorId)); } } } private boolean checkExcludedId(String name, Collection<String> excludeList) { if (excludeList == null || !excludeList.contains(name)) { return true; } LogManager.getInstance(ExceptionMatcher.class).log(Level.SEVERE, m_compName + " in service " + m_svcId.getAdminName() + " refers disallowed error " + name + ". This error will not be honored"); return false; } private boolean addSpecialErrorId(String name) { if ("COMM".equals(name)) { m_errorSubcategories.add(ErrorSubcategory.TRANSPORT_RECEIVE); m_errorSubcategories.add(ErrorSubcategory.TRANSPORT_SEND); return true; } if ("COMM_CONNECT".equals(name)) { m_errorIds.add(Long.valueOf(ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_CONNECT_EXCEPTION, ErrorConstants.ERRORDOMAIN).getErrorId())); m_errorIds.add(Long.valueOf(ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_CONNECT_TIMEOUT_EXCEPTION, ErrorConstants.ERRORDOMAIN).getErrorId())); m_errorIds.add(Long.valueOf(ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_COMM_FAILURE, ErrorConstants.ERRORDOMAIN).getErrorId())); return true; } if ("COMM_RECV".equals(name)) { m_errorSubcategories.add(ErrorSubcategory.TRANSPORT_RECEIVE); return true; } if ("COMM_SEND".equals(name)) { m_errorSubcategories.add(ErrorSubcategory.TRANSPORT_SEND); return true; } return false; } public String getMatchingError(ClientMessageContext ctx, Throwable exception) { if (exception instanceof ServiceInvocationException) { ServiceInvocationException invException = (ServiceInvocationException) exception; // Check client-side errors for (Throwable clientException : invException.getClientErrors()) { String result = getMatchingTransportException(clientException); if (result != null) { return result; } result = getMatchingException(clientException, true); if (result != null) { return result; } } // test the application exception embedded in the exception Throwable applicationException = invException.getApplicationException(); if (applicationException != null) { String result = getMatchingException(applicationException, true); if (result != null) { return result; } } // check error response Object errorResponse = invException.getErrorResponse(); if (errorResponse != null) { String result = getMatchingResponse(ctx, errorResponse); if (result != null) { return result; } } return getMatchingException(exception, false); } // For exceptions coming directly up from the framework, see if these are in the list. // This is a rarer use case, since most exceptions come as part of a ServiceInvocationException. String result = getMatchingException(exception, true); if (result != null) { return result; } return null; } /** * Returns true if any of the original exceptions (usually in ErrorData inside the message) * are matching this list */ protected String getMatchingResponse(ClientMessageContext ctx, Object errorResponse) { if (errorResponse instanceof ErrorMessage) { List<CommonErrorData> errorDataList = ((ErrorMessage) errorResponse).getError(); for (CommonErrorData errorData : errorDataList) { String exceptionName = errorData.getExceptionId(); Long errorId = Long.valueOf(errorData.getErrorId()); String result = getMatchingError(exceptionName, errorId); if (result != null) { return result; } } return null; } // this is not a standard error message, try custom response handler if (ctx != null) { ClientServiceContext svcCtx = ctx.getServiceContext(); ErrorResponseAdapter responseAdapter = svcCtx.getCustomErrorResponseAdapter(); if (responseAdapter != null) { String exceptionName = null; try { exceptionName = responseAdapter.getExceptionClassName(errorResponse); } catch (Throwable e) { getLogger().log(Level.SEVERE, "ErrorResponseAdapter '" + responseAdapter.getClass().getName() + "' threw unexpected error in getExceptionClassName " + e.toString(), e); } Long errorId = null; try { errorId = responseAdapter.getErrorId(errorResponse); } catch (Throwable e) { getLogger().log(Level.SEVERE, "ErrorResponseAdapter '" + responseAdapter.getClass().getName() + "' threw unexpected error in getErrorId " + e.toString(), e); } String result = getMatchingError(exceptionName, errorId); if (result != null) { return result; } } } return null; } /** * Returns true if the exception is listed in the exception list */ protected String getMatchingException(Throwable exception, boolean checkExceptionClass) { // This does a naming test only. It would be a good feature to test that the incoming exception // is an instance of the named exception, i.e. the incoming exception might be a derived class. // To test this, we'd have to instantiate all the named exceptions, which is cumbersome, // so we'll leave that as an exercise for the service writer Long errorId = null; if (exception instanceof ServiceExceptionInterface) { ServiceExceptionInterface ex2 = (ServiceExceptionInterface)exception; ErrorMessage msg = ex2.getErrorMessage(); if (msg != null) { List<CommonErrorData> errorDatas = msg.getError(); if (errorDatas != null && !errorDatas.isEmpty()) { CommonErrorData errorData = errorDatas.get(0); if (errorData != null) { errorId = Long.valueOf(errorData.getErrorId()); } } } } if (checkExceptionClass) { String className = exception.getClass().getName(); return getMatchingError(className, errorId); } return getMatchingError(null, errorId); } /** * Returns true if an individual error (normally an ErrorData in the ErrorMessage) * matches based on either the configured exception list, or the configured error ID list */ protected String getMatchingError(String exceptionName, Long errorId) { if (exceptionName != null && m_exceptions.contains(exceptionName)) { return exceptionName; } if (errorId != null && m_errorIds.contains(errorId)) { return "Err" + errorId.toString(); } return null; } /** * Returns true if the exception is a TransportException whose error code is listed in * the transport code list */ protected String getMatchingTransportException(Throwable exception) { if (!(exception instanceof TransportException)) { return null; } TransportException transportException = (TransportException)exception; /* If we have a ErrorSubcategory.TRANSPORT_SEND problem, it means that we didnt even get to send a message/invoke at the address/connect to * the url. In that case, we simply want to retry again. The markdown/retry logic will take care of the rest. * If we do special checks for this, we will need every ClientConfig.xml to have the statuscode=0 listed in the retry options and make it mandatory * which is too clumsy to be a valid solution. Hence, check here and shortcut the process. As long as we send a string back, the exception is considered * retry-able. */ if(transportException.getSubcategory().getName().equals(ErrorSubcategory.TRANSPORT_SEND.getName())) return "Transport0"; String code = transportException.getStatusCode(); if (code != null) { code = code.toUpperCase(); if (m_transportCodes.contains(code)) { return "Transport" + code; } } return null; } private Logger getLogger() { return LogManager.getInstance(ExceptionMatcher.class); } }