org.eclipse.epp.internal.logging.aeri.ui.model.Reports.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.epp.internal.logging.aeri.ui.model.Reports.java

Source

/**
 * Copyright (c) 2015 Codetrails GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marcel Bruch - initial API and implementation.
 */
package org.eclipse.epp.internal.logging.aeri.ui.model;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Optional.fromNullable;
import static java.text.MessageFormat.format;
import static org.apache.commons.lang3.StringUtils.*;
import static org.eclipse.epp.internal.logging.aeri.ui.l10n.LogMessages.*;
import static org.eclipse.epp.internal.logging.aeri.ui.l10n.Logs.log;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.epp.internal.logging.aeri.ui.Constants;
import org.eclipse.epp.internal.logging.aeri.ui.model.util.ModelSwitch;
import org.eclipse.epp.internal.logging.aeri.ui.utils.AnonymousId;
import org.eclipse.epp.internal.logging.aeri.ui.utils.EmfFieldExclusionStrategy;
import org.eclipse.epp.internal.logging.aeri.ui.utils.UuidTypeAdapter;
import org.eclipse.epp.internal.logging.aeri.ui.v2.ServerConfiguration;
import org.osgi.framework.Bundle;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.Sets;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Reports {

    public static final class ClearMessagesVisitor extends ModelSwitch<Object> {
        @Override
        public Object caseStatus(Status status) {
            status.setMessage(Constants.HIDDEN);
            return null;
        }

        @Override
        public Object caseThrowable(Throwable throwable) {
            throwable.setMessage(Constants.HIDDEN);
            return null;
        }
    }

    public static final class NotWhitelistedClassDetectorVisitor extends ModelSwitch<Object> {
        private List<Pattern> whitelist;
        private boolean detected = false;

        public NotWhitelistedClassDetectorVisitor(List<Pattern> whitelist) {
            this.whitelist = whitelist;
        }

        @Override
        public Object caseThrowable(Throwable throwable) {
            if (!isWhitelisted(throwable.getClassName(), whitelist)) {
                detected = true;
                return detected;
            }
            return null;
        }

        @Override
        public Object caseStackTraceElement(StackTraceElement element) {
            if (!isWhitelisted(element.getClassName(), whitelist)) {
                detected = true;
                return detected;
            }
            return null;
        }

        public boolean isDetected() {
            return detected;
        }
    }

    public static final class AnonymizeStacktraceVisitor extends ModelSwitch<Object> {
        private List<Pattern> whitelist;

        public AnonymizeStacktraceVisitor(List<Pattern> whitelist) {
            this.whitelist = whitelist;
        }

        @Override
        public Object caseThrowable(Throwable throwable) {
            if (!isWhitelisted(throwable.getClassName(), whitelist)) {
                throwable.setClassName(Constants.HIDDEN);
            }
            return null;
        }

        @Override
        public Object caseStackTraceElement(StackTraceElement element) {
            if (!isWhitelisted(element.getClassName(), whitelist)) {
                element.setClassName(Constants.HIDDEN);
                element.setMethodName(Constants.HIDDEN);
                element.setFileName(Constants.HIDDEN);
                element.setLineNumber(-1);
            }
            return null;
        }
    }

    public static final class ThrowableFingerprintComputer extends ModelSwitch<Object> {

        private StringBuilder content = new StringBuilder();
        private List<Pattern> whitelist;
        private int maxframes;

        public ThrowableFingerprintComputer(List<Pattern> whitelist, int maxframes) {
            this.whitelist = whitelist;
            this.maxframes = maxframes;
        }

        @Override
        public Object caseStackTraceElement(StackTraceElement element) {
            if (maxframes < 0) {
                return Boolean.TRUE;
            }
            maxframes--;
            if (isWhitelisted(element.getClassName(), whitelist)) {
                content.append(element.getClassName()).append(element.getMethodName());
            }
            return null;
        }

        @Override
        public Object caseThrowable(Throwable throwable) {
            if (isWhitelisted(throwable.getClassName(), whitelist)) {
                content.append(throwable.getClassName());
            }
            return null;
        }

        public String hash() {
            return Hashing.murmur3_32().hashUnencodedChars(content.toString()).toString();
        }
    }

    public static final class CollectStackTraceElementPackagesVisitor extends ModelSwitch<Object> {
        public TreeSet<String> packages = Sets.newTreeSet();

        @Override
        public Object caseStackTraceElement(StackTraceElement element) {
            String pkg = replace(substringBeforeLast(element.getClassName(), "."), ".internal.", ".");
            packages.add(pkg);

            return null;
        }
    }

    public static class PrettyPrintVisitor extends ModelSwitch<Object> {
        private static final int RIGHT_PADDING = 20;
        private StringBuilder reportStringBuilder = new StringBuilder();
        private StringBuilder statusStringBuilder = new StringBuilder();
        private StringBuilder bundlesStringBuilder = new StringBuilder();

        public PrettyPrintVisitor() {
            bundlesStringBuilder = new StringBuilder();
            appendHeadline("BUNDLES", bundlesStringBuilder);
        }

        private void appendAttributes(EObject object, StringBuilder builder) {
            for (EAttribute attribute : object.eClass().getEAllAttributes()) {
                // String format = "%-" + RIGHT_PADDING + "s%s\n";
                // String line = String.format(format, attribute.getName(), value);
                Object value = firstNonNull(object.eGet(attribute), "");
                builder.append(StringUtils.rightPad(attribute.getName(), RIGHT_PADDING));
                builder.append(value);
                builder.append('\n');
            }
            builder.append("\n");
        }

        private void appendHeadline(String headline, StringBuilder builder) {
            if (builder.length() != 0) {
                builder.append("\n");
            }
            String line = headline.replaceAll(".", "-") + "\n";
            builder.append(line);
            builder.append(headline + "\n");
            builder.append(line);
        }

        @Override
        public Object caseErrorReport(ErrorReport report) {
            appendHeadline("REPORT", reportStringBuilder);
            appendAttributes(report, reportStringBuilder);
            return null;
        }

        @Override
        public Object caseStatus(Status status) {
            appendHeadline("STATUS", statusStringBuilder);
            appendAttributes(status, statusStringBuilder);
            Throwable exception = status.getException();
            if (exception != null) {
                statusStringBuilder.append("Exception:");
                append(exception, statusStringBuilder);
            }
            return null;
        }

        private void append(Throwable throwable, StringBuilder builder) {
            builder.append(String.format("%s: %s\n", throwable.getClassName(), throwable.getMessage()));
            for (StackTraceElement element : throwable.getStackTrace()) {
                builder.append(String.format("\t at %s.%s(%s:%s)\n", element.getClassName(),
                        element.getMethodName(), element.getFileName(), element.getLineNumber()));
            }
            Throwable cause = throwable.getCause();
            if (cause != null) {
                statusStringBuilder.append("Caused by: ");
                append(cause, builder);
            }
        }

        @Override
        public Object caseBundle(org.eclipse.epp.internal.logging.aeri.ui.model.Bundle bundle) {
            appendAttributes(bundle, bundlesStringBuilder);
            return null;
        }

        public String print() {
            return new StringBuilder().append(statusStringBuilder).append("\n").append(reportStringBuilder)
                    .append(bundlesStringBuilder).toString();
        }
    }

    public static final class ErrorAnalyserVisitor extends ModelSwitch<Object> {
        private static final ErrorAnalyser ANALYSER = new ErrorAnalyser();

        private Optional<String> errorComment = Optional.absent();

        private final List<org.eclipse.epp.internal.logging.aeri.ui.model.Bundle> presentBundles;

        public ErrorAnalyserVisitor(List<org.eclipse.epp.internal.logging.aeri.ui.model.Bundle> presentBundles) {
            this.presentBundles = presentBundles;
        }

        @Override
        public Object caseThrowable(Throwable throwable) {
            errorComment = ANALYSER.computeComment(presentBundles, throwable);
            if (errorComment.isPresent()) {
                return Boolean.TRUE;
            } else {
                return null;
            }
        }

        public Optional<String> getLinkageErrorComment() {
            return errorComment;
        }
    }

    private static class MultiStatusFilter {

        private static void filter(Status status) {
            HashSet<Throwable> throwables = new HashSet<Throwable>();
            filter(status, throwables);
        }

        private static void filter(Status status, Set<Throwable> throwables) {
            EList<Status> children = status.getChildren();
            int removedCount = 0;
            for (int i = children.size() - 1; i >= 0; i--) {
                Status childStatus = children.get(i);
                if (filterChild(childStatus, throwables)) {
                    children.remove(i);
                    removedCount++;
                } else {
                    filter(childStatus, throwables);
                }
            }
            if (removedCount > 0) {
                status.setMessage(String.format("%s [%d child-status duplicates removed by Error Reporting]",
                        status.getMessage(), removedCount));
            }
        }

        private static boolean filterChild(Status status, Set<Throwable> throwables) {
            Throwable throwable = status.getException();
            if (throwable.getStackTrace().isEmpty()) {
                return true;
            }
            for (Throwable t : throwables) {
                if (stackTraceMatches(throwable, t)) {
                    return true;
                }
            }
            throwables.add(throwable);
            return false;
        }

        private static boolean stackTraceMatches(Throwable throwable, Throwable t) {
            EList<StackTraceElement> stackTrace = throwable.getStackTrace();
            EList<StackTraceElement> stackTrace2 = t.getStackTrace();
            if (stackTrace.size() != stackTrace2.size()) {
                return false;
            }
            for (int i = 0; i < stackTrace.size(); i++) {
                StackTraceElement ste = stackTrace.get(i);
                StackTraceElement ste2 = stackTrace2.get(i);
                if (!classNameAndMethodNameEqual(ste, ste2)) {
                    return false;
                }
            }
            return true;
        }

        private static boolean classNameAndMethodNameEqual(StackTraceElement ste, StackTraceElement ste2) {
            return ste.getClassName().equals(ste2.getClassName())
                    && ste.getMethodName().equals(ste2.getMethodName());
        }

    }

    private static ModelFactory factory = ModelFactory.eINSTANCE;

    static boolean isWhitelisted(String className, List<Pattern> whitelist) {
        for (Pattern whitelistedPattern : whitelist) {
            if (whitelistedPattern.matcher(className).matches()) {
                return true;
            }
        }
        return false;
    }

    public static ErrorReport copy(ErrorReport org) {
        return EcoreUtil.copy(org);
    }

    public static String toJson(ErrorReport report, boolean pretty) {
        Gson gson = createGson(pretty);
        String json = gson.toJson(report);
        return json;
    }

    public static ErrorReport createAnonymizedSendCopy(ErrorReport report, Settings settings,
            ServerConfiguration configuration) {
        ErrorReport copy = copy(report);
        copy.setName(settings.getName());
        copy.setEmail(settings.getEmail());
        if (settings.isAnonymizeStrackTraceElements()) {
            anonymizeStackTrace(copy, configuration);
        }
        if (settings.isAnonymizeMessages()) {
            clearMessages(copy);
        }
        return copy;
    }

    private static Gson createGson(boolean pretty) {
        GsonBuilder builder = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
        builder.registerTypeAdapter(UUID.class, new UuidTypeAdapter());
        builder.addSerializationExclusionStrategy(new EmfFieldExclusionStrategy());
        if (pretty) {
            builder.setPrettyPrinting();
        }
        Gson gson = builder.create();
        return gson;
    }

    public static ErrorReport newErrorReport(IStatus event, Settings settings, ServerConfiguration configuration) {
        ErrorReport mReport = factory.createErrorReport();
        mReport.setAnonymousId(AnonymousId.getId());
        mReport.setName(settings.getName());
        mReport.setEmail(settings.getEmail());

        mReport.setJavaRuntimeVersion(SystemUtils.JAVA_RUNTIME_VERSION);
        mReport.setEclipseBuildId(getEclipseBuildId().or("-"));
        mReport.setEclipseProduct(System.getProperty("eclipse.product", "-"));
        mReport.setOsgiArch(System.getProperty("osgi.arch", "-"));
        mReport.setOsgiWs(System.getProperty("osgi.ws", "-"));
        mReport.setOsgiOs(System.getProperty(org.osgi.framework.Constants.FRAMEWORK_OS_NAME, "-"));
        mReport.setOsgiOsVersion(System.getProperty(org.osgi.framework.Constants.FRAMEWORK_OS_VERSION, "-"));
        mReport.setStatus(newStatus(event, configuration));

        return mReport;
    }

    public static Optional<String> getEclipseBuildId() {
        String res = System.getProperty("eclipse.buildId");
        return fromNullable(res);
    }

    public static void guessInvolvedPlugins(ErrorReport report) {
        CollectStackTraceElementPackagesVisitor v = new CollectStackTraceElementPackagesVisitor();
        visit(report, v);
        Set<String> uniqueBundleNames = Sets.newHashSet();
        for (String packageName : v.packages) {
            while (packageName.contains(".")) {
                Bundle guessedBundleForPackageName = Platform.getBundle(packageName);
                packageName = StringUtils.substringBeforeLast(packageName, ".");
                if (guessedBundleForPackageName != null) {
                    if (uniqueBundleNames.add(guessedBundleForPackageName.getSymbolicName())) {
                        org.eclipse.epp.internal.logging.aeri.ui.model.Bundle bundle = factory.createBundle();
                        bundle.setName(guessedBundleForPackageName.getSymbolicName());
                        bundle.setVersion(guessedBundleForPackageName.getVersion().toString());
                        report.getPresentBundles().add(bundle);
                    }
                    continue;
                }
            }
        }
    }

    public static void insertErrorAnalyseComment(ErrorReport report) {
        ErrorAnalyserVisitor visitor = new ErrorAnalyserVisitor(report.getPresentBundles());
        visit(report, visitor);
        Optional<String> comment = visitor.getLinkageErrorComment();
        if (comment.isPresent()) {
            report.setComment(comment.get());
        }
    }

    @VisibleForTesting
    public static Status newStatus(IStatus status, ServerConfiguration configuration) {
        Status mStatus = factory.createStatus();
        mStatus.setMessage(removeSourceFileContents(status.getMessage()));
        mStatus.setSeverity(status.getSeverity());
        mStatus.setCode(status.getCode());
        mStatus.setPluginId(status.getPlugin());

        Bundle bundle = Platform.getBundle(status.getPlugin());
        if (bundle != null) {
            mStatus.setPluginVersion(bundle.getVersion().toString());
        }

        List<Status> mChildren = mStatus.getChildren();
        java.lang.Throwable exception = status.getException();
        // CoreException handling
        for (java.lang.Throwable cur = exception; cur != null; cur = cur.getCause()) {
            if (cur instanceof CoreException) {
                CoreException coreException = (CoreException) cur;
                IStatus coreExceptionStatus = coreException.getStatus();
                Status mCoreExceptionStatus = newStatus(coreExceptionStatus, configuration);
                String detachedMessage = format(
                        "{0} [detached from CoreException of Status ''{1}'' by Error Reporting]",
                        mCoreExceptionStatus.getMessage(), mStatus.getMessage());
                mCoreExceptionStatus.setMessage(detachedMessage);
                mChildren.add(mCoreExceptionStatus);
                // further CoreExceptions are handled in the detached Status
                break;
            }
        }
        // Multistatus handling
        for (IStatus child : status.getChildren()) {
            mChildren.add(newStatus(child, configuration));
        }
        // some stacktraces from ui.monitoring should be filtered
        boolean needFiltering = "org.eclipse.ui.monitoring".equals(status.getPlugin())
                && (status.getCode() == 0 || status.getCode() == 1);
        if (needFiltering) {
            MultiStatusFilter.filter(mStatus);
        }

        if (exception != null) {
            Throwable mException = newThrowable(exception);
            mStatus.setException(mException);
        }

        mStatus.setFingerprint(computeFingerprintFor(mStatus, configuration));

        return mStatus;
    }

    public static String computeFingerprintFor(Status status, ServerConfiguration configuration) {
        ThrowableFingerprintComputer fingerprintComputer = new ThrowableFingerprintComputer(
                configuration.getAcceptedPackagesPatterns(), 1024);
        visit(status, fingerprintComputer);
        return fingerprintComputer.hash();
    }

    private static String removeSourceFileContents(String message) {
        if (message.contains(Constants.SOURCE_BEGIN_MESSAGE)) {
            return Constants.SOURCE_FILE_REMOVED;
        } else {
            return message;
        }
    }

    public static Throwable newThrowable(java.lang.Throwable throwable) {
        Throwable mThrowable = factory.createThrowable();
        mThrowable.setMessage(throwable.getMessage());
        mThrowable.setClassName(throwable.getClass().getName());
        List<StackTraceElement> mStackTrace = mThrowable.getStackTrace();
        for (java.lang.StackTraceElement stackTraceElement : throwable.getStackTrace()) {
            StackTraceElement mStackTraceElement = factory.createStackTraceElement();
            mStackTraceElement.setFileName(stackTraceElement.getFileName());
            mStackTraceElement.setClassName(ensureNotBlank(stackTraceElement.getClassName(), throwable));
            mStackTraceElement.setMethodName(ensureNotBlank(stackTraceElement.getMethodName(), throwable));
            mStackTraceElement.setLineNumber(stackTraceElement.getLineNumber());
            mStackTraceElement.setNative(stackTraceElement.isNativeMethod());
            mStackTrace.add(mStackTraceElement);
        }
        java.lang.Throwable cause = throwable.getCause();
        if (cause != null) {
            if (cause == throwable) {
                log(WARN_CYCLIC_EXCEPTION, cause.toString());
                return mThrowable;
            }
            mThrowable.setCause(newThrowable(cause));
        }
        return mThrowable;
    }

    private static String ensureNotBlank(String str, java.lang.Throwable throwable) {
        if (isBlank(str)) {
            // Note: we cannot call throwable.toString()) nor
            // reflectionToString() --> NPE:
            log(WARN_STACKTRACE_WITH_NULL);
            return Constants.MISSING;
        }
        return str;
    }

    public static void clearMessages(ErrorReport report) {
        visit(report, new ClearMessagesVisitor());
    }

    public static void anonymizeStackTrace(ErrorReport report, final ServerConfiguration configuration) {
        visit(report, new AnonymizeStacktraceVisitor(configuration.getAcceptedPackagesPatterns()));
    }

    public static boolean containsNotWhitelistedClasses(ErrorReport report,
            final ServerConfiguration configuration) {
        NotWhitelistedClassDetectorVisitor visitor = new NotWhitelistedClassDetectorVisitor(
                configuration.getAcceptedPackagesPatterns());
        visit(report, visitor);
        return visitor.isDetected();
    }

    public static String prettyPrint(ErrorReport report) {
        PrettyPrintVisitor prettyPrintVisitor = new PrettyPrintVisitor();
        visit(report, prettyPrintVisitor);
        return prettyPrintVisitor.print();
    }

    public static String getFingerprint(ErrorReport report) {
        return report.getStatus().getFingerprint();
    }

    public static <T, K extends EObject> T visit(K object, ModelSwitch<T> s) {
        T t = s.doSwitch(object);
        if (t != null) {
            return t;
        }
        TreeIterator<EObject> allContents = EcoreUtil.getAllContents(object, true);
        for (TreeIterator<EObject> iterator = allContents; iterator.hasNext();) {
            EObject modelElement = iterator.next();
            t = s.doSwitch(modelElement);
            if (t != null) {
                return t;
            }
        }
        return null;
    }

    public static String exactIdentityHash(ErrorReport report) {
        final Hasher hasher = Hashing.murmur3_128().newHasher();
        ModelSwitch<Hasher> s = new ModelSwitch<Hasher>() {
            @Override
            public Hasher caseErrorReport(ErrorReport object) {
                hasher.putString(stripToEmpty(object.getEclipseProduct()), UTF_8);
                hasher.putString(stripToEmpty(object.getEclipseBuildId()), UTF_8);
                hasher.putString(stripToEmpty(object.getJavaRuntimeVersion()), UTF_8);
                hasher.putString(stripToEmpty(object.getOsgiOs()), UTF_8);
                hasher.putString(stripToEmpty(object.getOsgiOsVersion()), UTF_8);
                hasher.putString(stripToEmpty(object.getOsgiArch()), UTF_8);
                hasher.putString(stripToEmpty(object.getOsgiWs()), UTF_8);
                return null;
            };

            @Override
            public Hasher caseStatus(Status object) {
                hasher.putString(stripToEmpty(object.getPluginId()), UTF_8);
                hasher.putString(stripToEmpty(object.getPluginVersion()), UTF_8);
                hasher.putString(stripToEmpty(object.getMessage()), UTF_8);
                hasher.putInt(object.getSeverity());
                hasher.putInt(object.getCode());
                return null;
            }

            @Override
            public Hasher caseBundle(org.eclipse.epp.internal.logging.aeri.ui.model.Bundle object) {
                hasher.putString(stripToEmpty(object.getName()), UTF_8);
                hasher.putString(stripToEmpty(object.getVersion()), UTF_8);
                return null;
            }

            @Override
            public Hasher caseStackTraceElement(StackTraceElement object) {
                hasher.putString(stripToEmpty(object.getClassName()), UTF_8);
                hasher.putString(stripToEmpty(object.getMethodName()), UTF_8);
                hasher.putInt(object.getLineNumber());
                return null;
            }

            @Override
            public Hasher caseThrowable(Throwable object) {
                hasher.putString(stripToEmpty(object.getClassName()), UTF_8);
                hasher.putString(stripToEmpty(object.getMessage()), UTF_8);
                return null;
            }
        };

        visit(report, s);
        String hash = hasher.hash().toString();
        return hash;
    }

    public static String traceIdentityHash(ErrorReport report) {
        final Hasher hasher = Hashing.murmur3_128().newHasher();
        visit(report, new ModelSwitch<Hasher>() {

            @Override
            public Hasher caseStackTraceElement(StackTraceElement element) {
                hasher.putString(element.getClassName(), UTF_8);
                hasher.putString(element.getMethodName(), UTF_8);
                hasher.putInt(element.getLineNumber());
                return null;
            }
        });
        String hash = hasher.hash().toString();
        return hash;
    }

    public static String toShortClassName(String className) {
        if (isBlank(className)) {
            return "";
        } else if (contains(className, '$')) {
            return substringAfterLast(className, "$");
        } else if (contains(className, '.')) {
            return substringAfterLast(className, ".");
        } else {
            return className;
        }
    }
}