org.jahia.bin.errors.ErrorFileDumper.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.bin.errors.ErrorFileDumper.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ==================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ===================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 */
package org.jahia.bin.errors;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.statistics.StatisticsGateway;

import org.apache.commons.collections.iterators.EnumerationIterator;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.jahia.bin.Jahia;
import org.jahia.exceptions.JahiaException;
import org.jahia.registries.ServicesRegistry;
import org.jahia.services.SpringContextSingleton;
import org.jahia.services.cache.Cache;
import org.jahia.services.cache.ehcache.EhCacheImpl;
import org.jahia.settings.SettingsBean;
import org.jahia.tools.jvm.ThreadMonitor;
import org.jahia.utils.RequestLoadAverage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.*;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.*;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * System information utility.
 * User: loom
 * Date: Jul 16, 2010
 * Time: 4:56:24 PM
 * IMPORTANT NOTE : As this code gets called by log4j appenders, please do not use Log4J loggers in this code !
 */
public class ErrorFileDumper {

    public static final FastDateFormat DATE_FORMAT_DIRECTORY = FastDateFormat.getInstance("yyyy_MM_dd");
    public static final FastDateFormat DATE_FORMAT_FILE = FastDateFormat.getInstance("yyyy_MM_dd-HH_mm_ss_SSS");

    private static final long MIN_INTERVAL_BETWEEN_QUEUE_WARNING = 1000L;
    private static final long MIN_INTERVAL_BETWEEN_HIGHLOAD_WARNING = 1000L;
    private static final Logger logger = LoggerFactory.getLogger(ErrorFileDumper.class);

    private static Throwable previousException = null;
    private static int previousExceptionOccurrences = 0;
    private static long totalExceptionCount = 0L;

    private static volatile ExecutorService executorService;
    private static volatile int maximumTasksAllowed = 100;
    private static volatile double highLoadBoundary = 10.0;
    private static volatile int tasksSubmitted = 0;
    private static volatile long lastCallToDump = 0;
    private static volatile long lastHighLoadMessageTime = 0;

    /**
     * A low priority thread factory
     */
    private static class LowPriorityThreadFactory implements ThreadFactory {

        public Thread newThread(Runnable runnable) {
            Thread lowPriorityThread = new Thread(runnable);
            lowPriorityThread.setName("LowPriorityThread");
            lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
            lowPriorityThread.setName("ErrorFileDumperThread");
            return lowPriorityThread;
        }
    }

    /**
     * Utility class to copy useful information from the HTTP request object, as we won't have access to it in
     * asynchronous threads.
     */
    public static class HttpRequestData {

        private String requestURL;
        private String queryString;
        private String method;
        private Map<String, String> headers = new HashMap<String, String>();
        private String remoteHost;
        private String remoteAddr;

        public HttpRequestData(HttpServletRequest request) {

            // When dealing with a forwarded request, we are still interested in the original URL and query string
            // which are stored as a dedicated request attributes
            String originalRequestUri = (String) request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
            if (originalRequestUri == null) {
                this.requestURL = request.getRequestURI();
            } else {
                this.requestURL = originalRequestUri;
            }
            String originalQueryString = (String) request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
            if (originalQueryString == null) {
                this.queryString = request.getQueryString();
            } else {
                this.queryString = originalQueryString;
            }

            this.method = request.getMethod();
            this.remoteHost = request.getRemoteHost();
            this.remoteAddr = request.getRemoteAddr();
            Iterator<?> headerNames = new EnumerationIterator(request.getHeaderNames());
            while (headerNames.hasNext()) {
                String headerName = (String) headerNames.next();
                String headerValue = request.getHeader(headerName);
                headers.put(headerName, headerValue);
            }
        }

        public String getRequestURL() {
            return requestURL;
        }

        public String getQueryString() {
            return queryString;
        }

        public String getMethod() {
            return method;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public String getRemoteHost() {
            return remoteHost;
        }

        public String getRemoteAddr() {
            return remoteAddr;
        }
    }

    private static class FileDumperRunnable implements Runnable {

        private Throwable t;
        private HttpRequestData requestData;

