/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.core.config; import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.api.legacy.exception.ExceptionReader; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.context.notification.MuleContextNotificationListener; import org.mule.runtime.core.api.registry.ServiceType; import org.mule.runtime.core.config.i18n.CoreMessages; import org.mule.runtime.core.context.notification.MuleContextNotification; import org.mule.runtime.core.context.notification.NotificationException; import org.mule.runtime.core.util.PropertiesUtils; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <code>ExceptionHelper</code> provides a number of helper functions that can be useful for dealing with Mule exceptions. This * class has 3 core functions - * <p/> * 1. ErrorCode lookup. A corresponding Mule error code can be found using for a given Mule exception 2. Additional Error * information such as Java doc url for a given exception can be resolved using this class 3. Error code mappings can be looked up * by providing the the protocol to map to and the Mule exception. */ public final class ExceptionHelper extends org.mule.runtime.api.exception.ExceptionHelper { public static final String RESOURCE_ROOT = "META-INF/"; /** * This is the property to set the error code to no the message it is the property name the Transport provider uses set the set * the error code on the underlying message */ private static final String ERROR_CODE_PROPERTY = "error.code.property"; /** * logger used by this class */ private static final Logger logger = LoggerFactory.getLogger(ExceptionHelper.class); private static String J2SE_VERSION = ""; /** * todo How do you get the j2ee version?? */ private static final String J2EE_VERSION = "1.3ee"; private static Properties errorDocs = new Properties(); private static Properties errorCodes = new Properties(); private static Map<String, Properties> errorMappings = new HashMap<>(); private static Map<String, Boolean> disposeListenerRegistered = new HashMap<>(); private static boolean initialised = false; static { initialise(); } /** * Do not instanciate. */ private ExceptionHelper() { super(); } private static void initialise() { try { if (initialised) { return; } J2SE_VERSION = System.getProperty("java.specification.version"); String name = RESOURCE_ROOT + ServiceType.EXCEPTION.getPath() + "/mule-exception-codes.properties"; InputStream in = ExceptionHelper.class.getClassLoader().getResourceAsStream(name); if (in == null) { throw new IllegalArgumentException("Failed to load resource: " + name); } errorCodes.load(in); in.close(); name = RESOURCE_ROOT + ServiceType.EXCEPTION.getPath() + "/mule-exception-config.properties"; in = ExceptionHelper.class.getClassLoader().getResourceAsStream(name); if (in == null) { throw new IllegalArgumentException("Failed to load resource: " + name); } errorDocs.load(in); in.close(); initialised = true; } catch (Exception e) { throw new MuleRuntimeException(CoreMessages.failedToLoad("Exception resources"), e); } } public static int getErrorCode(Class exception) { // TODO MULE-10834 - We won't be using error code for now. return -1; } private static Properties getErrorMappings(String protocol, final MuleContext muleContext) { Properties m = errorMappings.get(getErrorMappingCacheKey(protocol, muleContext)); if (m != null) { return m; } else { String name = RESOURCE_ROOT + ServiceType.EXCEPTION.getPath() + "/" + protocol + "-exception-mappings.properties"; Properties p = PropertiesUtils.loadAllProperties(name, muleContext.getExecutionClassLoader()); errorMappings.put(getErrorMappingCacheKey(protocol, muleContext), p); registerAppDisposeListener(muleContext); return p; } } private static void registerAppDisposeListener(MuleContext muleContext) { if (!disposeListenerRegistered.containsKey(muleContext.getConfiguration().getId())) { try { muleContext.registerListener(createClearCacheListenerOnContextDispose(muleContext)); disposeListenerRegistered.put(muleContext.getConfiguration().getId(), true); } catch (NotificationException e) { throw new MuleRuntimeException(e); } } } private static MuleContextNotificationListener<MuleContextNotification> createClearCacheListenerOnContextDispose(final MuleContext muleContext) { return new MuleContextNotificationListener<MuleContextNotification>() { @Override public boolean isBlocking() { return false; } @Override public void onNotification(MuleContextNotification notification) { if (notification.getAction() == MuleContextNotification.CONTEXT_DISPOSED) { clearCacheFor(muleContext); disposeListenerRegistered.remove(notification.getMuleContext().getConfiguration().getId()); } } }; } private static String getErrorMappingCacheKey(String protocol, MuleContext muleContext) { return protocol + "-" + muleContext.getConfiguration().getId(); } public static String getErrorCodePropertyName(String protocol, MuleContext muleContext) { protocol = protocol.toLowerCase(); Properties mappings = getErrorMappings(protocol, muleContext); if (mappings == null) { return null; } return mappings.getProperty(ERROR_CODE_PROPERTY); } /** * Maps an exception thrown for a certain protocol to an error. When there's no specific error for such transport it will return * a generic error. Most likely the returned error is an integer code. * * @param protocol scheme for the transport * @param exception exception mapped to error * @param muleContext the application context * @return the error for exception for the specific protocol */ public static String getErrorMapping(String protocol, Class exception, MuleContext muleContext) { String code = getTransportErrorMapping(protocol, exception, muleContext); if (code != null) { return code; } code = String.valueOf(getErrorCode(exception)); // Finally lookup mapping based on error code and return the Mule error // code if a match is not found return getErrorMappings(protocol, muleContext).getProperty(code, code); } /** * * Maps an exception thrown for a certain protocol to an error. Most likely the returned error is an integer code. * * @param protocol scheme for the transport * @param exception exception mapped to error * @param muleContext the application context * @return the error for exception for the specific protocol */ public static String getTransportErrorMapping(String protocol, Class exception, MuleContext muleContext) { protocol = protocol.toLowerCase(); Properties mappings = getErrorMappings(protocol, muleContext); if (mappings == null) { logger.info("No mappings found for protocol: " + protocol); return String.valueOf(getErrorCode(exception)); } Class clazz = exception; String code = null; while (!clazz.equals(Object.class)) { code = mappings.getProperty(clazz.getName()); if (code == null) { clazz = clazz.getSuperclass(); } else { return code; } } return null; } /** * @deprecated since 3.8.0 */ @Deprecated public static String getJavaDocUrl(Class<?> exception) { return getDocUrl("javadoc.", exception.getName()); } /** * @deprecated since 3.8.0 */ @Deprecated public static String getDocUrl(Class<?> exception) { return getDocUrl("doc.", exception.getName()); } private static String getDocUrl(String prefix, String packageName) { String key = prefix; if (packageName.startsWith("java.") || packageName.startsWith("javax.")) { key += J2SE_VERSION; } String url = getUrl(key, packageName); if (url == null && (packageName.startsWith("java.") || packageName.startsWith("javax."))) { key = prefix + J2EE_VERSION; url = getUrl(key, packageName); } if (url != null) { if (!url.endsWith("/")) { url += "/"; } String s = packageName.replaceAll("[.]", "/"); s += ".html"; url += s; } return url; } private static String getUrl(String key, String packageName) { String url = null; if (!key.endsWith(".")) { key += "."; } while (packageName.length() > 0) { url = errorDocs.getProperty(key + packageName, null); if (url == null) { int i = packageName.lastIndexOf("."); if (i == -1) { packageName = ""; } else { packageName = packageName.substring(0, i); } } else { break; } } return url; } public static Throwable sanitizeIfNeeded(Throwable t) { return fullStackTraces ? t : sanitize(t); } public static Throwable getRootParentException(Throwable t) { Throwable cause = t; Throwable parent = t; while (cause != null) { if (cause.getCause() == null) { return parent; } parent = cause; cause = getExceptionReader(cause).getCause(cause); // address some misbehaving exceptions, avoid endless loop if (t == cause) { break; } } return t; } public static <T> T traverseCauseHierarchy(Throwable e, ExceptionEvaluator<T> evaluator) { LinkedList<Throwable> exceptions = new LinkedList<>(); exceptions.add(e); while (e.getCause() != null && !e.getCause().equals(e)) { exceptions.addFirst(e.getCause()); e = e.getCause(); } for (Throwable exception : exceptions) { T value = evaluator.evaluate(exception); if (value != null) { return value; } } return null; } public static String writeException(Throwable t) { ExceptionReader er = getExceptionReader(t); StringBuilder msg = new StringBuilder(); msg.append(er.getMessage(t)).append(". Type: ").append(t.getClass()); return msg.toString(); } public static interface ExceptionEvaluator<T> { T evaluate(Throwable e); } private static void clearCacheFor(MuleContext muleContext) { List<String> entriesToRemove = new ArrayList<>(); for (String key : errorMappings.keySet()) { if (key.endsWith(muleContext.getConfiguration().getId())) { entriesToRemove.add(key); } } for (String key : entriesToRemove) { errorMappings.remove(key); } } }