| /** |
| * 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 |
| */ |
| package org.eclipse.epp.internal.logging.aeri.ide.server.mars; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Predicates.alwaysFalse; |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| import static org.apache.commons.lang3.StringUtils.isNotBlank; |
| import static org.eclipse.epp.internal.logging.aeri.ide.IIdePackage.*; |
| import static org.eclipse.epp.internal.logging.aeri.ide.processors.AnonymizeStackTracesProcessor.CTX_ACCEPTED_PACKAGES_PATTERNS; |
| import static org.eclipse.epp.logging.aeri.core.IModelPackage.*; |
| import static org.eclipse.epp.logging.aeri.core.util.Formats.format; |
| import static org.eclipse.epp.logging.aeri.core.util.Links.*; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeoutException; |
| |
| import javax.annotation.PostConstruct; |
| import javax.annotation.PreDestroy; |
| import javax.inject.Inject; |
| |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.http.client.fluent.Executor; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.e4.core.contexts.IEclipseContext; |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.common.notify.impl.AdapterImpl; |
| import org.eclipse.epp.internal.logging.aeri.ide.IServerDescriptor; |
| import org.eclipse.epp.internal.logging.aeri.ide.l10n.LogMessages; |
| import org.eclipse.epp.internal.logging.aeri.ide.l10n.Messages; |
| import org.eclipse.epp.internal.logging.aeri.ide.server.LocalReportsHistory; |
| import org.eclipse.epp.internal.logging.aeri.ide.server.LocalReportsHistory.LocalHistorySeenFilter; |
| import org.eclipse.epp.internal.logging.aeri.ide.server.rest.RestBasedProblemsHistory; |
| import org.eclipse.epp.logging.aeri.core.IModelFactory; |
| import org.eclipse.epp.logging.aeri.core.IProblemState; |
| import org.eclipse.epp.logging.aeri.core.IReport; |
| import org.eclipse.epp.logging.aeri.core.IReportProcessor; |
| import org.eclipse.epp.logging.aeri.core.ISendOptions; |
| import org.eclipse.epp.logging.aeri.core.IServerConnection; |
| import org.eclipse.epp.logging.aeri.core.ISystemSettings; |
| import org.eclipse.epp.logging.aeri.core.ProblemStatus; |
| import org.eclipse.epp.logging.aeri.core.SendMode; |
| import org.eclipse.epp.logging.aeri.core.filters.AcceptFreezeFilter; |
| import org.eclipse.epp.logging.aeri.core.filters.AcceptedPluginsFilter; |
| import org.eclipse.epp.logging.aeri.core.filters.AcceptedProductsFilter; |
| import org.eclipse.epp.logging.aeri.core.filters.DecoratingDebugFilter; |
| import org.eclipse.epp.logging.aeri.core.filters.RequiredPackagesFilter; |
| import org.eclipse.epp.logging.aeri.core.filters.StatusIgnorePatternsFilter; |
| import org.eclipse.epp.logging.aeri.core.util.Formats; |
| import org.eclipse.epp.logging.aeri.core.util.Logs; |
| import org.eclipse.epp.logging.aeri.core.util.Reports; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.util.concurrent.AbstractIdleService; |
| |
| public class ServerConnection extends AbstractIdleService implements IServerConnection { |
| |
| private final class UpdateDatabaseOnFeatureChangeAdapter extends AdapterImpl { |
| private final Class<?> featureClass; |
| private final int[] observedFeatureIds; |
| |
| UpdateDatabaseOnFeatureChangeAdapter(Class<?> featureClass, int... observedFeatureIds) { |
| this.featureClass = featureClass; |
| this.observedFeatureIds = observedFeatureIds; |
| } |
| |
| @Override |
| public void notifyChanged(Notification msg) { |
| int featureID = msg.getFeatureID(featureClass); |
| if (msg.getEventType() == Notification.SET && ArrayUtils.contains(observedFeatureIds, featureID)) { |
| if (!shouldUse()) { |
| return; |
| } |
| for (IProblemsHistory remoteHistory : remoteHistories) { |
| remoteHistory.sync(io, systemSettings); |
| } |
| } |
| } |
| } |
| |
| private final IServerDescriptor server; |
| private final ISystemSettings systemSettings; |
| private final File configurationArea; |
| private Predicate<IStatus> statusFilters = alwaysFalse(); |
| private IO io; |
| private LocalReportsHistory localHistory; |
| private final List<IProblemsHistory> remoteHistories = new ArrayList<>(); |
| |
| @Inject |
| public ServerConnection(IServerDescriptor descriptor, ISystemSettings system, File configurationArea) { |
| this.systemSettings = checkNotNull(system); |
| this.configurationArea = checkNotNull(configurationArea); |
| this.server = checkNotNull(descriptor); |
| } |
| |
| @PostConstruct |
| private void e4Start() { |
| startAsync(); |
| } |
| |
| @Override |
| protected void startUp() throws Exception { |
| try { |
| { |
| File file = new File(configurationArea, "server-config.json"); //$NON-NLS-1$ |
| io = createIO(file); |
| if (file.exists()) { |
| io.loadConfiguration(); |
| } |
| if (io.isConfigurationOutdated()) { |
| io.refreshConfiguration(checkNotNull(getLink(server, REL_DISCOVERY)).getHref(), new NullProgressMonitor()); |
| io.saveConfiguration(); |
| } |
| } |
| |
| { |
| File localHistoryStateLocation = new File(configurationArea, "local-history"); //$NON-NLS-1$ |
| localHistoryStateLocation.mkdirs(); |
| localHistory = new LocalReportsHistory(localHistoryStateLocation); |
| localHistory.startAsync(); |
| } |
| |
| { |
| if (io.getConfiguration().getInterestUrl() != null) { |
| File cacheDir = new File(configurationArea, "http-cache"); |
| cacheDir.mkdirs(); |
| remoteHistories.add(createRestBasedProblemsHistory(cacheDir)); |
| } |
| if (io.getConfiguration().getProblemsUrl() != null) { |
| remoteHistories.add(createServerProblemsHistory()); |
| } |
| |
| if (shouldUse()) { |
| for (IProblemsHistory remoteHistory : remoteHistories) { |
| remoteHistory.sync(io, systemSettings); |
| } |
| } |
| |
| server.eAdapters().add(new UpdateDatabaseOnFeatureChangeAdapter(IServerDescriptor.class, SERVER_DESCRIPTOR__ENABLED, |
| SERVER_DESCRIPTOR__CONFIGURED)); |
| systemSettings.eAdapters().add(new UpdateDatabaseOnFeatureChangeAdapter(ISystemSettings.class, SYSTEM_SETTINGS__CONFIGURED, |
| SYSTEM_SETTINGS__SEND_MODE)); |
| } |
| |
| { |
| // make sure we can operate before removing the AlwaysFalse filter... |
| ServerConfiguration configuration = io.getConfiguration(); |
| checkNotNull(configuration, "no configuration available"); //$NON-NLS-1$ |
| checkNotNull(localHistory); |
| |
| @SuppressWarnings("unchecked") |
| Predicate<? super IStatus>[] statusFilters = DecoratingDebugFilter.decorate( |
| // @formatter:off |
| new LocalHistorySeenFilter(localHistory, systemSettings), |
| new AcceptedProductsFilter(configuration.getAcceptedProductsPatterns()), |
| new RequiredPackagesFilter(configuration.getRequiredPackagesPatterns()), |
| new AcceptedPluginsFilter(configuration.getAcceptedPluginsPatterns()), |
| new StatusIgnorePatternsFilter(configuration.getIgnoredPluginMessagesPatterns()), |
| new AcceptFreezeFilter(configuration.isAcceptUiFreezes()) |
| // @formatter:on |
| ); |
| this.statusFilters = Predicates.and(statusFilters); |
| } |
| } catch (Exception e) { |
| Logs.log(LogMessages.WARN_SERVER_FAILURE, e, server.getId(), e.getMessage()); |
| // IdleService is missing proper means to set state to FAILED. |
| // If something went wrong, at least set its state to !RUNNNING |
| stopAsync(); |
| } |
| } |
| |
| private IProblemsHistory createRestBasedProblemsHistory(File cacheDir) throws IOException { |
| return new RestBasedProblemsHistory(io.getConfiguration(), cacheDir); |
| } |
| |
| @VisibleForTesting |
| protected IProblemsHistory createServerProblemsHistory() { |
| File remoteHistoryStateLocation = new File(configurationArea, "remote-history"); //$NON-NLS-1$ |
| ProblemsDatabaseProblemsHistory remoteHistory = new ProblemsDatabaseProblemsHistory(remoteHistoryStateLocation); |
| remoteHistory.startAsync(); |
| return remoteHistory; |
| } |
| |
| private boolean shouldUse() { |
| if (!systemSettings.isConfigured() || systemSettings.getSendMode() == SendMode.NEVER) { |
| return false; |
| } |
| if (!server.isConfigured() || !server.isEnabled()) { |
| return false; |
| } |
| return true; |
| } |
| |
| @VisibleForTesting |
| protected IO createIO(File file) { |
| return new IO(Executor.newInstance(), file); |
| } |
| |
| @Override |
| public IProblemState interested(IStatus status, IEclipseContext context, IProgressMonitor monitor) { |
| if (!isRunning() || !statusFilters.apply(status)) { |
| IProblemState res = IModelFactory.eINSTANCE.createProblemState(); |
| res.setStatus(ProblemStatus.IGNORED); |
| return res; |
| } else if (!shouldUse()) { |
| // FIXME |
| // This prevents remote requests (IProblemsHistory.seen ) to the server before Aeri has been enabled by the user. |
| // The downside is that problems that occur before Aeri is enabled will be shown as unconfirmed. |
| // Ideally, once Aeri is enabled, the server is queried and the response is used. This requires bigger changes in the control |
| // flow however. |
| return problemStateUnconfirmedBeforeSend(); |
| } else { |
| IProblemState seen = null; |
| for (IProblemsHistory remoteHistory : remoteHistories) { |
| seen = remoteHistory.seen(status).orNull(); |
| if (seen == null) { |
| continue; |
| } |
| |
| String message = seen.getMessage(); |
| if (seen.getMessage() != null) { |
| return seen; |
| } |
| switch (seen.getStatus()) { |
| case IGNORED: |
| return seen; |
| case NEEDINFO: |
| message = msgNeedinfoBeforeSend(seen); |
| break; |
| case WONTFIX: |
| message = msgWontFixBeforeSend(seen); |
| break; |
| case FIXED: |
| message = msgFixedBeforeSend(seen); |
| break; |
| case UNCONFIRMED: |
| message = msgUnconfirmedBeforeSend(); |
| break; |
| default: |
| message = Formats.format("Unexpected state {0}", seen.getStatus()); //$NON-NLS-1$ |
| } |
| seen.setMessage(message); |
| break; |
| } |
| if (seen == null) { |
| return problemStateUnconfirmedBeforeSend(); |
| } |
| return seen; |
| } |
| } |
| |
| private IProblemState problemStateUnconfirmedBeforeSend() { |
| IProblemState res = IModelFactory.eINSTANCE.createProblemState(); |
| res.setStatus(ProblemStatus.UNCONFIRMED); |
| res.setMessage(msgUnconfirmedBeforeSend()); |
| return res; |
| } |
| |
| @Override |
| public IReport transform(IStatus status, IEclipseContext context) { |
| // set the accepted patterns for the AnonymizeStackTracesProcessor |
| context.set(CTX_ACCEPTED_PACKAGES_PATTERNS, io.getConfiguration().getAcceptedPackagesPatterns()); |
| |
| ISendOptions options = checkNotNull(context.get(ISendOptions.class)); |
| IReport report = Reports.newReport(status); |
| report.setComment(options.getComment()); |
| for (IReportProcessor processor : options.getEnabledProcessors()) { |
| processor.process(report, status, context); |
| } |
| report.setAnonymousId(options.getReporterId()); |
| report.setName(options.getReporterName()); |
| report.setEmail(options.getReporterEmail()); |
| report.setSeverity(options.getSeverity()); |
| return report; |
| } |
| |
| @Override |
| public IProblemState submit(IStatus status, IEclipseContext context, IProgressMonitor monitor) throws IOException { |
| IReport report = transform(status, context); |
| IProblemState response = io.upload(report, monitor); |
| localHistory.remember(status); |
| |
| String message = response.getMessage(); |
| if (isNotBlank(message)) { |
| message = StringUtils.replace(message, "{link,", "{0,link,"); |
| message = format(message, response); |
| message = format(Messages.PROBLEM_MESSAGES_FORWARD_SERVER_RESPONSE, server.getName(), message); |
| response.setMessage(message); |
| return response; |
| } |
| switch (response.getStatus()) { |
| case NEW: |
| message = msgNewAfterSend(response); |
| break; |
| case UNCONFIRMED: |
| message = msgUnconfirmedAfterSend(response); |
| break; |
| case CONFIRMED: |
| message = msgConfirmedAfterSend(response); |
| break; |
| case FIXED: |
| message = msgFixedAfterSend(response); |
| break; |
| case NEEDINFO: |
| message = msgNeedinfoAfterSend(response); |
| break; |
| case FAILURE: |
| message = msgFailure(); |
| break; |
| case IGNORED: |
| case INVALID: |
| message = msgInvalidOrIgnored(response); |
| break; |
| } |
| response.setMessage(message); |
| return response; |
| } |
| |
| private String msgFailure() { |
| return format(Messages.PROBLEM_MESSAGES_SERVER_FAILURE, server.getName()); |
| } |
| |
| private String msgInvalidOrIgnored(IProblemState response) { |
| return format(Messages.PROBLEM_MESSAGES_IGNORED_OR_INVALID_STATUS, server.getName(), response); |
| } |
| |
| @Override |
| public void discarded(IStatus status, IEclipseContext context) { |
| localHistory.remember(status); |
| } |
| |
| protected String msgNewAfterSend(IProblemState response) { |
| return format(Messages.PROBLEM_MESSAGES_NEW_AFTER_SEND, server.getName(), response); |
| } |
| |
| protected String msgUnconfirmedBeforeSend() { |
| return format(Messages.PROBLEM_MESSAGES_UNCONFIRMED_BEFORE_SEND, server.getName()); |
| } |
| |
| protected String msgUnconfirmedAfterSend(IProblemState response) { |
| return format(Messages.PROBLEM_MESSAGES_UNCONFIRMED_AFTER_SEND, server.getName(), response); |
| } |
| |
| protected String msgConfirmedAfterSend(IProblemState response) { |
| if (hasLink(response, REL_BUG)) { |
| return format(Messages.PROBLEM_MESSAGES_CONFIRMED_BUG_AFTER_SEND, server.getName(), response); |
| } |
| return format(Messages.PROBLEM_MESSAGES_CONFIRMED_NO_BUG_AFTER_SEND, server.getName(), response); |
| } |
| |
| protected String msgNeedinfoBeforeSend(IProblemState cachedState) { |
| if (hasLink(cachedState, REL_BUG)) { |
| return Formats.format(Messages.PROBLEM_MESSAGES_NEEDINFO_BUG_BEFORE_SEND, server.getName(), cachedState); |
| } |
| return Formats.format(Messages.PROBLEM_MESSAGES_NEEDINFO_NO_BUG_BEFORE_SEND, server.getName(), cachedState); |
| } |
| |
| protected String msgWontFixBeforeSend(IProblemState cachedState) { |
| if (hasLink(cachedState, REL_BUG)) { |
| return Formats.format(Messages.PROBLEM_MESSAGES_WONTFIX_BUG_BEFORE_SEND, server.getName(), cachedState); |
| } |
| return Formats.format(Messages.PROBLEM_MESSAGES_WONTFIX_NO_BUG_BEFORE_SEND, server.getName(), cachedState); |
| } |
| |
| protected String msgNeedinfoAfterSend(IProblemState response) { |
| if (hasLink(response, REL_BUG)) { |
| return Formats.format(Messages.PROBLEM_MESSAGES_NEEDINFO_BUG_AFTER_SEND, server.getName(), response); |
| } |
| return Formats.format(Messages.PROBLEM_MESSAGES_NEEDINFO_NO_BUG_AFTER_SEND, server.getName(), response); |
| } |
| |
| protected String msgFixedBeforeSend(IProblemState cachedState) { |
| if (hasLink(cachedState, REL_BUG)) { |
| return Formats.format(Messages.PROBLEM_MESSAGES_FIXED_BUG_BEFORE_SEND, server.getName(), cachedState); |
| } |
| return format(Messages.PROBLEM_MESSAGES_FIXED_NO_BUG_BEFORE_SEND, server.getName(), cachedState); |
| |
| } |
| |
| protected String msgFixedAfterSend(IProblemState response) { |
| if (hasLink(response, REL_BUG)) { |
| return format(Messages.PROBLEM_MESSAGES_FIXED_BUG_AFTER_SEND, server.getName(), response); |
| } |
| return format(Messages.PROBLEM_MESSAGES_FIXED_NO_BUG_AFTER_SEND, server.getName(), response); |
| } |
| |
| @PreDestroy |
| private void diStop() throws TimeoutException { |
| stopAsync().awaitTerminated(2, SECONDS); |
| } |
| |
| @Override |
| protected void shutDown() throws Exception { |
| for (IProblemsHistory remoteHistory : remoteHistories) { |
| remoteHistory.close(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return server.getId() + " " + super.toString(); //$NON-NLS-1$ |
| } |
| } |