Use REST API to query server's "interest" in an error report

If the server announces a query URL in its discovery document, this
enables a REST-based remote history which takes precendence over the
problems.zip-based history.

To reduce network traffic, it caches HTTP requests in a Lucene index.

Bug: 508320
Change-Id: I98fd4e59f01820af6f7e27c44a487c735e55bae3
Signed-off-by: Andreas Sewe <andreas.sewe@codetrails.com>
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/META-INF/MANIFEST.MF b/bundles/org.eclipse.epp.logging.aeri.ide/META-INF/MANIFEST.MF
index f22469d..26adaba 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/META-INF/MANIFEST.MF
@@ -45,10 +45,15 @@
  org.apache.http;version="[4.3.0,5.0.0)",
  org.apache.http.auth;version="[4.3.0,5.0.0)",
  org.apache.http.client;version="[4.3.0,5.0.0)",
+ org.apache.http.client.cache;version="[4.3.0,5.0.0)",
  org.apache.http.client.entity;version="[4.3.0,5.0.0)",
  org.apache.http.client.fluent;version="[4.3.0,5.0.0)",
+ org.apache.http.client.utils;version="[4.3.0,5.0.0)",
  org.apache.http.conn;version="[4.3.0,5.0.0)",
  org.apache.http.entity;version="[4.3.0,5.0.0)",
+ org.apache.http.impl.client;version="[4.3.0,5.0.0)",
+ org.apache.http.impl.client.cache;version="[4.3.0,5.0.0)",
+ org.apache.http.message;version="[4.3.0,5.0.0)",
  org.apache.http.util;version="[4.3.0,5.0.0)"
 Export-Package: org.eclipse.epp.internal.logging.aeri.ide;x-internal:=true,
  org.eclipse.epp.internal.logging.aeri.ide.server;x-internal:=true,
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/LogMessages.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/LogMessages.java
index 586c3d9..005e0dc 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/LogMessages.java
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/LogMessages.java
@@ -7,9 +7,7 @@
  */
 package org.eclipse.epp.internal.logging.aeri.ide.l10n;
 
-import static org.eclipse.core.runtime.IStatus.ERROR;
-import static org.eclipse.core.runtime.IStatus.INFO;
-import static org.eclipse.core.runtime.IStatus.WARNING;
+import static org.eclipse.core.runtime.IStatus.*;
 
 import org.eclipse.epp.logging.aeri.core.util.Logs.DefaultLogMessage;
 import org.osgi.framework.Bundle;
@@ -35,6 +33,8 @@
     public static final LogMessages ERROR_SAVE_PREFERENCES_FAILED = new LogMessages(ERROR, Messages.LOG_ERROR_SAVE_PREFERENCES_FAILED);
 
     public static final LogMessages INFO_SERVER_NOT_AVAILABLE = new LogMessages(INFO, Messages.LOG_INFO_SERVER_NOT_AVAILABLE);
+    public static final LogMessages INFO_TEMPORARILY_DISABLED_REST_QUERIES = new LogMessages(INFO,
+            Messages.LOG_INFO_TEMPORARILY_DISABLED_REST_QUERIES);
 
     public static final LogMessages WARN_HISTORY_NOT_AVAILABLE = new LogMessages(WARNING, Messages.LOG_WARN_HISTORY_NOT_AVAILABLE);
     public static final LogMessages WARN_HISTORY_STOP_FAILED = new LogMessages(WARNING, Messages.LOG_WARN_HISTORY_STOP_FAILED);
@@ -51,6 +51,8 @@
             Messages.LOG_WARN_RESPONSE_UPLOAD_REPORT_FAILED);
     public static final LogMessages WARN_UNEXPECTED_SERVER_RESPONSE = new LogMessages(WARNING, Messages.LOG_WARN_UNEXECTED_SERVER_RESPONSE);
     public static final LogMessages WARN_REPORT_PROCESSOR_FAILED = new LogMessages(WARNING, Messages.LOG_WARN_REPORT_PROCESSOR_FAILED);
+    public static final LogMessages WARN_REST_QUERY_FAILED = new LogMessages(WARNING,
+            Messages.LOG_WARN_REST_QUERY_FAILED);
 
     public LogMessages(int severity, String message) {
         super(severity, code++, String.format("%s %s", message, VERSION)); //$NON-NLS-1$
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/Messages.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/Messages.java
index f3ef37d..3254539 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/Messages.java
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/Messages.java
@@ -111,6 +111,7 @@
     public static String LOG_HISTORY_ABBREVIATION;
 
     public static String LOG_INFO_SERVER_NOT_AVAILABLE;
+    public static String LOG_INFO_TEMPORARILY_DISABLED_REST_QUERIES;
 
     public static String LOG_WARN_EXTENSION_FAILED;
     public static String LOG_WARN_FORMATTING_FAILED;
@@ -123,6 +124,7 @@
     public static String LOG_WARN_REPORT_PROCESSOR_FAILED;
     public static String LOG_WARN_REPORTING_ERROR;
     public static String LOG_WARN_RESPONSE_UPLOAD_REPORT_FAILED;
+    public static String LOG_WARN_REST_QUERY_FAILED;
     public static String LOG_WARN_SERVER_FAILURE;
     public static String LOG_WARN_UNEXECTED_SERVER_RESPONSE;
     public static String LOG_WARN_URL_VALIDATION_FAILED;
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/messages.properties b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/messages.properties
index 01a95d9..4294684 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/messages.properties
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/l10n/messages.properties
@@ -101,6 +101,7 @@
 LOG_ERROR_SAVE_PREFERENCES_FAILED=Saving preferences failed: {0}={1}
 
 LOG_INFO_SERVER_NOT_AVAILABLE=The Error Reporting server is not available. Error Reporting will be disabled until the next restart.
+LOG_INFO_TEMPORARILY_DISABLED_REST_QUERIES=Temporarily disabled REST queries for server \u2018{0}\u2019.
 
 LOG_WARN_EXTENSION_FAILED=Extension \u2018{0}\u2019 failed: {1}.
 LOG_WARN_FORMATTING_FAILED=Formatting \u2018{0}\u2019 with arguments \u2018{1}\u2019 failed: {2}.
@@ -113,6 +114,7 @@
 LOG_WARN_REPORT_PROCESSOR_FAILED=Report processor \u2018{0}\u2019 failed with exception
 LOG_WARN_REPORTING_ERROR=Unexpected Error occurred while processing a log event. Please open a bug at https://bugs.eclipse.org/bugs/enter_bug.cgi?product=EPP&component=logging
 LOG_WARN_RESPONSE_UPLOAD_REPORT_FAILED=Sending report to \u2018{0}\u2019 failed: {1}
+LOG_WARN_REST_QUERY_FAILED=REST query for server \u2018{0}\u2019 failed: \u2018{1}\u2019
 LOG_WARN_SERVER_FAILURE=Server \u2018{0}\u2019 failed with exception: {1}.
 LOG_WARN_UNEXECTED_SERVER_RESPONSE=Unexpected response : {0} \u2010 {1}
 LOG_WARN_URL_VALIDATION_FAILED=Cannot open browser for URL \u2018{0}\u2019: {1}
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/IO.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/IO.java
index a17fb84..5a25f4d 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/IO.java
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/IO.java
@@ -101,21 +101,24 @@
                 .socketTimeout(configuration.getSocketTimeoutMs());
         String response = proxyAuthentication(executor, target).execute(request).returnContent().asString();
         ServerResponse raw = Json.deserialize(response, ServerResponse.class);
+        return extractProblemState(raw);
+    }
 
