blob: 262a8b56e20b1c10476a51b62b5b3c9a2cd034e2 [file] [log] [blame]
/**
* 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$
}
}