| /** |
| * Copyright (c) 2015 Codetrails GmbH. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| */ |
| package org.eclipse.epp.internal.logging.aeri.ide.server.mars; |
| |
| import static com.google.common.base.Preconditions.*; |
| import static org.apache.commons.lang3.StringUtils.*; |
| import static org.eclipse.epp.internal.logging.aeri.ide.l10n.LogMessages.*; |
| import static org.eclipse.epp.logging.aeri.core.SendMode.NEVER; |
| import static org.eclipse.epp.logging.aeri.core.util.Logs.log; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.UnknownHostException; |
| import java.util.Arrays; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.annotation.PostConstruct; |
| import javax.annotation.PreDestroy; |
| |
| import org.apache.commons.io.FileUtils; |
| 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.IndexReader; |
| 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.SearcherManager; |
| import org.apache.lucene.search.TermQuery; |
| import org.apache.lucene.search.TopDocs; |
| import org.apache.lucene.store.Directory; |
| import org.apache.lucene.store.FSDirectory; |
| import org.apache.lucene.util.IOUtils; |
| import org.apache.lucene.util.Version; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.epp.internal.logging.aeri.ide.l10n.LogMessages; |
| import org.eclipse.epp.internal.logging.aeri.ide.l10n.Messages; |
| import org.eclipse.epp.internal.logging.aeri.ide.utils.Formats; |
| import org.eclipse.epp.internal.logging.aeri.ide.utils.Zips; |
| 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.ResetSendMode; |
| import org.eclipse.epp.logging.aeri.core.util.Links; |
| import org.eclipse.epp.logging.aeri.core.util.Logs; |
| 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.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 { |
| |
| // values for problem databases |
| public static final String F_VERSION = "version"; //$NON-NLS-1$ |
| public static final String VERSION = "0.6"; //$NON-NLS-1$ |
| |
| public static final String F_MESSAGE = "message"; //$NON-NLS-1$ |
| public static final String F_ACTION = "action"; //$NON-NLS-1$ |
| public static final String F_BUG_ID = "bugId"; //$NON-NLS-1$ |
| public static final String F_BUG_URL = "bugUrl"; //$NON-NLS-1$ |
| public static final String F_PROBLEM_URL = "problemUrl"; //$NON-NLS-1$ |
| public static final String F_FINGERPRINT = "fingerprint"; //$NON-NLS-1$ |
| public static final String F_NEEDINFOS = "needinfos"; //$NON-NLS-1$ |
| |
| private File stateLocation; |
| private Directory index; |
| private SearcherManager manager; |
| |
| public ServerProblemsHistory(File stateLocation) { |
| this.stateLocation = stateLocation; |
| } |
| |
| @PostConstruct |
| private void e4PostConstruct() { |
| startAsync(); |
| } |
| |
| @Override |
| protected void startUp() throws Exception { |
| index = createIndexDirectory(); |
| if (!IndexReader.indexExists(index)) { |
| createInitialIndex(index); |
| } |
| manager = new SearcherManager(index, null, null); |
| } |
| |
| @VisibleForTesting |
| protected Directory createIndexDirectory() throws IOException { |
| stateLocation.mkdirs(); |
| FSDirectory directory = FSDirectory.open(stateLocation); |
| |
| return directory; |
| } |
| |
| private void createInitialIndex(Directory directory) throws IOException { |
| IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_35, new KeywordAnalyzer()); |
| conf.setOpenMode(OpenMode.CREATE_OR_APPEND); |
| try (IndexWriter writer = new IndexWriter(directory, conf)) { |
| Document meta = new Document(); |
| meta.add(new Field(F_VERSION, VERSION, Store.YES, Index.NO)); |
| writer.addDocument(meta); |
| writer.commit(); |
| } |
| } |
| |
| public 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) { |
| IndexSearcher searcher = manager.acquire(); |
| try { |
| TopDocs results = searcher.search(q, 1); |
| if (results.totalHits > 0) { |
| // HIT |
| int doc = results.scoreDocs[0].doc; |
| Document d = searcher.doc(doc); |
| IProblemState status = loadStatus(d); |
| return status; |
| } |
| } catch (Exception e) { |
| log(WARN_INDEX_NOT_AVAILABLE, e); |
| } finally { |
| try { |
| manager.release(searcher); |
| } catch (IOException e) { |
| log(WARN_INDEX_NOT_AVAILABLE, e); |
| } |
| } |
| IProblemState state = IModelFactory.eINSTANCE.createProblemState(); |
| state.setStatus(ProblemStatus.UNCONFIRMED); |
| return state; |
| } |
| |
| private IProblemState loadStatus(Document d) { |
| IProblemState state = IModelFactory.eINSTANCE.createProblemState(); |
| String bugUrl = stripToNull(d.get(F_BUG_URL)); |
| String bugId = stripToNull(d.get(F_BUG_ID)); |
| ProblemStatus status = parseProblemStatus(d.get(F_ACTION)); |
| |
| String message = stripToNull(d.get(F_MESSAGE)); |
| if (bugId != null) { |
| ILink bug = Links.createBugLink(bugUrl, Formats.format(Messages.LINK_TEXT_BUG, bugId)); |
| state.getLinks().put("bug", bug); //$NON-NLS-1$ |
| } |
| state.getNeedinfo().addAll(Arrays.asList(d.getValues(F_NEEDINFOS))); |
| state.setStatus(status); |
| state.setMessage(message); |
| return state; |
| } |
| |
| private ProblemStatus parseProblemStatus(String string) { |
| switch (defaultString(string)) { |
| // It's not exactly clear which states a server knows - be conservative: |
| case "IGNORE": //$NON-NLS-1$ |
| case "INVALID": //$NON-NLS-1$ |
| return ProblemStatus.IGNORED; |
| case "NEEDINFO": //$NON-NLS-1$ |
| return ProblemStatus.NEEDINFO; |
| case "FIXED": //$NON-NLS-1$ |
| return ProblemStatus.FIXED; |
| case "WONTFIX": //$NON-NLS-1$ |
| return ProblemStatus.WONTFIX; |
| case "NONE": //$NON-NLS-1$ |
| return ProblemStatus.UNCONFIRMED; |
| case "": //$NON-NLS-1$ |
| default: |
| Logs.log(LogMessages.DEBUG_UNKNOWN_SERVER_STATUS_IN_REMOTE_HISTORY, string); |
| return ProblemStatus.UNCONFIRMED; |
| } |
| } |
| |
| public void replaceContent(File tempDir) throws IOException { |
| IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_35, new KeywordAnalyzer()); |
| conf.setOpenMode(OpenMode.CREATE_OR_APPEND); |
| try (IndexWriter writer = new IndexWriter(index, conf); FSDirectory newContent = FSDirectory.open(tempDir);) { |
| writer.deleteAll(); |
| writer.addIndexes(newContent); |
| writer.commit(); |
| indexChanged(); |
| } |
| } |
| |
| @VisibleForTesting |
| protected void indexChanged() throws IOException { |
| manager.maybeReopen(); |
| } |
| |
| @PreDestroy |
| private void e4PreDestroy() { |
| try { |
| stopAsync().awaitTerminated(2, TimeUnit.SECONDS); |
| } catch (Exception e) { |
| log(WARN_HISTORY_STOP_FAILED, e); |
| } |
| } |
| |
| @Override |
| protected void shutDown() throws Exception { |
| IOUtils.close(index); |
| 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; |
| |
| public UpdateIndexJob(IO io, ISystemSettings settings, ServerProblemsHistory history) { |
| |
| super(Formats.format(Messages.JOB_NAME_UPDATE_INDEX, io.getConfiguration().getProblemsUrl())); |
| this.io = io; |
| this.settings = settings; |
| this.history = history; |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| SubMonitor progress = SubMonitor.convert(monitor, 3); |
| progress.beginTask(Messages.JOB_TASK_NAME_CHECKING_INDEX, 1000); |
| if (!io.isProblemsDatabaseOutdated()) { |
| return Status.OK_STATUS; |
| } |
| try { |
| progress.subTask(Messages.JOB_TASK_NAME_CHECKING_REMOTE_STATUS); |
| File tempRemoteIndexZip = File.createTempFile("problems-index", ".zip"); //$NON-NLS-1$ //$NON-NLS-2$ |
| int downloadStatus = io.downloadDatabase(tempRemoteIndexZip, progress); |
| if (downloadStatus == HttpStatus.SC_NOT_MODIFIED) { |
| return Status.OK_STATUS; |
| } else if (downloadStatus != HttpStatus.SC_OK) { |
| // Could not access problems.zip for whatever reason; switch off error reporting until restart. |
| settings.setSendMode(NEVER); |
| settings.setResetSendMode(ResetSendMode.RESTART); |
| log(INFO_SERVER_NOT_AVAILABLE); |
| return Status.OK_STATUS; |
| } |
| |
| progress.worked(1); |
| File tempDir = Files.createTempDir(); |
| progress.subTask(Messages.JOB_TASK_NAME_REPLACING_LOCAL_DATABASE); |
| try { |
| Zips.unzip(tempRemoteIndexZip, tempDir); |
| history.replaceContent(tempDir); |
| io.getConfiguration().setProblemsZipLastDownloadTimestamp(System.currentTimeMillis()); |
| io.saveConfiguration(); |
| progress.worked(1); |
| } finally { |
| // cleanup files |
| tempRemoteIndexZip.delete(); |
| FileUtils.deleteDirectory(tempDir); |
| progress.worked(1); |
| } |
| |
| return Status.OK_STATUS; |
| } catch (CancellationException e) { |
| return Status.CANCEL_STATUS; |
| } catch (UnknownHostException e) { |
| Logs.debug("Failed to connect to server", e); |
| return Status.CANCEL_STATUS; |
| } catch (Exception e) { |
| log(WARN_INDEX_UPDATE_FAILED, io.getConfiguration().getProblemsUrl(), e); |
| return Status.OK_STATUS; |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| } |
| } |