/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.plugin.pc.content;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.content.ContentSource;
import org.rhq.core.domain.content.ContentSourceSyncResults;
import org.rhq.core.domain.content.ContentSourceType;
import org.rhq.core.domain.content.ContentSyncStatus;
import org.rhq.core.domain.content.Repo;
import org.rhq.core.domain.content.RepoSyncResults;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.util.progresswatch.ProgressWatcher;
import org.rhq.enterprise.server.auth.SubjectManagerLocal;
import org.rhq.enterprise.server.content.ContentSourceManagerLocal;
import org.rhq.enterprise.server.content.RepoManagerLocal;
import org.rhq.enterprise.server.content.metadata.ContentSourceMetadataManagerLocal;
import org.rhq.enterprise.server.plugin.pc.ServerPluginEnvironment;
import org.rhq.enterprise.server.plugin.pc.content.metadata.ContentSourcePluginMetadataManager;
import org.rhq.enterprise.server.plugin.pc.content.sync.AdvisorySourceSynchronizer;
import org.rhq.enterprise.server.plugin.pc.content.sync.DistributionSourceSynchronizer;
import org.rhq.enterprise.server.plugin.pc.content.sync.PackageSourceSynchronizer;
import org.rhq.enterprise.server.plugin.pc.content.sync.RepoSourceSynchronizer;
import org.rhq.enterprise.server.util.LookupUtil;
/**
* Responsible for managing {@link ContentProvider} implementations. These
* implementations come from the content plugins themselves.
*
* @author John Mazzitelli
* @author Jason Dobies
*/
public class ContentProviderManager {
private static final Log log = LogFactory.getLog(ContentProviderManager.class);
private ContentServerPluginManager pluginManager;
private Map<ContentSource, ContentProvider> adapters;
// This is used as a monitor lock to the synchronizeContentProvider method;
// it helps us avoid two content sources getting synchronized at the same
// time.
private final Object synchronizeContentSourceLock = new Object();
/**
* Asks that the adapter responsible for the given content source return a
* stream to the package bits for the package at the given location.
*
* @param contentSourceId
* the adapter for this content source will be used to stream the
* bits
* @param location
* where the adapter can find the package bits on the content
* source
* @return the stream to the package bits
* @throws Exception
* if the adapter failed to load the bits
*/
public InputStream loadPackageBits(int contentSourceId, String location) throws Exception {
ContentProvider adapter = getIsolatedContentProvider(contentSourceId);
PackageSource packageSource = (PackageSource) adapter;
InputStream inputStream = packageSource.getInputStream(location);
if (inputStream == null) {
throw new Exception("Adapter for content source [" + contentSourceId
+ "] failed to give us a stream to the package at location [" + location + "]");
}
return inputStream;
}
/**
* Asks that the adapter responsible for the given content source return a
* stream to the DistributionFile bits for the DistributionFile at the given
* location.
*
* @param contentSourceId
* the adapter for this content source will be used to stream the
* bits
* @param location
* where the adapter can find the DistributionFile bits on the
* source
* @return the stream to the DistributionFile bits
* @throws Exception
* if the adapter failed to load the bits
*/
public InputStream loadDistributionFileBits(int contentSourceId, String location) throws Exception {
ContentProvider adapter = getIsolatedContentProvider(contentSourceId);
DistributionSource distSource = (DistributionSource) adapter;
InputStream inputStream = distSource.getInputStream(location);
if (inputStream == null) {
throw new Exception("Adapter for content source [" + contentSourceId
+ "] failed to give us a stream to the distribution file at location [" + location + "]");
}
return inputStream;
}
/**
* Asks the provider responsible for the given content source to synchronize
* with its remote repository. This will not attempt to load any package
* bits - it only synchronizes the repos and package version information.
* Note that if a synchronization is already currently underway, this method
* will not do anything and will return <code>false</code> (in effect, will
* let the currently running synchronization continue; we try not to step on
* it). If this method does actually do a sync, <code>true</code> will be
* returned.
*
* @param contentSourceId
* identifies the database entry of the provider
* @return <code>true</code> if the synchronization completed;
* <code>false</code> if there was already a synchronization
* happening and this method aborted
* @throws Exception
*/
public boolean synchronizeContentProvider(int contentSourceId) throws Exception {
ContentSourceManagerLocal contentSourceManager = LookupUtil.getContentSourceManager();
ContentProvider provider = getIsolatedContentProvider(contentSourceId);
ContentSourceSyncResults results = null;
SubjectManagerLocal subjMgr = LookupUtil.getSubjectManager();
Subject overlord = subjMgr.getOverlord();
// append to this as we go along, building a status report
StringBuilder progress = new StringBuilder();
try {
ContentSource contentSource = contentSourceManager.getContentSource(overlord, contentSourceId);
if (contentSource == null) {
throw new Exception("Cannot sync a non-existing content source [" + contentSourceId + "]");
}
// This should not take very long so it should be OK to block other
// callers.
// We are avoiding the problem that would occur if we try to
// synchronize the same source
// at the same time. We could do it more cleverly by synchronizing
// on a per content source
// basis, but I don't see a need right now to make this more
// complicated.
// We can come back and revisit if we need more fine-grained
// locking.
synchronized (synchronizeContentSourceLock) {
progress.append(new Date()).append(": ");
progress.append("Start synchronization of content source [").append(contentSource.getName()).append(
"]\n");
progress.append(new Date()).append(": ");
progress.append("Getting currently known list of packages...\n");
results = new ContentSourceSyncResults(contentSource);
results.setResults(progress.toString());
results = contentSourceManager.persistContentSourceSyncResults(results);
}
if (results == null) {
// note that it technically is still possible to have concurrent
// syncs - if two
// threads running in two different servers (i.e. different VMs)
// both try to sync the
// same content source and both enter the
// persistContentSourceSyncResults method at
// the same time, you'll get two inprogress rows - this is so
// rare as to not care.
// Even if it does happen, it may still work, or
// one sync will get an error and rollback its tx and no harm
// will be done.
log.info("Content source [" + contentSource.getName()
+ "] is already being synchronized - this sync request will be ignored.");
return false;
}
RepoSourceSynchronizer repoSourceSynchronizer = new RepoSourceSynchronizer(contentSource, provider);
repoSourceSynchronizer.synchronizeCandidateRepos(progress);
results.setStatus(ContentSyncStatus.SUCCESS);
results.setResults(progress.toString());
} catch (Throwable t) {
if (results != null) {
// try to reload the results in case it was updated by the SLSB
// before the
// exception happened
ContentSourceSyncResults reloadedResults = contentSourceManager.getContentSourceSyncResults(results
.getId());
if (reloadedResults != null) {
results = reloadedResults;
if (results.getResults() != null) {
progress = new StringBuilder(results.getResults());
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(baos));
progress.append(new Date()).append(": ");
progress.append("SYNCHRONIZATION ERROR - STACK TRACE FOLLOWS:\n");
progress.append(baos.toString());
results.setResults(progress.toString());
results.setStatus(ContentSyncStatus.FAILURE);
// finally clause will merge this
}
throw new Exception("Failed to sync content source [" + contentSourceId + "]", t);
} finally {
if (results != null) {
results.setEndTime(System.currentTimeMillis());
contentSourceManager.mergeContentSourceSyncResults(results);
}
}
return true;
}
/**
* Asks each content source associated with the given repo to synchronize
* the following information for the given repo:
* <ul>
* <li>Package Metadata</li>
* <li>Package Bits</li>
* <li>Distribution Tree Metadata</li>
* <li>Distribution Tree Bits</li>
* </ul>
*
* @param repoId
* must indicate a valid repo in the database
* @return <code>true</code> if the synchronize took place;
* <code>false</code> if it did not (for instance, if there is
* already a sync taking place for this repo)
* @throws Exception
* if the data required to perform the sync is missing or
* invalid
*/
public boolean synchronizeRepo(int repoId) {
log.debug("synchronizeRepo() :: start");
RepoManagerLocal repoManager = LookupUtil.getRepoManagerLocal();
SubjectManagerLocal subjectManager = LookupUtil.getSubjectManager();
Subject overlord = subjectManager.getOverlord();
// Load the repo to sync
Repo repo = repoManager.getRepo(overlord, repoId);
if (repo == null) {
throw new IllegalArgumentException("Invalid repository with id [" + repoId + "] specified for sync.");
}
// results =
// contentSourceManager.persistContentSourceSyncResults(results);
StringBuilder progress = new StringBuilder();
progress.append(new Date()).append(": ");
progress.append("Start synchronization of Repository [").append(repo.getName()).append("]\n");
progress.append(new Date()).append(": ");
progress.append("Getting currently known list of content source packages...\n");
SyncTracker tracker = new SyncTracker(new RepoSyncResults(repo), new ProgressWatcher());
tracker.setResults(progress.toString());
tracker.setRepoSyncResults(repoManager.persistRepoSyncResults(tracker.getRepoSyncResults()));
log.debug("synchronizeRepo :: inProgress");
tracker = updatePercentComplete(tracker, repoManager);
if (tracker.getRepoSyncResults() == null) {
log.info("Repository [" + repo.getName()
+ "] is already currently being synchronized - this sync request will be ignored.");
return false;
}
boolean syncCancelled = false;
try {
ThreadUtil.checkInterrupted();
// Sync each of the content sources associated with the repo.
for (ContentSource source : repo.getContentSources()) {
try {
ContentProvider provider = getIsolatedContentProvider(source.getId());
SyncProgressWeight sw = provider.getSyncProgressWeight();
PackageSourceSynchronizer packageSourceSynchronizer = new PackageSourceSynchronizer(repo, source,
provider);
log.debug("synchronizeRepo :: synchronizePackageMetadata");
tracker = updateSyncStatus(tracker, ContentSyncStatus.PACKAGEMETADATA);
tracker = packageSourceSynchronizer.synchronizePackageMetadata(tracker);
} catch (Exception e) {
processSyncException(e, tracker, repo, source, repoManager);
}
}
ThreadUtil.checkInterrupted();
// Setup ProgressWatcher
for (ContentSource source : repo.getContentSources()) {
ContentProvider provider = getIsolatedContentProvider(source.getId());
SyncProgressWeight sw = provider.getSyncProgressWeight();
tracker.getProgressWatcher().addWork(sw.getPackageMetadataWeight());
tracker.addAdvisoryMetadataWork(provider);
tracker.getProgressWatcher().addWork(sw.getDistribtutionBitsWeight());
tracker.getProgressWatcher().addWork(sw.getDistribtutionMetadataWeight());
tracker.addPackageBitsWork(provider);
tracker.getProgressWatcher().finishWork(sw.getPackageMetadataWeight());
}
tracker = updatePercentComplete(tracker, repoManager);
// Sync package bits.
for (ContentSource source : repo.getContentSources()) {
// Don't let the entire sync fail if a single content source
// fails
try {
ContentProvider provider = getIsolatedContentProvider(source.getId());
if (provider instanceof PackageSource) {
PackageSourceSynchronizer packageSourceSyncer = new PackageSourceSynchronizer(repo, source,
provider);
log.debug("synchronizeRepo :: synchronizePackageBits");
tracker = updateSyncStatus(tracker, ContentSyncStatus.PACKAGEBITS);
tracker = packageSourceSyncer.synchronizePackageBits(tracker, provider);
tracker = updatePercentComplete(tracker, repoManager);
}
} catch (Exception e) {
processSyncException(e, tracker, repo, source, repoManager);
}
// Check for cancellation after each content source.
ThreadUtil.checkInterrupted();
}
// Sync distro metadata.
log.debug("synchronizeRepo :: synchronizeDistributionMetadata");
for (ContentSource source : repo.getContentSources()) {
try {
ContentProvider provider = getIsolatedContentProvider(source.getId());
if (provider instanceof DistributionSource) {
DistributionSourceSynchronizer distroSourceSyncer = new DistributionSourceSynchronizer(
repo, source, provider);
tracker = updateSyncStatus(tracker, ContentSyncStatus.DISTROMETADATA);
tracker = distroSourceSyncer.synchronizeDistributionMetadata(tracker);
tracker = updatePercentComplete(tracker, repoManager);
}
} catch (Exception e) {
processSyncException(e, tracker, repo, source, repoManager);
}
// Check for cancellation after each content source.
ThreadUtil.checkInterrupted();
}
// Sync distro bits.
log.debug("synchronizeRepo :: synchronizeDistributionBits");
for (ContentSource source : repo.getContentSources()) {
try {
ContentProvider provider = getIsolatedContentProvider(source.getId());
if (provider instanceof DistributionSource) {
DistributionSourceSynchronizer distroSourceSyncer = new DistributionSourceSynchronizer(
repo, source, provider);
tracker = updateSyncStatus(tracker, ContentSyncStatus.DISTROBITS);
tracker = distroSourceSyncer.synchronizeDistributionBits(tracker);
tracker = updatePercentComplete(tracker, repoManager);
}
ThreadUtil.checkInterrupted();
} catch (Exception e) {
processSyncException(e, tracker, repo, source, repoManager);
}
// Check for cancellation after each content source.
ThreadUtil.checkInterrupted();
}
// Sync advisory metadata.
log.debug("synchronizeRepo :: synchronizeAdvisoryMetadata");
for (ContentSource source : repo.getContentSources()) {
try {
ContentProvider provider = getIsolatedContentProvider(source.getId());
if (provider instanceof AdvisorySource) {
tracker = updateSyncStatus(tracker, ContentSyncStatus.ADVISORYMETADATA);
AdvisorySourceSynchronizer advisorySourceSyncer = new AdvisorySourceSynchronizer(repo, source,
provider);
tracker = advisorySourceSyncer.synchronizeAdvisoryMetadata(tracker);
tracker = updatePercentComplete(tracker, repoManager);
}
} catch (Exception e) {
processSyncException(e, tracker, repo, source, repoManager);
}
// Check for cancellation after each content source.
ThreadUtil.checkInterrupted();
}
// Update status to finished.
if (tracker.getRepoSyncResults().getStatus() != ContentSyncStatus.FAILURE) {
tracker.getRepoSyncResults().setStatus(ContentSyncStatus.SUCCESS);
}
tracker = updateSyncStatus(tracker, tracker.getRepoSyncResults().getStatus());
progress = new StringBuilder();
progress.append("\n");
progress.append(tracker.getRepoSyncResults().getResults());
progress.append("\n");
progress.append(new Date()).append(": ");
progress.append("Repository [").append(repo.getName()).append("] ");
progress.append("completed syncing with ");
progress.append((tracker.getRepoSyncResults().getStatus() == ContentSyncStatus.FAILURE) ? "one or more" : "no");
progress.append(" errors.\n");
tracker.setResults(progress.toString());
log.debug("synchronizeRepo :: " + tracker.getRepoSyncResults().getStatus());
} catch (InterruptedException e) {
RepoSyncResults recentResults = repoManager.getMostRecentSyncResults(overlord, repo.getId());
log.debug("Caught InterruptedException during sync of repo with id [" + repoId + "].");
progress.append("\n ** Cancelled syncing **");
tracker.setResults(progress.toString());
tracker.getProgressWatcher().resetToZero();
tracker = updatePercentComplete(tracker, repoManager);
try {
tracker = updateSyncStatus(tracker, ContentSyncStatus.CANCELLED);
} catch (InterruptedException e1) {
throw new RuntimeException("Unexpected InterruptedException", e1);
}
syncCancelled = true;
}
if (tracker.getRepoSyncResults() != null) {
// pw.stop();
tracker.getRepoSyncResults().setEndTime(System.currentTimeMillis());
// results.setPercentComplete(new
// Long(pw.getPercentComplete()));
repoManager.mergeRepoSyncResults(tracker.getRepoSyncResults());
log.debug("synchronizeRepo :: merging results.");
}
return !syncCancelled;
}
private SyncTracker updatePercentComplete(SyncTracker tracker, RepoManagerLocal repoManager) {
tracker.getRepoSyncResults().setPercentComplete(new Long(tracker.getProgressWatcher().getPercentComplete()));
tracker.setRepoSyncResults(repoManager.mergeRepoSyncResults(tracker.getRepoSyncResults()));
return tracker;
}
private SyncTracker processSyncException(Exception e, SyncTracker tracker, Repo repo, ContentSource source,
RepoManagerLocal repoManager) throws InterruptedException {
if (e instanceof InterruptedException) {
InterruptedException ie = (InterruptedException) e;
throw ie;
}
StringBuilder progress = new StringBuilder();
log.error("Error while synchronizing repo [" + repo + "] with content source [" + source
+ "]. Synchronization of the repo will continue for any other associated content sources.", e);
// Try to reload the results in case it was updated by the SLSB before the exception happened.
RepoSyncResults reloadedResults = repoManager.getRepoSyncResults(tracker.getRepoSyncResults().getId());
if (reloadedResults != null) {
tracker.setRepoSyncResults(reloadedResults);
if (tracker.getRepoSyncResults().getResults() != null) {
progress = new StringBuilder(tracker.getRepoSyncResults().getResults());
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(baos));
progress.append(new Date()).append(": ");
progress.append("SYNCHRONIZATION ERROR - STACK TRACE FOLLOWS:\n");
progress.append(baos.toString());
tracker.setResults(progress.toString());
tracker.setStatus(ContentSyncStatus.FAILURE);
return tracker;
}
private SyncTracker updateSyncStatus(SyncTracker tracker, ContentSyncStatus status)
throws InterruptedException {
RepoManagerLocal repoManager = LookupUtil.getRepoManagerLocal();
SubjectManagerLocal subjMgr = LookupUtil.getSubjectManager();
Subject overlord = subjMgr.getOverlord();
int repoId = tracker.getRepoId();
RepoSyncResults cancelCheck = repoManager.getMostRecentSyncResults(overlord, repoId);
if (cancelCheck.getStatus() == ContentSyncStatus.CANCELLING) {
throw new InterruptedException();
}
RepoSyncResults results = tracker.getRepoSyncResults();
results.setStatus(status);
results = repoManager.mergeRepoSyncResults(results);
tracker.setRepoSyncResults(results);
return tracker;
}
/**
* Tests the connection to the content source that has the given ID.
*
* @param contentSourceId the id of the content source to be tested
*
* @throws Exception if the test connection to the specified content source failed
*/
public void testConnection(int contentSourceId) throws Exception {
ContentProvider adapter = getIsolatedContentProvider(contentSourceId);
adapter.testConnection();
//noinspection UnnecessaryReturnStatement
return;
}
/**
* Returns a set of all content sources whose adapters are managed by this
* object.
*
* @return all content sources
*/
public Set<ContentSource> getAllContentSources() {
synchronized (this.adapters) {
return new HashSet<ContentSource>(this.adapters.keySet());
}
}
/**
* Call this method when a new content source is added to the system during
* runtime. This can also be used to restart an adapter that was previously
* {@link #shutdownAdapter(ContentSource) shutdown}.
*
* <p>
* If there is already an adapter currently started for the given content
* source, this returns silently.
* </p>
*
* @param contentSource
* the new content source that was added
* @throws InitializationException
* if the provider throws an error on its startup
*/
public void startAdapter(ContentSource contentSource) throws InitializationException {
synchronized (this.adapters) {
if (this.adapters.containsKey(contentSource)) {
return; // already exists, which means it was already
// initialized
}
}
instantiateAdapter(contentSource);
try {
log.info("Initializing content source adapter for [" + contentSource + "] of type ["
+ contentSource.getContentSourceType() + "]");
ContentProvider adapter = getIsolatedContentSourceAdapter(contentSource);
adapter.initialize(contentSource.getConfiguration());
} catch (Exception e) {
log.warn("Failed to initialize adapter for content source [" + contentSource.getName() + "]");
// The adapter is put in the adapter cache when it is instantiated.
// If it failed to start, remove it
// from the cache so we don't immediately return at the start of
// this method.
this.adapters.remove(contentSource);
throw new InitializationException(e);
}
}
/**
* Call this method when a content source is removed from the system during
* runtime or if you just want to shutdown the adapter for whatever reason.
* You can restart the adapter by calling
* {@link #startAdapter(ContentSource)}.
*
* <p>
* If there are no adapters currently started for the given content source,
* this returns silently.
* </p>
*
* @param contentSource
* the content source being deleted
*/
public void shutdownAdapter(ContentSource contentSource) {
synchronized (this.adapters) {
if (!this.adapters.containsKey(contentSource)) {
return; // doesn't exist, it was already shutdown
}
}
try {
log.info("Shutting down content source adapter for [" + contentSource + "] of type ["
+ contentSource.getContentSourceType() + "]");
ContentProvider adapter = getIsolatedContentSourceAdapter(contentSource);
adapter.shutdown();
} catch (Throwable t) {
log.warn("Failed to shutdown adapter for content source [" + contentSource.getName() + "]", t);
} finally {
this.adapters.remove(contentSource);
}
}
/**
* Convienence method that simply {@link #shutdownAdapter(ContentSource)
* shuts down the adapter} and then {@link #startAdapter(ContentSource)
* restarts it}. Call this when, for example, a content source's
* {@link ContentSource#getConfiguration() configuration} has changed.
*
* @param contentSource
* the content source whose adapter is to be restarted
* @throws Exception
* if there is an error asking the provider to shutdown or start
*/
public void restartAdapter(ContentSource contentSource) throws Exception {
shutdownAdapter(contentSource);
startAdapter(contentSource);
}
/**
* Given a ID to a content source, this returns the adapter that is
* responsible for communicating with that content source where that adapter
* object will ensure invocations on it are isolated to its plugin
* classloader.
*
* @param contentProviderId
* an ID to a {@link ContentSource}
* @return the adapter that is communicating with the content source,
* isolated to its classloader
* @throws RuntimeException
* if there is no content source with the given ID
*/
public ContentProvider getIsolatedContentProvider(int contentProviderId) throws RuntimeException {
synchronized (this.adapters) {
for (ContentSource contentSource : this.adapters.keySet()) {
if (contentSource.getId() == contentProviderId) {
return getIsolatedContentSourceAdapter(contentSource);
}
}
}
throw new RuntimeException("Content source ID [" + contentProviderId + "] doesn't exist; can't get adapter");
}
/**
* Tells this manager to initialize itself which will initialize all the
* adapters.
*
* <p>
* This is protected so only the plugin container and subclasses can use it.
* </p>
*
* @param pluginManager
* the plugin manager this object can use to obtain information
* from (like classloaders)
*/
protected void initialize(ContentServerPluginManager pluginManager) {
this.pluginManager = pluginManager;
ContentSourceMetadataManagerLocal metadataManager = LookupUtil.getContentSourceMetadataManager();
ContentSourceManagerLocal contentSourceManager = LookupUtil.getContentSourceManager();
// Our plugin manager should have parsed all descriptors and have our
// types for us.
// Let's register the types to make sure they are merged with the old
// existing types.
ContentSourcePluginMetadataManager pluginMetadataManager = this.pluginManager.getMetadataManager();
Set<ContentSourceType> allTypes = pluginMetadataManager.getAllContentSourceTypes();
metadataManager.registerTypes(allTypes);
// now let's instantiate all adapters for all known content sources
createInitialAdaptersMap();
PageControl pc = PageControl.getUnlimitedInstance();
Subject overlord = LookupUtil.getSubjectManager().getOverlord();
List<ContentSource> contentSources = contentSourceManager.getAllContentSources(overlord, pc);
// let's initalize all adapters for all content sources
if (contentSources != null) {
for (ContentSource contentSource : contentSources) {
try {
startAdapter(contentSource);
} catch (Exception e) {
log.warn("Failed to start adapator for content source [" + contentSource + "]");
}
}
}
}
/**
* Tells this manager to shutdown. This will effectively shutdown all of its
* adapters.
*
* <p>
* This is protected so only the plugin container and subclasses can use it.
* </p>
*/
protected void shutdown() {
HashMap<ContentSource, ContentProvider> adaptersCopy;
// shutdown all adapters to give them a chance to clean up after
// themselves
synchronized (this.adapters) {
adaptersCopy = new HashMap<ContentSource, ContentProvider>(this.adapters);
}
for (ContentSource contentSource : adaptersCopy.keySet()) {
shutdownAdapter(contentSource);
}
synchronized (this.adapters) {
this.adapters.clear();
}
}
/**
* This is protected so only the plugin container and subclasses can use it.
*/
protected void createInitialAdaptersMap() {
this.adapters = new HashMap<ContentSource, ContentProvider>();
}
/**
* This returns the adapter that is responsible for communicating with the
* given content source where that adaptor object will ensure invocations on
* it are isolated to its plugin classloader.
*
* @param contentSource
* the returned adapter communicates with this content source
* @return the adapter that is communicating with the content source,
* isolated to its classloader
* @throws RuntimeException
* if there is no content source adapter available
*/
protected ContentProvider getIsolatedContentSourceAdapter(ContentSource contentSource) throws RuntimeException {
ContentProvider adapter;
synchronized (this.adapters) {
adapter = this.adapters.get(contentSource);
}
if (adapter == null) {
throw new RuntimeException("There is no adapter for content source [" + contentSource + "]");
}
ServerPluginEnvironment env = this.pluginManager.getPluginEnvironment(contentSource.getContentSourceType());
if (env == null) {
throw new RuntimeException("There is no plugin env. for content source [" + contentSource + "]");
}
ClassLoader classLoader = env.getPluginClassLoader();
IsolatedInvocationHandler handler = new IsolatedInvocationHandler(adapter, classLoader);
List<Class<?>> ifacesList = new ArrayList<Class<?>>(1);
ifacesList.add(ContentProvider.class);
if (adapter instanceof RepoSource) {
ifacesList.add(RepoSource.class);
}
if (adapter instanceof PackageSource) {
ifacesList.add(PackageSource.class);
}
if (adapter instanceof DistributionSource) {
ifacesList.add(DistributionSource.class);
}
if (adapter instanceof AdvisorySource) {
ifacesList.add(AdvisorySource.class);
}
Class<?>[] ifaces = ifacesList.toArray(new Class<?>[ifacesList.size()]);
return (ContentProvider) Proxy.newProxyInstance(classLoader, ifaces, handler);
}
/**
* Creates a provider instance that will service the give
* {@link ContentSource}.
*
* @param contentSource
* the source that the adapter will connect to
* @return an adapter instance; will be <code>null</code> if failed to
* create adapter
*/
protected ContentProvider instantiateAdapter(ContentSource contentSource) {
ContentProvider adapter = null;
String apiClassName = "?";
String pluginName = "?";
try {
ContentSourceType type = contentSource.getContentSourceType();
apiClassName = type.getContentSourceApiClass();
pluginName = this.pluginManager.getMetadataManager().getPluginNameFromContentSourceType(type);
ServerPluginEnvironment pluginEnv = this.pluginManager.getPluginEnvironment(pluginName);
ClassLoader pluginClassloader = pluginEnv.getPluginClassLoader();
ClassLoader startingClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(pluginClassloader);
Class<?> apiClass = Class.forName(apiClassName, true, pluginClassloader);
if (ContentProvider.class.isAssignableFrom(apiClass)) {
adapter = (ContentProvider) apiClass.newInstance();
} else {
log.warn("The API class [" + apiClassName + "] does not implement ["
+ ContentProvider.class.getName() + "] in plugin [" + pluginName + "]");
}
} finally {
Thread.currentThread().setContextClassLoader(startingClassLoader);
}
} catch (Throwable t) {
log.warn("Failed to create the API class [" + apiClassName + "] for plugin [" + pluginName + "]", t);
}
if (adapter != null) {
synchronized (this.adapters) {
this.adapters.put(contentSource, adapter);
}
}
return adapter;
}
/**
* This will handle invocations to an object ensuring that the call is
* isolated to within the appropriate classloader.
*/
private class IsolatedInvocationHandler implements InvocationHandler {
private final Object instance;
private final ClassLoader classLoader;
public IsolatedInvocationHandler(Object obj, ClassLoader cl) {
instance = obj;
classLoader = cl;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ClassLoader startingClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classLoader);
return method.invoke(instance, args);
} finally { Thread.currentThread().setContextClassLoader(startingClassLoader);
}
}
}
}