/******************************************************************************* * Copyright (c) 2013, 2016 Pivotal Software, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.wizard.content; import java.io.File; import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jface.operation.IRunnableContext; import org.springframework.ide.eclipse.boot.util.Log; import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator; import org.springframework.ide.eclipse.boot.wizard.github.auth.AuthenticatedDownloader; import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager; import org.springsource.ide.eclipse.commons.frameworks.core.util.JobUtil; import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable; /** * An instance of the class manages lists of content of different types. The * idea is to create a subclass that provides all the concrete details on how * different types of content are discovered, downloaded and cached on the local * file system. * <p> * But the infrastructure for managing/downloading the content is shared. * * @author Kris De Volder * @author Nieraj Singh */ public class ContentManager { private final Map<Class<?>, TypedContentManager<?>> byClass = new HashMap<>(); private final List<ContentType<?>> types = new ArrayList<>(); protected LiveVariable<DownloadState> prefetchContentTracker = new LiveVariable<>(DownloadState.NOT_STARTED); protected Throwable prefetchContentError = null; protected LiveVariable<DownloadState> prefetchContentProviderPropertiesTracker = new LiveVariable<>( DownloadState.NOT_STARTED); public <T extends GSContent> void register(Class<T> klass, String description, ContentProvider<T> provider) { try { Assert.isLegal(!byClass.containsKey(klass), "A content provider for " + klass + " is already registered"); prefetchContentTracker.setValue(DownloadState.NOT_STARTED); ContentType<T> ctype = new ContentType<>(klass, description); types.add(ctype); DownloadManager downloader = downloadManagerFor(klass); byClass.put(klass, new TypedContentManager<>(downloader, provider)); } catch (Throwable e) { BootWizardActivator.log(e); } } /** * Factory method to create a DownloadManager for a given content type name */ public DownloadManager downloadManagerFor(Class<?> contentType) throws IllegalStateException, IOException { return new DownloadManager(new AuthenticatedDownloader(), new File(BootWizardActivator.getDefault().getStateLocation().toFile(), contentType.getSimpleName())) .clearCache(); } /** * Fetch the content of a given type. May return null but only if no content * provider has been registered for the type. */ @SuppressWarnings("unchecked") public <T> T[] get(Class<T> type) { TypedContentManager<T> man = (TypedContentManager<T>) byClass.get(type); if (man != null) { return man.getAll(); } return null; } public ContentType<?>[] getTypes() { return types.toArray(new ContentType<?>[types.size()]); } public Object[] get(ContentType<?> ct) { if (ct != null) { return get(ct.getKlass()); } return null; } /** * Will return content for the given content type, IF and only if, there is no prefetching currently under way. Otherwise it returns null. * @param ct * @return content if no downloading is currently taking place. Null otherwise */ public Object[] getWithPrefetchCheck(ContentType<?> ct) { if (prefetchContentTracker.getValue() != DownloadState.IS_DOWNLOADING) { return get(ct); } return null; } /** * Creates a content manager that contains just a single content item. */ public static <T extends GSContent> ContentManager singleton(final Class<T> type, final String description, final T item) { ContentManager cm = new ContentManager(); cm.register(type, description, new ContentProvider<T>() { // @Override public T[] fetch(DownloadManager downloader) { @SuppressWarnings("unchecked") T[] array = (T[]) Array.newInstance(type, 1); item.setDownloader(downloader); array[0] = item; return array; } }); return cm; } /** * Prefetch all the content. May require network access therefore do not run in the UI thread. */ public void prefetchAllContent(IProgressMonitor monitor) { prefetchContentTracker.setValue(DownloadState.IS_DOWNLOADING); prefetchContentError = null; try { ContentType<?>[] allTypes = getTypes(); if (allTypes != null) { for (ContentType<?> type : allTypes) { if (monitor.isCanceled()) { break; } get(type); } } // Inform any listeners that completion has just finished (e.g. to let listeners refresh the content in their views) prefetchContentTracker.setValue(DownloadState.DOWNLOADING_COMPLETED); } catch (Throwable e) { prefetchContentError = e; prefetchContentTracker.setValue(DownloadState.DOWNLOADING_FAILED); throw e; } finally { // Mark the session as downloaded prefetchContentTracker.setValue(DownloadState.DOWNLOADED); } } public LiveVariable<DownloadState> getPrefetchContentTracker() { return prefetchContentTracker; } public Throwable getPrefetchContentError() { return prefetchContentError; } public LiveVariable<DownloadState> getPrefetchContentProviderPropertiesTracker() { return prefetchContentProviderPropertiesTracker; } /** * Will run {@link #prefetch(IProgressMonitor)} in the background. * @param runnableContext. Must not be null. */ public void prefetchInBackground(IRunnableContext runnableContext) { try { JobUtil.runBackgroundJobWithUIProgress((monitor) -> { prefetch(monitor); }, runnableContext, "Prefetching content..."); } catch (Exception e) { BootWizardActivator.log(e); } } /** * Will prefetch (download) all content. This may be a long-running process * and is not expected to be run in the UI thread. * * @param monitor * must not be null */ protected void prefetch(IProgressMonitor monitor) { String downloadLabel = "Downloading all content. Please wait..."; prefetchAllContent(SubMonitor.convert(monitor, downloadLabel, 50)); } /** * Disposes all prefetching tracking listeners. However, tracking states are preserved. */ public void disposePrefetchTrackingListeners() { // The purpose of disposing trackers is to clear any listeners that are registered to avoid possible memory leaks // on accumulating listeners over time, but the tracker states should be preserved. DownloadState propertiesDownloadState = prefetchContentProviderPropertiesTracker.getValue(); DownloadState contentDownloadState = prefetchContentTracker.getValue(); // Dispose all listeners to avoid memory leaks for listeners that keep getting accumulating over time. prefetchContentProviderPropertiesTracker.dispose(); prefetchContentTracker.dispose(); // Make sure new trackers are set prefetchContentProviderPropertiesTracker = new LiveVariable<>(propertiesDownloadState); prefetchContentTracker = new LiveVariable<>(contentDownloadState); } public static enum DownloadState { /** * Downloading currently under way */ IS_DOWNLOADING, /** * Active downloading session has just completed succesfully. For example, this state may be used to trigger * refresh of content in views. */ DOWNLOADING_COMPLETED, /** * Active downloading sessions has just completed with an exception. An explanation of the error may be retrieved * by calling the 'getException' method */ DOWNLOADING_FAILED, /** * A "Post-downloading" state, different than * {@link #DOWNLOADING_COMPLETED}, that indicates that all content has * already been downloaded before, and potentially no further action is * required on any participants that are listening for this state. */ DOWNLOADED, /** * No downloading has been initiated yet */ NOT_STARTED } }