+    public static IProblemState extractProblemState(ServerResponse serverResponse) {
         IProblemState problemState = IModelFactory.eINSTANCE.createProblemState();
 
         // this looks a bit weird: it's not a bug id but the public id of the submission...
-        String submissionUrl = raw.getSubmissionUrl().orNull();
+        String submissionUrl = serverResponse.getSubmissionUrl().orNull();
         if (submissionUrl != null) {
             Links.addLink(problemState, Links.REL_SUBMISSION, submissionUrl, Messages.LINK_TEXT_SUBMISSION);
         }
-        if (raw.hasBug()) {
-            Links.addLink(problemState, REL_BUG, raw.getBugUrl().orNull(),
-                    Formats.format(Messages.LINK_TEXT_BUG, raw.getBugId().or(Messages.LINK_TEXT_BUG_ID_NULL)));
+        if (serverResponse.hasBug()) {
+            Links.addLink(problemState, REL_BUG, serverResponse.getBugUrl().orNull(),
+                    Formats.format(Messages.LINK_TEXT_BUG, serverResponse.getBugId().or(Messages.LINK_TEXT_BUG_ID_NULL)));
         }
 
-        problemState.setStatus(tryParse(raw));
-        String message = raw.getInformation().orNull();
+        problemState.setStatus(tryParse(serverResponse));
+        String message = serverResponse.getInformation().orNull();
         if (message != null && !StringUtils.contains(message, "</a>") && !StringUtils.contains(message, "{link")) { //$NON-NLS-1$
             // TODO temporary for Mars.1 servers in Neon:
             // Server sends an 'additional' status message which may not contain any links. Let's append them in a generic way for Neon.M4
@@ -123,7 +126,7 @@
         }
         problemState.setMessage(message);
 
-        String[] keywords = raw.getKeywords().orNull();
+        String[] keywords = serverResponse.getKeywords().orNull();
         if (keywords != null) {
             for (String keyword : keywords) {
                 problemState.getNeedinfo().add(keyword);
@@ -134,7 +137,7 @@
     }
 
     // returns a string with all links. Separated by ' ' and with a leading ' ' if the list of links is not empty.
-    private String appendLinks(EMap<String, ILink> links) {
+    private static String appendLinks(EMap<String, ILink> links) {
         if (links.isEmpty()) {
             return ""; //$NON-NLS-1$
         }
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/IProblemsHistory.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/IProblemsHistory.java
new file mode 100644
index 0000000..15500a7
--- /dev/null
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/IProblemsHistory.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2016 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 java.io.Closeable;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.epp.logging.aeri.core.IProblemState;
+import org.eclipse.epp.logging.aeri.core.ISystemSettings;
+
+import com.google.common.base.Optional;
+
+public interface IProblemsHistory extends Closeable {
+
+    Optional<IProblemState> seen(IStatus status);
+
+    void sync(IO io, ISystemSettings systemSettings);
+}
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerProblemsHistory.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ProblemsDatabaseProblemsHistory.java
similarity index 86%
rename from bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerProblemsHistory.java
rename to bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ProblemsDatabaseProblemsHistory.java
index 94c21d0..8e840b6 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerProblemsHistory.java
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ProblemsDatabaseProblemsHistory.java
@@ -64,14 +64,14 @@
 import org.eclipse.epp.logging.aeri.core.util.Statuses;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicate;
+import com.google.common.base.Optional;
 import com.google.common.io.Files;
 import com.google.common.util.concurrent.AbstractIdleService;
 
 /**
  * A history of remotely known problems that are worth knowing on the client side.
  */
-public class ServerProblemsHistory extends AbstractIdleService {
+public class ProblemsDatabaseProblemsHistory extends AbstractIdleService implements IProblemsHistory {
 
     // values for problem databases
     public static final String F_VERSION = "version"; //$NON-NLS-1$
@@ -85,11 +85,13 @@
     public static final String F_FINGERPRINT = "fingerprint"; //$NON-NLS-1$
     public static final String F_NEEDINFOS = "needinfos"; //$NON-NLS-1$
 
-    private File stateLocation;
+    private final File stateLocation;
+
+    private UpdateIndexJob updateIndexJob;
     private Directory index;
     private SearcherManager manager;
 
-    public ServerProblemsHistory(File stateLocation) {
+    public ProblemsDatabaseProblemsHistory(File stateLocation) {
         this.stateLocation = stateLocation;
     }
 
@@ -108,7 +110,7 @@
     }
 
     @VisibleForTesting
-    protected Directory createIndexDirectory() throws IOException {
+    Directory createIndexDirectory() throws IOException {
         stateLocation.mkdirs();
         FSDirectory directory = FSDirectory.open(stateLocation);
 
@@ -126,14 +128,15 @@
         }
     }
 
-    public IProblemState seen(IStatus status) {
+    @Override
+    public Optional<IProblemState> seen(IStatus status) {
         checkNotNull(status);
         checkState(isRunning());
         String fingerprint = Statuses.traceIdentityHash(status);
         return seen(new TermQuery(new Term(F_FINGERPRINT, fingerprint)));
     }
 
-    private IProblemState seen(Query q) {
+    private Optional<IProblemState> seen(Query q) {
         IndexSearcher searcher = manager.acquire();
         try {
             TopDocs results = searcher.search(q, 1);
@@ -142,7 +145,7 @@
                 int doc = results.scoreDocs[0].doc;
                 Document d = searcher.doc(doc);
                 IProblemState status = loadStatus(d);
-                return status;
+                return Optional.of(status);
             }
         } catch (Exception e) {
             log(WARN_INDEX_NOT_AVAILABLE, e);
@@ -153,9 +156,7 @@
                 log(WARN_INDEX_NOT_AVAILABLE, e);
             }
         }
-        IProblemState state = IModelFactory.eINSTANCE.createProblemState();
-        state.setStatus(ProblemStatus.UNCONFIRMED);
-        return state;
+        return Optional.absent();
     }
 
     private IProblemState loadStatus(Document d) {
@@ -227,47 +228,13 @@
         manager.close();
     }
 
-    public static class RemoteProblemsHistoryFilter implements Predicate<IStatus> {
-
-        private ServerProblemsHistory index;
-
-        public RemoteProblemsHistoryFilter(ServerProblemsHistory index) {
-            this.index = index;
-        }
-
-        @Override
-        public boolean apply(IStatus input) {
-            if (!index.isRunning()) {
-                // if the database is not (yet) set up, let everything pass
-                return true;
-            }
-
-            IProblemState status = index.seen(input);
-            switch (status.getStatus()) {
-            case IGNORED:
-            case INVALID:
-            case FAILURE:
-                return false;
-            case NEW:
-            case UNCONFIRMED:
-            case CONFIRMED:
-            case FIXED:
-            case NEEDINFO:
-                return true;
-            default:
-                Logs.log(LogMessages.DEBUG_UNKNOWN_SERVER_STATUS, status.getStatus());
-                return true;
-            }
-        }
-    }
-
     public static class UpdateIndexJob extends Job {
 
         private IO io;
         private ISystemSettings settings;
-        private ServerProblemsHistory history;
+        private ProblemsDatabaseProblemsHistory history;
 
-        public UpdateIndexJob(IO io, ISystemSettings settings, ServerProblemsHistory history) {
+        public UpdateIndexJob(IO io, ISystemSettings settings, ProblemsDatabaseProblemsHistory history) {
 
             super(Formats.format(Messages.JOB_NAME_UPDATE_INDEX, io.getConfiguration().getProblemsUrl()));
             this.io = io;
@@ -327,4 +294,29 @@
         }
 
     }
+
+    @Override
+    public void sync(IO io, ISystemSettings systemSettings) {
+        if (shouldUpdateProblemDatabase(io, systemSettings)) {
+            updateProblemDatabase(io, systemSettings);
+        }
+    }
+
+    private boolean shouldUpdateProblemDatabase(IO io, ISystemSettings systemSettings) {
+        return !isUpdating() && io.isProblemsDatabaseOutdated();
+    }
+
+    private boolean isUpdating() {
+        return updateIndexJob != null && updateIndexJob.getResult() == null;
+    }
+
+    @VisibleForTesting
+    protected void updateProblemDatabase(IO io, ISystemSettings systemSettings) {
+        updateIndexJob = new UpdateIndexJob(io, systemSettings, this);
+        updateIndexJob.schedule();
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
 }
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConfiguration.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConfiguration.java
index 3d3c66c..7c204ee 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConfiguration.java
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConfiguration.java
@@ -37,7 +37,7 @@
 
     // in minutes
     private long problemsTtl;
-    private String queryUrl;
+    private String interestUrl;
 
     // max time in seconds until a connection to the server has to be established.
     private int connectTimeout = (int) TimeUnit.SECONDS.toMillis(3);;
@@ -213,12 +213,12 @@
         this.problemsTtl = problemsTtlInMinutes;
     }
 
-    public String getQueryUrl() {
-        return queryUrl;
+    public String getInterestUrl() {
+        return interestUrl;
     }
 
-    public void setQueryUrl(String queryUrl) {
-        this.queryUrl = queryUrl;
+    public void setInterestUrl(String interestUrl) {
+        this.interestUrl = interestUrl;
     }
 
     public List<String> getAcceptedProducts() {
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnection.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnection.java
index d8684c1..3495c72 100644
--- a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnection.java
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnection.java
@@ -19,6 +19,8 @@
 
 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;
@@ -39,7 +41,7 @@
 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.mars.ServerProblemsHistory.UpdateIndexJob;
+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;
@@ -79,8 +81,11 @@
         public void notifyChanged(Notification msg) {
             int featureID = msg.getFeatureID(featureClass);
             if (msg.getEventType() == Notification.SET && ArrayUtils.contains(observedFeatureIds, featureID)) {
-                if (shouldUpdateProblemDatabase(io, systemSettings, remoteHistory)) {
-                    updateProblemDatabase(io, systemSettings, remoteHistory);
+                if (!shouldUse()) {
+                    return;
+                }
+                for (IProblemsHistory remoteHistory : remoteHistories) {
+                    remoteHistory.sync(io, systemSettings);
                 }
             }
         }
@@ -91,9 +96,8 @@
     private final File configurationArea;
     private Predicate<IStatus> statusFilters = alwaysFalse();
     private IO io;
-    private ServerProblemsHistory remoteHistory;
     private LocalReportsHistory localHistory;
-    private UpdateIndexJob updateIndexJob;
+    private final List<IProblemsHistory> remoteHistories = new ArrayList<>();
 
     @Inject
     public ServerConnection(IServerDescriptor descriptor, ISystemSettings system, File configurationArea) {
@@ -111,17 +115,6 @@
     protected void startUp() throws Exception {
         try {
             {
-                File remoteHistoryStateLocation = new File(configurationArea, "remote-history"); //$NON-NLS-1$
-                remoteHistory = new ServerProblemsHistory(remoteHistoryStateLocation);
-                remoteHistory.startAsync();
-            }
-            {
-                File localHistoryStateLocation = new File(configurationArea, "local-history"); //$NON-NLS-1$
-                localHistoryStateLocation.mkdirs();
-                localHistory = new LocalReportsHistory(localHistoryStateLocation);
-                localHistory.startAsync();
-            }
-            {
                 File file = new File(configurationArea, "server-config.json"); //$NON-NLS-1$
                 io = createIO(file);
                 if (file.exists()) {
@@ -131,16 +124,37 @@
                     io.refreshConfiguration(checkNotNull(getLink(server, REL_DISCOVERY)).getHref(), new NullProgressMonitor());
                     io.saveConfiguration();
                 }
-                if (shouldUpdateProblemDatabase(io, systemSettings, remoteHistory)) {
-                    updateProblemDatabase(io, systemSettings, remoteHistory);
-                }
             }
+
             {
+                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();
@@ -168,13 +182,26 @@
         }
     }
 
-    private boolean shouldUpdateProblemDatabase(IO io, ISystemSettings systemSettings, ServerProblemsHistory remoteHistory) {
-        return !isUpdating() && server.isConfigured() && server.isEnabled() && systemSettings.isConfigured()
-                && systemSettings.getSendMode() != SendMode.NEVER && io.isProblemsDatabaseOutdated();
+    private IProblemsHistory createRestBasedProblemsHistory(File cacheDir) throws IOException {
+        return new RestBasedProblemsHistory(io.getConfiguration(), cacheDir);
     }
 
-    private boolean isUpdating() {
-        return updateIndexJob != null && updateIndexJob.getResult() == null;
+    @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
@@ -182,48 +209,66 @@
         return new IO(Executor.newInstance(), file);
     }
 
-    @VisibleForTesting
-    protected void updateProblemDatabase(IO io, ISystemSettings systemSettings, ServerProblemsHistory remoteHistory) {
-        updateIndexJob = new UpdateIndexJob(io, systemSettings, remoteHistory);
-        updateIndexJob.schedule();
-    }
-
     @Override
     public IProblemState interested(IStatus status, IEclipseContext context, IProgressMonitor monitor) {
-        if (!isRunning() || !statusFilters.apply(status)) {
+        if (!shouldUse()) {
+            // FIXME
+            // This prevents requests 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 if (!isRunning() || !statusFilters.apply(status)) {
             IProblemState res = IModelFactory.eINSTANCE.createProblemState();
             res.setStatus(ProblemStatus.IGNORED);
             return res;
         } else {
-            IProblemState seen = remoteHistory.seen(status);
-            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;
+            IProblemState seen = null;
+            for (IProblemsHistory remoteHistory : remoteHistories) {
+                seen = remoteHistory.seen(status).orNull();
+                if (seen == null) {
+                    continue;
+                }
 
-            case FIXED:
-                message = msgFixedBeforeSend(seen);
+                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;
-            case UNCONFIRMED:
-                message = msgUnconfirmedBeforeSend(seen);
-                break;
-            default:
-                message = Formats.format("Unexpected state {0}", seen.getStatus()); //$NON-NLS-1$
             }
-            seen.setMessage(message);
+            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
@@ -301,8 +346,8 @@
         return format(Messages.PROBLEM_MESSAGES_NEW_AFTER_SEND, server.getName(), response);
     }
 
-    protected String msgUnconfirmedBeforeSend(IProblemState cachedState) {
-        return format(Messages.PROBLEM_MESSAGES_UNCONFIRMED_BEFORE_SEND, server.getName(), cachedState);
+    protected String msgUnconfirmedBeforeSend() {
+        return format(Messages.PROBLEM_MESSAGES_UNCONFIRMED_BEFORE_SEND, server.getName());
     }
 
     protected String msgUnconfirmedAfterSend(IProblemState response) {
@@ -359,11 +404,13 @@
 
     @Override
     protected void shutDown() throws Exception {
+        for (IProblemsHistory remoteHistory : remoteHistories) {
+            remoteHistory.close();
+        }
     }
 
     @Override
     public String toString() {
         return server.getId() + " " + super.toString(); //$NON-NLS-1$
     }
-
 }
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/LuceneHttpCacheStorage.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/LuceneHttpCacheStorage.java
new file mode 100644
index 0000000..cfa53fe
--- /dev/null
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/LuceneHttpCacheStorage.java
@@ -0,0 +1,256 @@
+/**
+ * Copyright (c) 2016 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.rest;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.Header;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.client.cache.HttpCacheUpdateCallback;
+import org.apache.http.client.cache.HttpCacheUpdateException;
+import org.apache.http.client.cache.Resource;
+import org.apache.http.impl.client.cache.HeapResource;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicStatusLine;
+import org.apache.lucene.analysis.KeywordAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Field.Index;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.Fieldable;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.SearcherManager;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.Version;
+
+class LuceneHttpCacheStorage implements HttpCacheStorage, Closeable {
+
+    private static final String KEY_FIELD_NAME = "key";
+    private static final String REQUEST_DATE_FIELD_NAME = "requestDate";
+    private static final String RESPONSE_DATE_FIELD_NAME = "responseDate";
+    private static final String STATUS_CODE_FIELD_NAME = "statusLine/statusCode";
+    private static final String REASON_PHRASE_FIELD_NAME = "statusLine/reasonPhrase";
+    private static final String PROTOCOL_FIELD_NAME = "statusLine/protocolVersion/protocol";
+    private static final String MINOR_PROTOCOL_VERSION_FIELD_NAME = "statusLine/protocolVersion/minor";
+    private static final String MAJOR_PROTOCAL_VERSION_FIELD_NAME = "statusLine/protocolVersion/major";
+    private static final String HEADER_FIELD_NAMES = "header";
+    private static final int HEADER_FIELD_NAMES_LENGTH = HEADER_FIELD_NAMES.length();
+    private static final String BODY_FIELD_NAME = "body";
+    private static final String VARIANT_FIELD_NAMES = "variant";
+    private static final int VARIANT_FIELD_NAMES_LENGTH = VARIANT_FIELD_NAMES.length();
+
+    private final IndexWriter writer;
+    private final SearcherManager searcherManager;
+
+    LuceneHttpCacheStorage(Directory directory) throws IOException {
+        IndexWriterConfig writerConfig = new IndexWriterConfig(Version.LUCENE_35, new KeywordAnalyzer());
+        writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
+        writer = new IndexWriter(directory, writerConfig);
+        searcherManager = new SearcherManager(writer, true, null, null);
+    }
+
+    @Override
+    public HttpCacheEntry getEntry(String key) throws IOException {
+        searcherManager.maybeReopen();
+        IndexSearcher searcher = searcherManager.acquire();
+        try {
+            Query query = new TermQuery(new Term(KEY_FIELD_NAME, key));
+            TopDocs topDocs = searcher.search(query, 1);
+            if (topDocs.totalHits > 1) {
+                throw new IOException("Corrupt index (cache key is not unique)");
+            }
+
+            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+            if (scoreDocs.length > 0) {
+                ScoreDoc scoreDoc = scoreDocs[0];
+                Document document = searcher.doc(scoreDoc.doc);
+                return fromLuceneFields(document.getFields());
+            } else {
+                return null;
+            }
+        } finally {
+            searcherManager.release(searcher);
+        }
+    }
+
+    @Override
+    public void putEntry(String key, HttpCacheEntry entry) throws IOException {
+        Document document = toLuceneDocument(key, entry);
+        synchronized (this) {
+            writer.updateDocument(new Term(KEY_FIELD_NAME, key), document);
+        }
+    }
+
+    @Override
+    public void updateEntry(String key, HttpCacheUpdateCallback callback) throws IOException, HttpCacheUpdateException {
+        synchronized (this) {
+            HttpCacheEntry existingEntry = getEntry(key);
+            HttpCacheEntry newEntry = callback.update(existingEntry);
+            Document newDocument = toLuceneDocument(key, newEntry);
+            writer.updateDocument(new Term(KEY_FIELD_NAME, key), newDocument);
+        }
+    }
+
+    @Override
+    public void removeEntry(String key) throws IOException {
+        Query query = new TermQuery(new Term(KEY_FIELD_NAME, key));
+        synchronized (this) {
+            writer.deleteDocuments(query);
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        searcherManager.close();
+        writer.close();
+    }
+
+    private Document toLuceneDocument(String key, HttpCacheEntry entry) throws IOException {
+        Document document = new Document();
+        document.add(new Field(KEY_FIELD_NAME, key, Store.NO, Index.NOT_ANALYZED_NO_NORMS));
+        for (Fieldable field : toLuceneFields(entry)) {
+            document.add(field);
+        }
+        return document;
+    }
+
+    private List<Fieldable> toLuceneFields(HttpCacheEntry entry) throws IOException {
+        List<Fieldable> fields = new ArrayList<>();
+
+        fields.add(new Field(REQUEST_DATE_FIELD_NAME, Long.toString(entry.getRequestDate().getTime()), Store.YES, Index.NO));
+
+        fields.add(new Field(RESPONSE_DATE_FIELD_NAME, Long.toString(entry.getResponseDate().getTime()), Store.YES, Index.NO));
+
+        StatusLine statusLine = entry.getStatusLine();
+        fields.add(new Field(STATUS_CODE_FIELD_NAME, Integer.toString(statusLine.getStatusCode()), Store.YES, Index.NO));
+        fields.add(new Field(REASON_PHRASE_FIELD_NAME, statusLine.getReasonPhrase(), Store.YES, Index.NO));
+
+        ProtocolVersion protocolVersion = statusLine.getProtocolVersion();
+        fields.add(new Field(PROTOCOL_FIELD_NAME, protocolVersion.getProtocol(), Store.YES, Index.NO));
+        fields.add(new Field(MAJOR_PROTOCAL_VERSION_FIELD_NAME, Integer.toString(protocolVersion.getMajor()), Store.YES, Index.NO));
+        fields.add(new Field(MINOR_PROTOCOL_VERSION_FIELD_NAME, Integer.toString(protocolVersion.getMinor()), Store.YES, Index.NO));
+
+        Header[] headers = entry.getAllHeaders();
+        for (int index = 0; index < headers.length; index++) {
+            Header header = headers[index];
+            fields.add(new Field(HEADER_FIELD_NAMES + '/' + index + '/' + header.getName(), header.getValue(), Store.YES, Index.NO));
+        }
+
+        Resource body = entry.getResource();
+        if (body != null) {
+            fields.add(new Field(BODY_FIELD_NAME, IOUtils.toByteArray(body.getInputStream())));
+        }
+
+        if (entry.hasVariants()) {
+            for (Entry<String, String> variant : entry.getVariantMap().entrySet()) {
+                fields.add(new Field(VARIANT_FIELD_NAMES + '/' + variant.getKey(), variant.getValue(), Store.YES, Index.NO));
+            }
+        }
+
+        return fields;
+    }
+
+    private HttpCacheEntry fromLuceneFields(List<Fieldable> fields) throws IOException {
+        Date requestDate = null;
+        Date responseDate = null;
+        int statusCode = Integer.MIN_VALUE;
+        String reasonPhrase = null;
+        String protocol = null;
+        int majorProtocolVersion = Integer.MIN_VALUE;
+        int minorProtocolVersion = Integer.MIN_VALUE;
+        List<Header> responseHeaders = new ArrayList<>();
+        Resource body = null;
+        Map<String, String> variantMap = new HashMap<>();
+
+        for (Fieldable field : fields) {
+            String fieldName = field.name();
+            if (REQUEST_DATE_FIELD_NAME.equals(fieldName)) {
+                requestDate = parseDateField(field);
+            } else if (RESPONSE_DATE_FIELD_NAME.equals(fieldName)) {
+                responseDate = parseDateField(field);
+            } else if (STATUS_CODE_FIELD_NAME.equals(fieldName)) {
+                statusCode = parseIntField(field);
+            } else if (REASON_PHRASE_FIELD_NAME.equals(fieldName)) {
+                reasonPhrase = field.stringValue();
+            } else if (PROTOCOL_FIELD_NAME.equals(fieldName)) {
+                protocol = field.stringValue();
+            } else if (MAJOR_PROTOCAL_VERSION_FIELD_NAME.equals(fieldName)) {
+                majorProtocolVersion = parseIntField(field);
+            } else if (MINOR_PROTOCOL_VERSION_FIELD_NAME.equals(fieldName)) {
+                minorProtocolVersion = parseIntField(field);
+            } else if (fieldName.startsWith(HEADER_FIELD_NAMES)) {
+                try {
+                    int secondSlash = fieldName.indexOf('/', HEADER_FIELD_NAMES_LENGTH + 1);
+                    String indexString = fieldName.substring(HEADER_FIELD_NAMES_LENGTH + 1, secondSlash);
+                    int index = Integer.parseInt(indexString);
+                    String headerName = fieldName.substring(secondSlash + 1);
+                    String headerValue = field.stringValue();
+                    Header header = new BasicHeader(headerName, headerValue);
+                    responseHeaders.add(index, header);
+                } catch (NumberFormatException e) {
+                    throw new IOException(e);
+                }
+            } else if (BODY_FIELD_NAME.equals(fieldName)) {
+                body = new HeapResource(field.getBinaryValue());
+            } else if (fieldName.startsWith(VARIANT_FIELD_NAMES)) {
+                String variantKey = fieldName.substring(VARIANT_FIELD_NAMES_LENGTH + 1);
+                String cacheKey = field.stringValue();
+                variantMap.put(variantKey, cacheKey);
+            } else {
+                throw new IOException("Corrupt index (unknown field: " + fieldName + ")");
+            }
+        }
+
+        try {
+            ProtocolVersion protocolVersion = new ProtocolVersion(protocol, majorProtocolVersion, minorProtocolVersion);
+            StatusLine statusLine = new BasicStatusLine(protocolVersion, statusCode, reasonPhrase);
+            return new HttpCacheEntry(requestDate, responseDate, statusLine, responseHeaders.toArray(new Header[responseHeaders.size()]),
+                    body, variantMap);
+        } catch (IllegalArgumentException e) {
+            throw new IOException("Corrupt index", e);
+        }
+    }
+
+    private Date parseDateField(Fieldable field) throws IOException {
+        try {
+            String stringValue = field.stringValue();
+            long longValue = Long.parseLong(stringValue);
+            return new Date(longValue);
+        } catch (NumberFormatException e) {
+            throw new IOException(field.name(), e);
+        }
+    }
+
+    private int parseIntField(Fieldable field) throws IOException {
+        try {
+            String stringValue = field.stringValue();
+            return Integer.parseInt(stringValue);
+        } catch (NumberFormatException e) {
+            throw new IOException(field.name(), e);
+        }
+    }
+}
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/RestBasedProblemsHistory.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/RestBasedProblemsHistory.java
new file mode 100644
index 0000000..7c3cc07
--- /dev/null
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/RestBasedProblemsHistory.java
@@ -0,0 +1,239 @@
+/**
+ * Copyright (c) 2016 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.rest;
+
+import static org.apache.http.HttpStatus.SC_NOT_FOUND;
+import static org.eclipse.epp.internal.logging.aeri.ide.l10n.LogMessages.WARN_REST_QUERY_FAILED;
+import static org.eclipse.epp.internal.logging.aeri.ide.server.Proxies.*;
+import static org.eclipse.epp.logging.aeri.core.util.Logs.log;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.client.fluent.Executor;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.client.utils.DateUtils;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.cache.CachingHttpClientBuilder;
+import org.apache.http.impl.client.cache.CachingHttpClients;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.epp.internal.logging.aeri.ide.l10n.LogMessages;
+import org.eclipse.epp.internal.logging.aeri.ide.server.json.Json;
+import org.eclipse.epp.internal.logging.aeri.ide.server.mars.IO;
+import org.eclipse.epp.internal.logging.aeri.ide.server.mars.IProblemsHistory;
+import org.eclipse.epp.internal.logging.aeri.ide.server.mars.ServerConfiguration;
+import org.eclipse.epp.logging.aeri.core.ILink;
+import org.eclipse.epp.logging.aeri.core.IModelFactory;
+import org.eclipse.epp.logging.aeri.core.IProblemState;
+import org.eclipse.epp.logging.aeri.core.ISystemSettings;
+import org.eclipse.epp.logging.aeri.core.ProblemStatus;
+import org.eclipse.epp.logging.aeri.core.util.Statuses;
+
+import com.google.common.base.Optional;
+import com.google.gson.reflect.TypeToken;
+
+public class RestBasedProblemsHistory implements IProblemsHistory {
+
+    private static final ContentType STATUS_REPONSES_CONTENT_TYPE = ContentType.create("application/x.aer.status-reponses+json");
+    private static final String STACK_TRACE_FINGERPRINT__QUERY = "stackTraceFingerprint";
+
+    private final ServerConfiguration config;
+    private final URI baseURI;
+    private final LuceneHttpCacheStorage storage;
+    private final CloseableHttpClient client;
+    private final Executor executor;
+
+    private Date embargoDate = new Date();
+
+    public RestBasedProblemsHistory(ServerConfiguration config, File cacheDir) throws IOException {
+        this.config = config;
+        try {
+            this.baseURI = new URI(config.getInterestUrl());
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        Directory directory = FSDirectory.open(cacheDir);
+        storage = new LuceneHttpCacheStorage(directory);
+        client = createClient(storage);
+        executor = Executor.newInstance(client);
+    }
+
+    @Override
+    public Optional<IProblemState> seen(IStatus status) {
+        Date now = new Date();
+        if (now.before(embargoDate)) {
+            log(LogMessages.INFO_TEMPORARILY_DISABLED_REST_QUERIES, config.getTitle());
+            return Optional.absent();
+        }
+
+        String fingerprint = Statuses.traceIdentityHash(status);
+        URI restURI;
+        try {
+            restURI = new URIBuilder(baseURI).addParameter(STACK_TRACE_FINGERPRINT__QUERY, fingerprint).build();
+        } catch (URISyntaxException e1) {
+            return Optional.absent();
+        }
+
+        try {
+            Request request = Request.Get(restURI).addHeader(HttpHeaders.ACCEPT, STATUS_REPONSES_CONTENT_TYPE.toString())
+                    .viaProxy(getProxyHost(restURI).orNull()).connectTimeout(config.getConnectTimeoutMs()).staleConnectionCheck(true)
+                    .socketTimeout(config.getSocketTimeoutMs());
+            StatusReponse statusReponse = proxyAuthentication(executor, restURI).execute(request).handleResponse(new RestReponseHandler());
+
+            if (statusReponse != null) {
+                return Optional.of(toProblemState(statusReponse));
+            } else {
+                return Optional.absent();
+            }
+        } catch (IOException e) {
+            log(WARN_REST_QUERY_FAILED, e, config.getTitle(), restURI);
+            return Optional.absent();
+        }
+    }
+
+    @Override
+    public void sync(IO io, ISystemSettings systemSettings) {
+        // No-op
+    }
+
+    @Override
+    public void close() throws IOException {
+        client.close();
+        storage.close();
+    }
+
+    /**
+     * For legacy reasons, {@link IProblemState} is not a perfect match for {@link StatusReponse}, but this hopefully will change in the
+     * future.
+     */
+    private IProblemState toProblemState(StatusReponse serverResponse) {
+        IProblemState mProblemState = IModelFactory.eINSTANCE.createProblemState();
+        mProblemState.setStatus(toProblemStatus(serverResponse.getSituation()));
+        mProblemState.setMessage(serverResponse.getMessage());
+        for (String auxiliaryInformationRequest : serverResponse.getAuxiliaryInformationRequests()) {
+            mProblemState.getNeedinfo().add(auxiliaryInformationRequest);
+        }
+        for (Link link : serverResponse.getLinks()) {
+            ILink mLink = IModelFactory.eINSTANCE.createLink();
+            mLink.setHref(link.getHref().toString());
+            mLink.setRel(link.getRel());
+            mLink.setTitle(link.getTitle());
+            mProblemState.getLinks().put(link.getRel(), mLink);
+        }
+        return mProblemState;
+    }
+
+    private ProblemStatus toProblemStatus(ProblemSituation situation) {
+        switch (situation) {
+        case FAILURE:
+            return ProblemStatus.FAILURE;
+        case FIXED:
+            return ProblemStatus.FIXED;
+        case IGNORE:
+            return ProblemStatus.IGNORED;
+        case OPEN:
+            return ProblemStatus.CONFIRMED;
+        case WONTFIX:
+            return ProblemStatus.WONTFIX;
+        default:
+            throw new IllegalArgumentException(situation.toString());
+        }
+    }
+
+    private final class RestReponseHandler implements ResponseHandler<StatusReponse> {
+        @Override
+        public StatusReponse handleResponse(HttpResponse response) throws IOException {
+            StatusLine statusLine = response.getStatusLine();
+            HttpEntity entity = response.getEntity();
+            if (statusLine.getStatusCode() == SC_NOT_FOUND) {
+                return null;
+            } else if (statusLine.getStatusCode() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+                Header retryAfterHeader = response.getFirstHeader(HttpHeaders.RETRY_AFTER);
+                if (retryAfterHeader != null) {
+                    embargoDate = parseRetryAfter(retryAfterHeader.getValue());
+                }
+                throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
+            } else if (statusLine.getStatusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
+                throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
+            }
+
+            if (entity == null) {
+                throw new ClientProtocolException("Response contains no content");
+            }
+            ContentType contentType = ContentType.getOrDefault(entity);
+            // Ignore any parameters sent by the server, as application/json doesn't have any as per RFC 7159
+            if (!contentType.getMimeType().equals(STATUS_REPONSES_CONTENT_TYPE.getMimeType())) {
+                throw new ClientProtocolException("Unexpected content type: " + contentType);
+            }
+
+            try {
+                List<StatusReponse> statusResponses = Json.deserialize(entity.getContent(), new TypeToken<List<StatusReponse>>() {
+                }.getType());
+                if (statusResponses.isEmpty()) {
+                    throw new IOException("Expected at least one status response");
+                }
+                return statusResponses.get(0);
+            } catch (Exception e) {
+                throw new ClientProtocolException("Cannot parse content", e);
+            }
+        }
+    }
+
+    /**
+     * Workaround for <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=508270">Bug 508270</a>
+     */
+    private CloseableHttpClient createClient(HttpCacheStorage storage) {
+        CachingHttpClientBuilder builder = CachingHttpClients.custom().setHttpCacheStorage(storage);
+        try {
+            Method buildMethod = CachingHttpClientBuilder.class.getMethod("build");
+            return (CloseableHttpClient) buildMethod.invoke(builder);
+        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
+                | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Date parseRetryAfter(String retryAfter) {
+        if (retryAfter != null) {
+            try {
+                return new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Long.parseLong(retryAfter)));
+            } catch (NumberFormatException ignore) {
+                Date date = DateUtils.parseDate(retryAfter);
+                return date != null ? date : tomorrow();
+            }
+        } else {
+            return tomorrow();
+        }
+    }
+
+    private Date tomorrow() {
+        return new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1));
+    }
+}
diff --git a/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/StatusReponse.java b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/StatusReponse.java
new file mode 100644
index 0000000..5108d72
--- /dev/null
+++ b/bundles/org.eclipse.epp.logging.aeri.ide/src/org/eclipse/epp/internal/logging/aeri/ide/server/rest/StatusReponse.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2016 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.rest;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import com.google.gson.annotations.SerializedName;
+
+final class StatusReponse {
+
+    @SerializedName("situation")
+    private ProblemSituation situation;
+
+    /**
+     * Inline-level HTML
+     */
+    @SerializedName("message")
+    private String message;
+
+    @SerializedName("links")
+    private List<Link> links;
+
+    @SerializedName("auxiliaryInformationRequests")
+    private Set<String> auxiliaryInformationRequests;
+
+    public ProblemSituation getSituation() {
+        return situation;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public List<Link> getLinks() {
+        return links;
+    }
+
+    public Set<String> getAuxiliaryInformationRequests() {
+        return auxiliaryInformationRequests;
+    }
+}
+
+final class Link {
+
+    @SerializedName("href")
+    private URI href;
+
+    @SerializedName("rel")
+    private String rel;
+
+    @SerializedName("title")
+    private String title;
+
+    public URI getHref() {
+        return href;
+    }
+
+    public String getRel() {
+        return rel;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other == null) {
+            return false;
+        }
+        if (getClass() != other.getClass()) {
+            return false;
+        }
+        Link that = (Link) other;
+        return Objects.equals(this.href, that.href) && Objects.equals(this.rel, that.rel) && Objects.equals(this.title, that.title);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(href, rel, title);
+    }
+}
+
+enum ProblemSituation {
+
+    OPEN("Open"), FIXED("Fixed"), WONTFIX("Won't Fix"), IGNORE("Ignored"), FAILURE("Failure");
+
+    private final String label;
+
+    ProblemSituation(String label) {
+        this.label = label;
+    }
+
+    @Override
+    public String toString() {
+        return label;
+    }
+}
diff --git a/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ProblemsDatabaseProblemsHistoryTest.java b/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ProblemsDatabaseProblemsHistoryTest.java
new file mode 100644
index 0000000..cd458f4
--- /dev/null
+++ b/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ProblemsDatabaseProblemsHistoryTest.java
@@ -0,0 +1,221 @@
+package org.eclipse.epp.internal.logging.aeri.ide.server.mars;
+
+import static com.google.common.base.Optional.absent;
+import static org.eclipse.epp.logging.aeri.core.ResetSendMode.RESTART;
+import static org.eclipse.epp.logging.aeri.core.SendMode.NEVER;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.http.HttpStatus;
+import org.apache.lucene.analysis.KeywordAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Field.Index;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.Version;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.epp.internal.logging.aeri.ide.server.mars.ProblemsDatabaseProblemsHistory.UpdateIndexJob;
+import org.eclipse.epp.internal.logging.aeri.ide.utils.Zips;
+import org.eclipse.epp.logging.aeri.core.ISystemSettings;
+import org.eclipse.epp.logging.aeri.core.ProblemStatus;
+import org.eclipse.epp.logging.aeri.core.util.Statuses;
+import org.eclipse.epp.logging.aeri.tests.util.TestStatuses;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class ProblemsDatabaseProblemsHistoryTest {
+
+    private ProblemsDatabaseProblemsHistory sut;
+
+    private RAMDirectory directory;
+
+    @Before
+    public void setup() {
+        sut = new ProblemsDatabaseProblemsHistory(null) {
+
+            @Override
+            Directory createIndexDirectory() throws IOException {
+                directory = new RAMDirectory();
+                return directory;
+            }
+        };
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSeenWithHistoryNotRunning() {
+        sut.seen(TestStatuses.coreExceptionStatus());
+    }
+
+    @Test
+    public void testSeenOnEmptyProblemsDatabase() {
+        sut.startAsync().awaitRunning();
+
+        assertThat(sut.seen(TestStatuses.coreExceptionStatus()), is(absent()));
+    }
+
+    @Test
+    public void testIndexProblemStatus() throws Exception {
+        sut.startAsync().awaitRunning();
+
+        Status status = TestStatuses.thirdPartyStatus();
+        addToIndex(status, "NEEDINFO");
+
+        assertThat(sut.seen(status).get().getStatus(), is(ProblemStatus.NEEDINFO));
+    }
+
+    @Test
+    public void testIndexMatchStatus() throws Exception {
+        sut.startAsync().awaitRunning();
+
+        Status status = TestStatuses.multiStatus();
+        addToIndex(status, "FIXED");
+        // should not match
+        addToIndex(TestStatuses.coreExceptionStatus(), "NONE");
+
+        assertThat(sut.seen(status).get().getStatus(), is(ProblemStatus.FIXED));
+    }
+
+    public static class UpdateIndexJobTest {
+
+        @Rule
+        public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+        @Test
+        public void testUpdateLocalNotOutdated() {
+            IO io = mock(IO.class);
+            when(io.getConfiguration()).thenReturn(new ServerConfiguration());
+            ISystemSettings settings = mock(ISystemSettings.class);
+            ProblemsDatabaseProblemsHistory history = mock(ProblemsDatabaseProblemsHistory.class);
+
+            when(io.isProblemsDatabaseOutdated()).thenReturn(false);
+            UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
+
+            job.run(mock(IProgressMonitor.class));
+            verify(io).getConfiguration();
+            verify(io).isProblemsDatabaseOutdated();
+            verifyNoMoreInteractions(io);
+            verifyNoMoreInteractions(settings);
+            verifyNoMoreInteractions(history);
+        }
+
+        @Test
+        public void testUpdateRemoteNotModified() throws IOException {
+            IO io = mock(IO.class);
+            when(io.getConfiguration()).thenReturn(new ServerConfiguration());
+            ISystemSettings settings = mock(ISystemSettings.class);
+            ProblemsDatabaseProblemsHistory history = mock(ProblemsDatabaseProblemsHistory.class);
+
+            when(io.isProblemsDatabaseOutdated()).thenReturn(true);
+            when(io.downloadDatabase(any(File.class), any(IProgressMonitor.class))).thenReturn(HttpStatus.SC_NOT_MODIFIED);
+            UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
+
+            job.run(mock(IProgressMonitor.class));
+
+            verify(io).getConfiguration();
+            verify(io).isProblemsDatabaseOutdated();
+            verify(io).downloadDatabase(any(File.class), any(IProgressMonitor.class));
+            verifyNoMoreInteractions(io);
+            verifyNoMoreInteractions(settings);
+            verifyNoMoreInteractions(history);
+        }
+
+        @Test
+        public void testUpdateFromRemoteFailure() throws IOException {
+            IO io = mock(IO.class);
+            when(io.getConfiguration()).thenReturn(new ServerConfiguration());
+
+            ISystemSettings settings = mock(ISystemSettings.class);
+            ProblemsDatabaseProblemsHistory history = mock(ProblemsDatabaseProblemsHistory.class);
+
+            when(io.isProblemsDatabaseOutdated()).thenReturn(true);
+            when(io.downloadDatabase(any(File.class), any(IProgressMonitor.class))).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
+            UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
+
+            job.run(mock(IProgressMonitor.class));
+
+            verify(io).getConfiguration();
+            verify(io).isProblemsDatabaseOutdated();
+            verify(io).downloadDatabase(any(File.class), any(IProgressMonitor.class));
+            verifyNoMoreInteractions(io);
+            verify(settings).setSendMode(NEVER);
+            verify(settings).setResetSendMode(RESTART);
+            verifyNoMoreInteractions(settings);
+            verifyNoMoreInteractions(history);
+        }
+
+        @Test
+        public void testUpdateFromRemoteSuccess() throws IOException {
+            IO io = mock(IO.class);
+            when(io.getConfiguration()).thenReturn(new ServerConfiguration());
+
+            ISystemSettings settings = mock(ISystemSettings.class);
+            ProblemsDatabaseProblemsHistory history = mock(ProblemsDatabaseProblemsHistory.class);
+
+            when(io.isProblemsDatabaseOutdated()).thenReturn(true);
+            // the job will try to unzip the index for the service
+            when(io.downloadDatabase(any(File.class), any(IProgressMonitor.class))).then(new Answer<Integer>() {
+
+                @Override
+                public Integer answer(InvocationOnMock invocation) throws Throwable {
+                    File file = (File) invocation.getArguments()[0];
+                    createMinimalZipFile(file);
+                    return HttpStatus.SC_OK;
+                }
+
+            });
+
+            UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
+
+            long downloadTimestampBefore = io.getConfiguration().getProblemsZipLastDownloadTimestamp();
+
+            job.run(mock(IProgressMonitor.class));
+
+            long downloadedTimestampNow = io.getConfiguration().getProblemsZipLastDownloadTimestamp();
+
+            assertThat(downloadedTimestampNow, is(not(downloadTimestampBefore)));
+
+            verify(io, atLeastOnce()).getConfiguration();
+            verify(io).isProblemsDatabaseOutdated();
+            verify(io).downloadDatabase(any(File.class), any(IProgressMonitor.class));
+            verify(io).saveConfiguration();
+            verifyNoMoreInteractions(io);
+            verifyNoMoreInteractions(settings);
+            verify(history).replaceContent(any(File.class));
+        }
+
+        private void createMinimalZipFile(File file) throws IOException {
+            File folder = temporaryFolder.newFolder();
+            File f = new File(folder, "empty");
+            f.createNewFile();
+            Zips.zip(folder, file);
+        }
+    }
+
+    private void addToIndex(Status status, String problemStatus) throws Exception {
+        IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_35, new KeywordAnalyzer());
+        conf.setOpenMode(OpenMode.CREATE_OR_APPEND);
+        try (IndexWriter writer = new IndexWriter(directory, conf)) {
+            Document doc = new Document();
+            doc.add(new Field(ProblemsDatabaseProblemsHistory.F_FINGERPRINT, Statuses.traceIdentityHash(status), Store.NO, Index.NOT_ANALYZED));
+            doc.add(new Field(ProblemsDatabaseProblemsHistory.F_ACTION, problemStatus, Store.YES, Index.NO));
+            writer.addDocument(doc);
+            writer.commit();
+            sut.indexChanged();
+        }
+    }
+}
diff --git a/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/RemoteProblemsHistoryTest.java b/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/RemoteProblemsHistoryTest.java
deleted file mode 100644
index d362f59..0000000
--- a/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/RemoteProblemsHistoryTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package org.eclipse.epp.internal.logging.aeri.ide.server.mars;
-
-import static org.eclipse.epp.logging.aeri.core.SendMode.NEVER;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.apache.http.HttpStatus;
-import org.apache.lucene.analysis.KeywordAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.Field.Index;
-import org.apache.lucene.document.Field.Store;
-import org.apache.lucene.index.CorruptIndexException;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.IndexWriterConfig.OpenMode;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.LockObtainFailedException;
-import org.apache.lucene.store.RAMDirectory;
-import org.apache.lucene.util.Version;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.epp.internal.logging.aeri.ide.server.mars.ServerProblemsHistory.RemoteProblemsHistoryFilter;
-import org.eclipse.epp.internal.logging.aeri.ide.server.mars.ServerProblemsHistory.UpdateIndexJob;
-import org.eclipse.epp.internal.logging.aeri.ide.utils.Zips;
-import org.eclipse.epp.logging.aeri.core.ISystemSettings;
-import org.eclipse.epp.logging.aeri.core.ProblemStatus;
-import org.eclipse.epp.logging.aeri.core.ResetSendMode;
-import org.eclipse.epp.logging.aeri.core.util.Statuses;
-import org.eclipse.epp.logging.aeri.tests.util.TestStatus;
-import org.eclipse.epp.logging.aeri.tests.util.TestStatuses;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-public class RemoteProblemsHistoryTest {
-
-    private ServerProblemsHistory sut;
-    private RemoteProblemsHistoryFilter filter;
-    private TestStatus status;
-
-    private RAMDirectory directory;
-
-    @Rule
-    public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-    @Before
-    public void setup() {
-        sut = new ServerProblemsHistory(null) {
-
-            @Override
-            protected Directory createIndexDirectory() throws IOException {
-                directory = new RAMDirectory();
-                return directory;
-            }
-
-        };
-        filter = new RemoteProblemsHistoryFilter(sut);
-        status = new TestStatus();
-        status.setException(new RuntimeException().fillInStackTrace());
-    }
-
-    @Test
-    public void testNotRunning() {
-        // not started should not fail anything
-        assertTrue(filter.apply(status));
-    }
-
-    @Test
-    public void testEmptyProblemsDatabase() {
-        sut.startAsync().awaitRunning();
-        assertTrue(filter.apply(status));
-    }
-
-    @Test
-    public void testIndexProblemStatus() throws Exception {
-        sut.startAsync().awaitRunning();
-        Status status = TestStatuses.thirdPartyStatus();
-
-        index(status, "NEEDINFO");
-
-        assertThat(sut.seen(status).getStatus(), is(ProblemStatus.NEEDINFO));
-    }
-
-    @Test
-    public void testIndexMatchStatus() throws Exception {
-        sut.startAsync().awaitRunning();
-        Status status = TestStatuses.multiStatus();
-
-        index(status, "FIXED");
-        // should not match
-        index(TestStatuses.coreExceptionStatus(), "NONE");
-
-        assertThat(sut.seen(status).getStatus(), is(ProblemStatus.FIXED));
-    }
-
-    @Test
-    public void testUpdateLocalNotOutdated() {
-        IO io = mock(IO.class);
-        when(io.getConfiguration()).thenReturn(new ServerConfiguration());
-        ISystemSettings settings = mock(ISystemSettings.class);
-        ServerProblemsHistory history = mock(ServerProblemsHistory.class);
-
-        when(io.isProblemsDatabaseOutdated()).thenReturn(false);
-        UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
-
-        job.run(mock(IProgressMonitor.class));
-        verify(io).getConfiguration();
-        verify(io).isProblemsDatabaseOutdated();
-        verifyNoMoreInteractions(io);
-        verifyNoMoreInteractions(settings);
-        verifyNoMoreInteractions(history);
-    }
-
-    @Test
-    public void testUpdateRemoteNotModified() throws IOException {
-        IO io = mock(IO.class);
-        when(io.getConfiguration()).thenReturn(new ServerConfiguration());
-        ISystemSettings settings = mock(ISystemSettings.class);
-        ServerProblemsHistory history = mock(ServerProblemsHistory.class);
-
-        when(io.isProblemsDatabaseOutdated()).thenReturn(true);
-        when(io.downloadDatabase(any(File.class), any(IProgressMonitor.class))).thenReturn(HttpStatus.SC_NOT_MODIFIED);
-        UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
-
-        job.run(mock(IProgressMonitor.class));
-
-        verify(io).getConfiguration();
-        verify(io).isProblemsDatabaseOutdated();
-        verify(io).downloadDatabase(any(File.class), any(IProgressMonitor.class));
-        verifyNoMoreInteractions(io);
-        verifyNoMoreInteractions(settings);
-        verifyNoMoreInteractions(history);
-    }
-
-    @Test
-    public void testUpdateFromRemoteFailure() throws IOException {
-        IO io = mock(IO.class);
-        when(io.getConfiguration()).thenReturn(new ServerConfiguration());
-
-        ISystemSettings settings = mock(ISystemSettings.class);
-        ServerProblemsHistory history = mock(ServerProblemsHistory.class);
-
-        when(io.isProblemsDatabaseOutdated()).thenReturn(true);
-        when(io.downloadDatabase(any(File.class), any(IProgressMonitor.class))).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
-        UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
-
-        job.run(mock(IProgressMonitor.class));
-
-        verify(io).getConfiguration();
-        verify(io).isProblemsDatabaseOutdated();
-        verify(io).downloadDatabase(any(File.class), any(IProgressMonitor.class));
-        verifyNoMoreInteractions(io);
-        verify(settings).setSendMode(NEVER);
-        verify(settings).setResetSendMode(ResetSendMode.RESTART);
-        verifyNoMoreInteractions(settings);
-        verifyNoMoreInteractions(history);
-    }
-
-    @Test
-    public void testUpdateFromRemoteSuccess() throws IOException {
-        IO io = mock(IO.class);
-        when(io.getConfiguration()).thenReturn(new ServerConfiguration());
-
-        ISystemSettings settings = mock(ISystemSettings.class);
-        ServerProblemsHistory history = mock(ServerProblemsHistory.class);
-
-        when(io.isProblemsDatabaseOutdated()).thenReturn(true);
-        // the job will try to unzip the index for the service
-        when(io.downloadDatabase(any(File.class), any(IProgressMonitor.class))).then(new Answer<Integer>() {
-
-            @Override
-            public Integer answer(InvocationOnMock invocation) throws Throwable {
-                File file = (File) invocation.getArguments()[0];
-                createMinimalZipFile(file);
-                return HttpStatus.SC_OK;
-            }
-
-        });
-
-        UpdateIndexJob job = new UpdateIndexJob(io, settings, history);
-
-        long downloadTimestampBefore = io.getConfiguration().getProblemsZipLastDownloadTimestamp();
-
-        job.run(mock(IProgressMonitor.class));
-
-        long downloadedTimestampNow = io.getConfiguration().getProblemsZipLastDownloadTimestamp();
-
-        assertThat(downloadedTimestampNow, is(not(downloadTimestampBefore)));
-
-        verify(io, atLeastOnce()).getConfiguration();
-        verify(io).isProblemsDatabaseOutdated();
-        verify(io).downloadDatabase(any(File.class), any(IProgressMonitor.class));
-        verify(io).saveConfiguration();
-        verifyNoMoreInteractions(io);
-        verifyNoMoreInteractions(settings);
-        verify(history).replaceContent(any(File.class));
-    }
-
-    private void index(Status status, String problemStatus) throws CorruptIndexException, LockObtainFailedException, IOException {
-        IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_35, new KeywordAnalyzer());
-        conf.setOpenMode(OpenMode.CREATE_OR_APPEND);
-        try (IndexWriter writer = new IndexWriter(directory, conf)) {
-            Document doc = new Document();
-            doc.add(new Field(ServerProblemsHistory.F_FINGERPRINT, Statuses.traceIdentityHash(status), Store.NO, Index.NOT_ANALYZED));
-            doc.add(new Field(ServerProblemsHistory.F_ACTION, problemStatus, Store.YES, Index.NO));
-            writer.addDocument(doc);
-            writer.commit();
-            sut.indexChanged();
-        }
-    }
-
-    private void createMinimalZipFile(File file) throws IOException {
-        File folder = temporaryFolder.newFolder();
-        File f = new File(folder, "empty");
-        f.createNewFile();
-        Zips.zip(folder, file);
-    }
-}
diff --git a/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionDatabaseDownloadTest.java b/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionDatabaseDownloadTest.java
index a510fd6..5eae48b 100644
--- a/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionDatabaseDownloadTest.java
+++ b/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionDatabaseDownloadTest.java
@@ -1,8 +1,6 @@
 package org.eclipse.epp.internal.logging.aeri.ide.server.mars;
 
-import static org.eclipse.epp.logging.aeri.core.SendMode.BACKGROUND;
-import static org.eclipse.epp.logging.aeri.core.SendMode.NEVER;
-import static org.eclipse.epp.logging.aeri.core.SendMode.NOTIFY;
+import static org.eclipse.epp.logging.aeri.core.SendMode.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.*;
 
@@ -25,10 +23,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.mockito.verification.VerificationMode;
 
 @RunWith(Parameterized.class)
 public class ServerConnectionDatabaseDownloadTest {
@@ -37,113 +31,63 @@
     private static final boolean NOT_ENABLED = false;
     private static final boolean CONFIGURED = true;
     private static final boolean NOT_CONFIGURED = false;
-    private static final boolean DB_OUTDATED = true;
-    private static final boolean NOT_DB_OUTDATED = false;
     private static final boolean ENABLED_AFTER_START = true;
     private static final boolean NOT_ENABLED_AFTER_START = false;
     private static final boolean CONFIGURED_AFTER_START = true;
     private static final boolean NOT_CONFIGURED_AFTER_START = false;
 
-    @Parameters(name = "{0},{1},{2},{3},{4},{5}")
+    @Parameters(name = "{0}, {1}, {2}, {3}, {4}, {5}")
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] {
                 //
-                { BACKGROUND, ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NEVER, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, times(1) },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, never() },
-                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, NOT_DB_OUTDATED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, never() },
+                { BACKGROUND, ENABLED, CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 3 },
+                { BACKGROUND, ENABLED, CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 2 },
+                { BACKGROUND, ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 1 },
+                { BACKGROUND, ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 1 },
+                { BACKGROUND, ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 1 },
+                { BACKGROUND, ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { BACKGROUND, ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { BACKGROUND, ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { BACKGROUND, NOT_ENABLED, CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 2 },
+                { BACKGROUND, NOT_ENABLED, CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 1 },
+                { BACKGROUND, NOT_ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { BACKGROUND, NOT_ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 1 },
+                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { BACKGROUND, NOT_ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NEVER, NOT_ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, ENABLED, CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 3 },
+                { NOTIFY, ENABLED, CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 2 },
+                { NOTIFY, ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 1 },
+                { NOTIFY, ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 1 },
+                { NOTIFY, ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 1 },
+                { NOTIFY, ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, NOT_ENABLED, CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 2 },
+                { NOTIFY, NOT_ENABLED, CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 1 },
+                { NOTIFY, NOT_ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, NOT_ENABLED, CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, CONFIGURED_AFTER_START, 1 },
+                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, CONFIGURED_AFTER_START, 0 },
+                { NOTIFY, NOT_ENABLED, NOT_CONFIGURED, NOT_ENABLED_AFTER_START, NOT_CONFIGURED_AFTER_START, 0 },
                 //
         });
     }
@@ -158,16 +102,13 @@
     public boolean isInitialConfigured;
 
     @Parameter(3)
-    public boolean isDatabaseOutdated;
-
-    @Parameter(4)
     public boolean isEnabledAfterStartup;
 
-    @Parameter(5)
+    @Parameter(4)
     public boolean isConfiguredAfterStartup;
 
-    @Parameter(6)
-    public VerificationMode timesUpdateCalled;
+    @Parameter(5)
+    public int expectedSyncCalls;
 
     private IModelFactory mFac = IModelFactory.eINSTANCE;
     private IIdeFactory iFac = IIdeFactory.eINSTANCE;
@@ -179,34 +120,44 @@
 
     private IServerDescriptor descriptor;
     private ISystemSettings settings;
+    private IProblemsHistory serverProblemsHistory;
 
-    private boolean isMockDatabaseDownloaded;
+    private boolean syncCalled = false;
 
     @Before
     public void setup() throws IOException {
-        descriptor = iFac.createServerDescriptor();
+        descriptor = createServerDescriptor();
+
+        settings = createSystemSettings();
+
+        File configurationArea = temporaryFolder.newFolder();
+
+        sut = spy(new ServerConnection(descriptor, settings, configurationArea));
+
+        ServerConfiguration serverConfiguration = new ServerConfiguration();
+        serverConfiguration.setProblemsUrl("http://example.org/problems.zip");
+
+        io = mock(IO.class);
+        when(io.getConfiguration()).thenReturn(serverConfiguration);
+
+        when(sut.createIO(any())).thenReturn(io);
+
+        serverProblemsHistory = mock(IProblemsHistory.class);
+
+        when(sut.createServerProblemsHistory()).thenReturn(serverProblemsHistory);
+    }
+
+    private IServerDescriptor createServerDescriptor() {
+        IServerDescriptor descriptor = iFac.createServerDescriptor();
         descriptor.setEnabled(false);
         descriptor.setConfigured(false);
-        io = Mockito.mock(IO.class);
-        when(io.isProblemsDatabaseOutdated()).thenAnswer(new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) throws Throwable {
-                return isDatabaseOutdated && !isMockDatabaseDownloaded;
-            }
-        });
-        File configurationArea = temporaryFolder.newFolder();
-        settings = mFac.createSystemSettings();
+        return descriptor;
+    }
+
+    private ISystemSettings createSystemSettings() {
+        ISystemSettings settings = mFac.createSystemSettings();
         settings.setConfigured(true);
-        ServerConnection serverConnection = new ServerConnection(descriptor, settings, configurationArea);
-        sut = Mockito.spy(serverConnection);
-        isMockDatabaseDownloaded = false;
-        Mockito.doAnswer(new Answer<Void>() {
-            public Void answer(InvocationOnMock invocation) {
-                isMockDatabaseDownloaded = true;
-                return null;
-            }
-        }).when(sut).updateProblemDatabase(any(), any(), any());
-        when(sut.createIO(any())).thenReturn(io);
+        return settings;
     }
 
     @After
@@ -217,15 +168,17 @@
     @Test
     public void test() throws Exception {
         settings.setSendMode(sendMode);
+
         descriptor.setEnabled(isInitialEnabled);
+
         descriptor.setConfigured(isInitialConfigured);
 
         sut.startUp();
 
         descriptor.setEnabled(isEnabledAfterStartup);
+
         descriptor.setConfigured(isConfiguredAfterStartup);
 
-        verify(sut, timesUpdateCalled).updateProblemDatabase(any(), any(), any());
+        verify(serverProblemsHistory, times(expectedSyncCalls)).sync(any(), any());
     }
-
 }
diff --git a/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionTest.java b/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionTest.java
index 361b738..c7bcdf5 100644
--- a/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionTest.java
+++ b/tests/org.eclipse.epp.logging.aeri.ide.tests/src/org/eclipse/epp/internal/logging/aeri/ide/server/mars/ServerConnectionTest.java
@@ -43,7 +43,6 @@
         assertFalse(sut.msgNeedinfoBeforeSend(state).contains("{"));
         assertFalse(sut.msgNewAfterSend(state).contains("{"));
         assertFalse(sut.msgUnconfirmedAfterSend(state).contains("{"));
-        assertFalse(sut.msgUnconfirmedBeforeSend(state).contains("{"));
+        assertFalse(sut.msgUnconfirmedBeforeSend().contains("{"));
     }
-
 }