        public FileDumperRunnable(Throwable t, HttpRequestData requestData) {
            this.t = t;
            this.requestData = requestData;
        }

        public void run() {
            try {
                performDumpToFile(t, requestData);
                tasksSubmitted--;
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    public static void start() {
        if (isShutdown()) {
            executorService = Executors.newSingleThreadExecutor(new LowPriorityThreadFactory());
            System.out.println("Started error file dumper executor service");
        }
    }

    public static void shutdown() {
        shutdown(100L);
    }

    public static void shutdown(long millisecondsToWait) {
        if (!isShutdown()) {
            System.out.println("Shutting down error file dumper executor service...");
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(millisecondsToWait, TimeUnit.MILLISECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
            executorService = null;
            System.out.println("...done shutting down error file dumper executor service.");
        }
    }

    public static boolean isShutdown() {
        return (executorService == null);
    }

    public static void setFileDumpActivated(boolean fileDumpActivated) {
        if (fileDumpActivated && isShutdown()) {
            start();
        } else if (!fileDumpActivated && isFileDumpActivated()) {
            shutdown();
        }
    }

    public static boolean isFileDumpActivated() {
        return !isShutdown();
    }

    public static void dumpToFile(Throwable t, HttpServletRequest request) throws IOException {

        if (isShutdown()) {
            return;
        }

        if (isHighLoad()) {
            return;
        }

        int maxTasks = maximumTasksAllowed;
        if (tasksSubmitted < maxTasks) {
            HttpRequestData requestData = null;
            if (request != null) {
                requestData = new HttpRequestData(request);
            }
            executorService.submit(new FileDumperRunnable(t, requestData));
            tasksSubmitted++;
            lastCallToDump = System.currentTimeMillis();
        } else {
            long now = System.currentTimeMillis();
            if ((now - lastCallToDump) > MIN_INTERVAL_BETWEEN_QUEUE_WARNING) {
                System.out.println(maxTasks + " error dumps already submitted, not allowing any more.");
                lastCallToDump = now;
            }
        }
    }

    public static int getMaximumTasksAllowed() {
        return maximumTasksAllowed;
    }

    /**
     * Sets the maximum number of parallel dumping tasks allowed to be queued. Default value is 100.
     */
    public static void setMaximumTasksAllowed(int maximumTasksAllowed) {
        ErrorFileDumper.maximumTasksAllowed = maximumTasksAllowed;
    }

    private static void performDumpToFile(Throwable exception, HttpRequestData httpRequestData) throws IOException {

        long dumpStartTime = System.currentTimeMillis();

        Throwable previousExceptionToDump;
        int previousExceptionOccurencesToDump;
        long momentaryTotalExceptionCount;

        synchronized (ErrorFileDumper.class) {

            if (previousException != null && exception != null
                    && exception.getClass().equals(previousException.getClass()) // For performance reasons, to avoid unnecessary toString() invocations.
                    && exception.toString().equals(previousException.toString())) {

                previousExceptionOccurrences++;
                SettingsBean settings = SettingsBean.getInstance();
                if (settings != null
                        && previousExceptionOccurrences < settings.getFileDumpMaxRegroupingOfPreviousException()) {
                    return;
                }
            }

            previousExceptionToDump = previousException;
            previousExceptionOccurencesToDump = previousExceptionOccurrences;
            previousException = exception;
            previousExceptionOccurrences = 1;

            totalExceptionCount++;
            momentaryTotalExceptionCount = totalExceptionCount;
        }

        StringWriter errorWriter = generateErrorReport(httpRequestData, exception,
                previousExceptionOccurencesToDump, previousExceptionToDump);
        File errorFile = getNextErrorFile(momentaryTotalExceptionCount);
        FileUtils.writeStringToFile(errorFile, errorWriter.toString(), "UTF-8");

        long dumpTotalTime = System.currentTimeMillis() - dumpStartTime;
        System.err.println("Error dumped to file " + errorFile.getAbsolutePath() + " in " + dumpTotalTime + "ms");
    }

    private static File getNextErrorFile(long exceptionCount) {
        Date now = new Date();
        File todaysDirectory = new File(SettingsBean.getErrorDir(), DATE_FORMAT_DIRECTORY.format(now));
        todaysDirectory.mkdirs();
        return new File(todaysDirectory,
                "error-" + DATE_FORMAT_FILE.format(now) + "-" + Long.toString(exceptionCount) + ".txt");
    }

    private static File getNextHeapDumpFile() {
        Date now = new Date();
        File todaysDirectory = new File(SettingsBean.getHeapDir(),
                ErrorFileDumper.DATE_FORMAT_DIRECTORY.format(now));
        todaysDirectory.mkdirs();
        return new File(todaysDirectory, "heap-" + ErrorFileDumper.DATE_FORMAT_FILE.format(now) + ".hprof");
    }

    public static StringWriter generateErrorReport(HttpRequestData requestData, Throwable exception,
            int previousExceptionOccurences, Throwable previousException) {

        StringWriter msgBodyWriter = new StringWriter();
        if (isHighLoad()) {
            return msgBodyWriter;
        }
        PrintWriter strOut = new PrintWriter(msgBodyWriter);
        if (previousExceptionOccurences > 1) {
            strOut.println("");
            strOut.println("The previous error: " + previousException.getMessage() + " occured "
                    + Integer.toString(previousExceptionOccurences) + " times.");
            strOut.println("");
        }
        strOut.println("");
        strOut.println(
                "Your Server has generated an error. Please review the details below for additional information: ");
        strOut.println("");
        if (exception instanceof JahiaException) {
            JahiaException nje = (JahiaException) exception;
            String severityMsg = "Undefined";
            switch (nje.getSeverity()) {
            case JahiaException.WARNING_SEVERITY:
                severityMsg = "WARNING";
                break;
            case JahiaException.ERROR_SEVERITY:
                severityMsg = "ERROR";
                break;
            case JahiaException.CRITICAL_SEVERITY:
                severityMsg = "CRITICAL";
                break;
            case JahiaException.FATAL_SEVERITY:
                severityMsg = "FATAL";
                break;
            }
            strOut.println("Severity: " + severityMsg);
        }
        strOut.println("");
        if (exception != null) {
            strOut.println("Error: " + exception.getMessage());
        }
        strOut.println("");
        if (requestData != null) {
            strOut.println("URL: " + requestData.getRequestURL());
            if (requestData.getQueryString() != null) {
                strOut.println("?" + requestData.getQueryString());
            }
            strOut.println("");
            strOut.println("Method: " + requestData.getMethod());
            strOut.println("");
            strOut.println("Remote host: " + requestData.getRemoteHost() + "     Remote Address: "
                    + requestData.getRemoteAddr());
            strOut.println("");
            strOut.println("Request headers:");
            strOut.println("-----------------");
            Iterator<Map.Entry<String, String>> headerEntryIterator = requestData.getHeaders().entrySet()
                    .iterator();
            while (headerEntryIterator.hasNext()) {
                Map.Entry<String, String> headerEntry = headerEntryIterator.next();
                strOut.println("   " + headerEntry.getKey() + " : " + headerEntry.getValue());
            }
        }

        strOut.println("");
        strOut.println("Stack trace:");
        strOut.println("-------------");
        String stackTraceStr = stackTraceToString(exception);
        strOut.println(stackTraceStr);

        outputSystemInfo(strOut);

        strOut.println("");
        strOut.println(
                "Depending on the severity of this error, server may still be operational or not. Please check your");
        strOut.println("installation as soon as possible.");
        strOut.println("");
        strOut.println("Yours Faithfully, ");
        strOut.println("    Server Notification Service");
        return msgBodyWriter;
    }

    public static void outputSystemInfo(PrintWriter strOut) {
        outputSystemInfoConsiderLoad(strOut);
    }

    public static double getHighLoadBoundary() {
        return highLoadBoundary;
    }

    /**
     * Sets the boundary load value above which all reporting will automatically deactivate. The default value is 10.0
     *
     * @param highLoadBoundary
     */
    public static void setHighLoadBoundary(double highLoadBoundary) {
        ErrorFileDumper.highLoadBoundary = highLoadBoundary;
    }

    private static void outputSystemInfoConsiderLoad(PrintWriter strOut) {
        boolean highLoad = isHighLoad();
        outputSystemInfo(strOut, !highLoad, !highLoad, !highLoad, !highLoad, !highLoad, !highLoad, !highLoad, true);
    }

    private static boolean isHighLoad() {
        boolean highLoad = false;
        RequestLoadAverage loadAverage = RequestLoadAverage.getInstance();
        highLoad = loadAverage != null && loadAverage.getOneMinuteLoad() > highLoadBoundary;
        if (highLoad) {
            long now = System.currentTimeMillis();
            if ((now - lastHighLoadMessageTime) > MIN_INTERVAL_BETWEEN_HIGHLOAD_WARNING) {
                System.out.println("High load (" + loadAverage.getOneMinuteLoad()
                        + ") detected, will deactivate reporting...");
            }
            lastHighLoadMessageTime = System.currentTimeMillis();
        }
        return highLoad;
    }

    public static void outputSystemInfo(PrintWriter strOut, boolean systemProperties, boolean jahiaSettings,
            boolean memory, boolean caches, boolean threads, boolean deadlocks, boolean loadAverage) {
        outputSystemInfo(strOut, systemProperties, false, jahiaSettings, memory, caches, threads, deadlocks,
                loadAverage);
    }

    public static void outputSystemInfo(PrintWriter strOut, boolean systemProperties, boolean environmentVariables,
            boolean jahiaSettings, boolean memory, boolean caches, boolean threads, boolean deadlocks,
            boolean loadAverage) {

        if (systemProperties) {
            // now let's output the system properties.
            strOut.println();
            strOut.println("System properties:");
            strOut.println("-------------------");
            Map<Object, Object> orderedProperties = new TreeMap<Object, Object>(System.getProperties());
            Iterator<Map.Entry<Object, Object>> entrySetIter = orderedProperties.entrySet().iterator();
            while (entrySetIter.hasNext()) {
                Map.Entry<Object, Object> curEntry = entrySetIter.next();
                String curPropertyName = (String) curEntry.getKey();
                String curPropertyValue = (String) curEntry.getValue();
                strOut.println("   " + curPropertyName + " : " + curPropertyValue);
            }
        }

        if (environmentVariables) {
            // now let's output the environment variables.
            strOut.println();
            strOut.println("Environment variables:");
            strOut.println("-------------------");
            Map<String, String> orderedProperties = new TreeMap<String, String>(System.getenv());
            Iterator<Map.Entry<String, String>> entrySetIter = orderedProperties.entrySet().iterator();
            while (entrySetIter.hasNext()) {
                Map.Entry<String, String> curEntry = entrySetIter.next();
                String curPropertyName = curEntry.getKey();
                String curPropertyValue = curEntry.getValue();
                strOut.println("   " + curPropertyName + " : " + curPropertyValue);
            }
        }

        if (jahiaSettings) {
            strOut.println();

            if (SettingsBean.getInstance() != null) {
                strOut.append("Server configuration (").append(Jahia.getFullProductVersion()).append(" - ")
                        .append(Jahia.getBuildDate()).append("):");
                strOut.println();
                strOut.println("---------------------");
                SettingsBean settings = SettingsBean.getInstance();
                Map<Object, Object> jahiaOrderedProperties = new TreeMap<Object, Object>(
                        settings.getPropertiesFile());
                Iterator<Map.Entry<Object, Object>> jahiaEntrySetIter = jahiaOrderedProperties.entrySet()
                        .iterator();
                while (jahiaEntrySetIter.hasNext()) {
                    Map.Entry<Object, Object> curEntry = jahiaEntrySetIter.next();
                    String curPropertyName = (String) curEntry.getKey();
                    String curPropertyValue = null;
                    if (curEntry.getValue() == null) {
                        curPropertyValue = null;
                    } else if (curEntry.getValue() instanceof String) {
                        curPropertyValue = (String) curEntry.getValue();
                    } else {
                        curPropertyValue = curEntry.getValue().toString();
                    }
                    if (curPropertyName.toLowerCase().indexOf("password") == -1
                            && (!"mail_server".equals(curPropertyName)
                                    || !StringUtils.contains(curPropertyValue, "&password=")
                                            && !StringUtils.contains(curPropertyValue, "?password="))) {
                        strOut.println("   " + curPropertyName + " = " + curPropertyValue);
                    }
                }
            }
        }

        if (memory) {
            MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
            MemoryUsage memoryUsage = memoryMXBean.getHeapMemoryUsage();
            printMemoryUsage(MemoryType.HEAP.toString(), memoryUsage, strOut);
            memoryUsage = memoryMXBean.getNonHeapMemoryUsage();
            printMemoryUsage(MemoryType.NON_HEAP.toString(), memoryUsage, strOut);

            strOut.println("--------------");
            strOut.println("Memory pool details");
            strOut.println("--------------");
            List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
            for (MemoryPoolMXBean bean : memoryPoolMXBeans) {
                printMemoryUsage("Memory Pool \"" + bean.getName() + "\" (" + bean.getType().toString() + ")",
                        bean.getUsage(), strOut);
            }
        }

        if (caches) {
            strOut.println();
            DecimalFormat percentFormat = new DecimalFormat("###.##");
            if (SpringContextSingleton.getInstance().isInitialized()
                    && (ServicesRegistry.getInstance().getCacheService() != null)) {
                strOut.println("Cache status:");
                strOut.println("--------------");

                // non Ehcaches
                SortedSet<String> sortedCacheNames = new TreeSet<>(
                        ServicesRegistry.getInstance().getCacheService().getNames());
                for (String sortedCacheName : sortedCacheNames) {
                    final Cache<Object, Object> objectCache = ServicesRegistry.getInstance().getCacheService()
                            .getCache(sortedCacheName);
                    if (objectCache != null
                            && !(((Cache<?, ?>) objectCache).getCacheImplementation() instanceof EhCacheImpl)) {
                        String efficiencyStr = "0";
                        if (!Double.isNaN(objectCache.getCacheEfficiency())) {
                            efficiencyStr = percentFormat.format(objectCache.getCacheEfficiency());
                        }
                        strOut.println(sortedCacheName + ": size=" + objectCache.size() + ", successful hits="
                                + objectCache.getSuccessHits() + ", total hits=" + objectCache.getTotalHits()
                                + ", efficiency=" + efficiencyStr + "%");
                    }
                }
            }

            // Ehcaches
            List<CacheManager> cacheManagers = CacheManager.ALL_CACHE_MANAGERS;
            for (CacheManager ehcacheManager : cacheManagers) {
                String[] ehcacheNames = ehcacheManager.getCacheNames();
                java.util.Arrays.sort(ehcacheNames);
                for (String ehcacheName : ehcacheNames) {
                    Ehcache ehcache = ehcacheManager.getEhcache(ehcacheName);
                    strOut.append(ehcacheName).append(": ");
                    if (ehcache != null) {
                        StatisticsGateway ehcacheStats = ehcache.getStatistics();
                        String efficiencyStr = "0";
                        if (ehcacheStats.cacheHitCount() + ehcacheStats.cacheMissCount() > 0) {
                            efficiencyStr = percentFormat.format(ehcacheStats.cacheHitCount() * 100f
                                    / (ehcacheStats.cacheHitCount() + ehcacheStats.cacheMissCount()));
                        }
                        strOut.append("size=" + ehcacheStats.getSize() + ", successful hits="
                                + ehcacheStats.cacheHitCount() + ", total hits="
                                + (ehcacheStats.cacheHitCount() + ehcacheStats.cacheMissCount()) + ", efficiency="
                                + efficiencyStr + "%");
                        strOut.println();
                    }
                }
            }
        }

        ThreadMonitor threadMonitor = null;
        if (threads) {
            strOut.println();
            strOut.println("Thread status:");
            strOut.println("--------------");
            threadMonitor = ThreadMonitor.getInstance();
            threadMonitor.generateThreadInfo(strOut);
        }

        if (deadlocks) {
            strOut.println();
            strOut.println("Deadlock status:");
            threadMonitor = threadMonitor != null ? threadMonitor : ThreadMonitor.getInstance();
            ;
            String deadlock = threadMonitor.findDeadlock();
            strOut.println(deadlock != null ? deadlock : "none");
        }

        if (loadAverage) {
            strOut.println();
            strOut.println("Request load average:");
            strOut.println("---------------------");
            RequestLoadAverage info = RequestLoadAverage.getInstance();
            if (info != null) {
                strOut.println("Over one minute=" + info.getOneMinuteLoad() + " Over five minute="
                        + info.getFiveMinuteLoad() + " Over fifteen minute=" + info.getFifteenMinuteLoad());
            } else {
                strOut.println("not available");
            }
            strOut.println();
        }

        strOut.flush();
    }

    private static void printMemoryUsage(String type, MemoryUsage usage, PrintWriter strOut) {
        strOut.println();
        strOut.print(type);
        strOut.print(" : ");
        strOut.print(Math.round((float) usage.getUsed() / (float) usage.getMax() * 100f));
        strOut.println("% used");
        strOut.println("---------------");
        strOut.print("Used      : ");
        strOut.println(org.jahia.utils.FileUtils.humanReadableByteCount(usage.getUsed(), true));
        strOut.print("Committed : ");
        strOut.println(org.jahia.utils.FileUtils.humanReadableByteCount(usage.getCommitted(), true));
        strOut.print("Max       : ");
        strOut.println(org.jahia.utils.FileUtils.humanReadableByteCount(usage.getMax(), true));
    }

    /**
     * Converts an exception stack trace to a string, going doing into all
     * the embedded exceptions too to detail as much as possible the real
     * causes of the error.
     *
     * @param t the exception (eventually that contains other exceptions) for
     *          which we want to convert the stack trace into a string.
     * @return a string containing all the stack traces of all the exceptions
     *         contained inside this exception, or an empty string if passed an
     *         empty string.
     */
    protected static String stackTraceToString(Throwable t) {
        int nestingDepth = getNestedExceptionDepth(t, 0);
        return recursiveStackTraceToString(t, nestingDepth);
    }

    protected static String recursiveStackTraceToString(Throwable t, int curDepth) {
        if (t == null) {
            return "";
        }
        StringWriter msgBodyWriter = new StringWriter();
        PrintWriter strOut = new PrintWriter(msgBodyWriter);
        Throwable innerThrowable = t.getCause();
        if (innerThrowable != null) {
            String innerExceptionTrace = recursiveStackTraceToString(innerThrowable, curDepth - 1);
            msgBodyWriter.write(innerExceptionTrace);
        }
        if (curDepth == 0) {
            strOut.println("Cause level : " + curDepth + " (level 0 is the most precise exception)");

        } else {
            strOut.println("Cause level : " + curDepth);
        }
        t.printStackTrace(strOut);
        return msgBodyWriter.toString();
    }

    protected static int getNestedExceptionDepth(Throwable t, int curDepth) {
        if (t == null) {
            return curDepth;
        }
        int newDepth = curDepth;
        Throwable innerThrowable = t.getCause();
        if (innerThrowable != null) {
            newDepth = getNestedExceptionDepth(innerThrowable, curDepth + 1);
        }
        return newDepth;
    }

    public static boolean isHeapDumpSupported() throws MalformedObjectNameException {
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName hotSpotDiagnostic = new ObjectName("com.sun.management:type=HotSpotDiagnostic");
        return mBeanServer.isRegistered(hotSpotDiagnostic);
    }

    public static File performHeapDump()
            throws MalformedObjectNameException, InstanceNotFoundException, ReflectionException, MBeanException {
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName hotSpotDiagnostic = new ObjectName("com.sun.management:type=HotSpotDiagnostic");
        if (!mBeanServer.isRegistered(hotSpotDiagnostic)) {
            throw new UnsupportedOperationException("Unable to find the "
                    + "'com.sun.management:type=HotSpotDiagnostic'" + " managed bean to perform heap dump");
        }
        File hprofFilePath = getNextHeapDumpFile();
        mBeanServer.invoke(hotSpotDiagnostic, "dumpHeap", new Object[] { hprofFilePath.getPath(), Boolean.TRUE },
                new String[] { String.class.getName(), boolean.class.getName() });
        return hprofFilePath;
    }
}