/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.loader;
import net.jxta.content.*;
import net.jxta.document.MimeMediaType;
import net.jxta.document.StructuredDocument;
import net.jxta.document.StructuredDocumentFactory;
import net.jxta.id.ID;
import net.jxta.id.IDFactory;
import net.jxta.impl.content.TransferAggregator;
import net.jxta.impl.peergroup.CompatibilityEquater;
import net.jxta.impl.peergroup.CompatibilityUtils;
import net.jxta.logging.Logging;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.JxtaLoader;
import net.jxta.platform.Module;
import net.jxta.platform.ModuleSpecID;
import net.jxta.protocol.ModuleImplAdvertisement;
import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class is the reference implementation of the JxtaLoader.
*/
public class RefJxtaLoader extends JxtaLoader {
/**
* Logger
*/
private final static transient Logger LOG =
Logger.getLogger(RefJxtaLoader.class.getName());
/**
* Maximum amount of time which will be allotted to the retrieval of
* remoge package Content items.
*/
private static final long MAX_XFER_TIME =
Long.getLong(RefJxtaLoader.class.getName() + ".maxTransferTime",
60 * 1000L);
/**
* The equator we will use to determine if compatibility statements are
* compatible with this JXTA implementation.
*/
private final CompatibilityEquater equator;
/**
* The PeerGroup we are loading modules for, or null if unknown.
*/
private final PeerGroup group;
/**
* <ul>
* <li>Keys are {@link net.jxta.platform.ModuleSpecID}.</li>
* <li>Values are {@link java.util.Map}.
* <ul>
* <li>Keys are {@link java.lang.String} Compatibility Statements serialized as XML UTF-8</li>
* <li>Values are {@link java.lang.Class}<? extends Module>.</li>
* </ul>
* </li>
* </ul>
*/
private final Map<ModuleSpecID, Map<String, Class<? extends Module>>> classes =
new HashMap<ModuleSpecID, Map<String, Class<? extends Module>>>();
/**
* Classes and ImplAdvs we have known. Weak Map so that classes can be GCed.
*/
private final Map<Class<? extends Module>, ModuleImplAdvertisement> implAdvs =
new WeakHashMap<Class<? extends Module>, ModuleImplAdvertisement>();
/**
* Construct a new loader for the specified URLS with the default parent
* loader and specified compatibility equator.
*
* @param urls The URLs from which to load classes and resources.
* @param equator The equator to use in comparing compatibility statements.
*/
public RefJxtaLoader(URL[] urls, CompatibilityEquater equator) {
this(urls, RefJxtaLoader.class.getClassLoader(), equator);
}
/**
* Construct a new loader for the specified URLS with the specified parent
* loader and specified compatibility equator.
*
* @param urls The URLs from which to load classes and resources.
* @param parent The parent class loader for delegation.
* @param equator The equator to use in comparing compatibility statements.
*/
public RefJxtaLoader(URL[] urls, ClassLoader parent, CompatibilityEquater equator) {
this(urls, parent, equator, null);
}
/**
* Construct a new loader for the specified URLS with the specified parent
* loader and specified compatibility equator and the specified peer
* group. The addition of the PeerGroup allows the loader to support
* loading of package URIs specified as ContentIDs.
*
* @param urls The URLs from which to load classes and resources.
* @param parent The parent class loader for delegation.
* @param equator The equator to use in comparing compatibility statements.
* @param pGroup The peer group which this loader is loading modules for
*/
public RefJxtaLoader(URL[] urls, ClassLoader parent, CompatibilityEquater equator, PeerGroup pGroup) {
super(urls, parent);
this.equator = equator;
group = pGroup;
}
///////////////////////////////////////////////////////////////////////////
// General ClassLoader extensions:
/**
* Loads a class, falling back to attempting to load the class from the
* URL provided, if available.
*
* @param name class name
* @param uri URI to class (e.g., jar URL, ContentID of jar)
* @return the Class
* @throws ClassNotFoundException if class not found
*/
protected Class loadClass(String name, URI uri) throws ClassNotFoundException {
return loadClass(name, uri, false);
}
/**
* Make a stub for a version that uses URL, so that code that load
* services can be written properly, even if it works only for classes
* that do not need download.
*
* @param name The class name.
* @param uri The location of the class.
* @param resolve If {@code true} then resolve the class.
* @return the class
* @throws ClassNotFoundException if class not found
*/
protected Class loadClass(String name, URI uri, boolean resolve) throws ClassNotFoundException {
try {
// First, make sure we don't already have it loaded/loadable
return loadClass(name, resolve);
} catch (ClassNotFoundException e) {
if (uri == null) {
// Nothing more we can do
throw(e);
}
/*
* If the package URI is a JXTA ContentID we will need to retrieve
* the Content and then redefine the URL we use later on, pointing
* the URL at the newly downloaded Content which we assume is a
* jar file.
*/
try {
ID id = IDFactory.fromURI(uri);
if (!(id instanceof ContentID)) {
throw(new ClassNotFoundException(
"Of all JXTA IDs, only ContentIDs are supported package URIs"));
}
URI contentURI = retrieveContent((ContentID) id);
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": Using Content URI: " + contentURI);
}
// Switch to the Content URI and fall through
uri = contentURI;
} catch (URISyntaxException urisx) {
// Not a JXTA ID. Fall through and try as URL.
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": Not a JXTA ID: " + uri);
}
}
/*
* Fall back to the standard jar loading mechanism. We just
* turn it into a URL and add the URL to what the URLClassLoader
* handles, then try loading the class again.
*/
try {
URL url = uri.toURL();
addURL(url);
return loadClass(name, resolve);
} catch (MalformedURLException mux) {
throw(new ClassNotFoundException(
"Could not load class from URL: " + uri));
}
}
}
/**
* {@inheritDoc}
*
* This implementation is equivalent to the default ClassLoader
* implementation with one exception - it attempts to load classes
* via the thread's context class loader if all the normal sources
* fail to load the requested class.
*/
@Override
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
/*
* First, we ask the super-class. This will consult the local
* classes already laoded, the parent loader (if set), and the
* system loader. Failing those it will call the local
* findClass(String) method.
*/
try {
return super.loadClass(name, resolve);
} catch (ClassNotFoundException cnfx) {
// Fall through
}
/*
* The only thing left to try is the context loader.
*/
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
Class newClass = contextLoader.loadClass(name);
if (resolve) {
resolveClass(newClass);
}
return newClass;
}
///////////////////////////////////////////////////////////////////////////
// JxtaLoader implementation:
/**
* {@inheritDoc}
*
* @throws ClassNotFoundException if module cannot be loaded or if the
* class which is loaded is not a Module implementation
*/
@Override
public synchronized Class<? extends Module> findClass(ModuleSpecID spec) throws ClassNotFoundException {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": findClass(MSID=" + spec + ")");
}
// search through already existing compats for something that works
// if found, return it
Class<? extends Module> result = searchCompats(spec);
if (result != null) {
return result;
}
// search for more compats via Jar SPI
// results contain MSID, Class name, and description
// if MSID matches, generate/discover MIA, then define the class.
locateModuleImplementations(spec);
// search through compats again.
// if found, return it
// throw CNFE
result = searchCompats(spec);
if (result != null) {
return result;
}
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + " No class found for MSID");
}
throw new ClassNotFoundException(spec.toString());
}
/**
* {@inheritDoc}
*
* @throws ClassNotFoundException if class cannot be found or if class
* is not a Module implementation
*/
@Override
public Class<? extends Module> loadClass(ModuleSpecID spec) throws ClassNotFoundException {
/*
* Here we replicate the logic of the standard
* ClassLoader.loadClass(String,boolean) method, but this time we
* do so for Modules. The only main difference is that we only
* defer to our parent loader (since it is the only JxtaLoader).
*/
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": loadClass(MSID=" + spec + ")");
}
// Try the parent JxtaLoader, if present
try {
ClassLoader parentLoader = getParent();
if (parentLoader instanceof JxtaLoader) {
JxtaLoader jxtaLoader = (JxtaLoader) parentLoader;
Class<? extends Module> result = jxtaLoader.loadClass(spec);
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": Parent found: " + result);
}
return result;
} else {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": No parent loader to try.");
}
}
} catch (ClassNotFoundException cnfx) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, hashHex() + ": Parent could not load MSID: " + spec);
}
// Fall through
}
// Now try locally
try {
Class found = findClass(spec);
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": Self loaded: " + found);
}
return verifyAndCast(found);
} catch (ClassNotFoundException cnfx) {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": Self loader threw: "
+ cnfx.getClass() + ": " + cnfx.getMessage());
}
throw(cnfx);
}
}
/**
* {@inheritDoc}
* @throws ClassFormatError if class cannot be found or if class is not
* a Module implementation
*/
@Override
public synchronized Class<? extends Module> defineClass(ModuleImplAdvertisement impl) throws ClassFormatError {
String asString = impl.getCompat().toString();
// See if we have any classes defined for this ModuleSpecID.
// Note that there may be multiple definitions with different compatibility statements.
Map<String, Class<? extends Module>> compats = classes.get(impl.getModuleSpecID());
if (null == compats) {
compats = new HashMap<String, Class<? extends Module>>();
classes.put(impl.getModuleSpecID(), compats);
}
// See if there is a class defined which matches the compatibility statement of the implAdv.
Class<? extends Module> loaded = compats.get(asString);
if (null == loaded) {
try {
URI uri = URI.create(impl.getUri());
Class<?> clazz = loadClass(impl.getCode(), uri, false);
loaded = verifyAndCast(clazz);
} catch (IllegalArgumentException iax) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, hashHex() + ": Caught exception", iax);
}
throw new ClassFormatError("Class '" + impl.getCode()
+ "' could not be loaded from : " + impl.getUri());
} catch (ClassNotFoundException failed) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, hashHex() + ": Caught exception", failed);
}
throw new ClassFormatError("Class '" + impl.getCode()
+ "' could not be loaded from : " + impl.getUri());
}
// Remember the class along with the matching compatibility statement.
compats.put(asString, loaded);
}
// Force update of impl advertisement. This is done because the class will frequently redefine itself.
implAdvs.put(loaded, impl);
return loaded;
}
/**
* {@inheritDoc}
*/
@Override
public ModuleImplAdvertisement findModuleImplAdvertisement(Class clazz) {
Class<? extends Module> modClass;
try {
modClass = verifyAndCast(clazz);
} catch (ClassNotFoundException cnfx) {
// Not a Module class
return null;
}
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": findModuleImplAdv(" + clazz + ")");
}
ClassLoader parentLoader = getParent();
if (parentLoader instanceof JxtaLoader) {
JxtaLoader jxtaLoader = (JxtaLoader) parentLoader;
ModuleImplAdvertisement result = jxtaLoader.findModuleImplAdvertisement(modClass);
if (result != null) {
return result;
}
}
ModuleImplAdvertisement result = implAdvs.get(modClass);
if (result == null) {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": MIA for class not found");
}
return null;
} else {
return result.clone();
}
}
/**
* {@inheritDoc}
*/
@Override
public ModuleImplAdvertisement findModuleImplAdvertisement(ModuleSpecID msid) {
Class<? extends Module> moduleClass;
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": findModuleImplAdvertisement(MSID=" + msid + ")");
}
try {
moduleClass = loadClass(msid);
} catch (ClassNotFoundException failed) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, hashHex() + ": Failed to find class for " + msid, failed);
}
return null;
}
return findModuleImplAdvertisement(moduleClass);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(super.toString());
result.append(" - Classes: ");
for (Map.Entry<ModuleSpecID, Map<String, Class<? extends Module>>> eachMCID : classes.entrySet()) {
ModuleSpecID mcid = eachMCID.getKey();
result.append("\n\t").append(mcid).append(" :");
for (Map.Entry<String, Class<? extends Module>> eachClass : eachMCID.getValue().entrySet()) {
result.append("\n\t\t").append(eachClass.getValue().toString());
}
}
return result.toString();
}
///////////////////////////////////////////////////////////////////////////
// Private methods:
/**
* Searches through the already discovered compatibility statements of
* module implementations looking for something already loaded which
* will work.
*
* @param msid ModuleSpecID to search for
* @return Class instance which (compatibly) implements the module spec,
* or null if no class is know about which satisfies the module spec
* compatibly
*/
private Class<? extends Module> searchCompats(ModuleSpecID msid) {
Map<String, Class<? extends Module>> compats = classes.get(msid);
if (null == compats) {
return null;
}
for (Map.Entry<String, Class<? extends Module>> anEntry : compats.entrySet()) {
String aCompat = anEntry.getKey();
StructuredDocument asDoc;
try {
asDoc = StructuredDocumentFactory.newStructuredDocument(
MimeMediaType.XMLUTF8, new StringReader(aCompat));
} catch (IOException iox) {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.log(Level.FINEST, hashHex() + ": Caught exception", iox);
}
continue;
}
if (equator.compatible(asDoc)) {
return anEntry.getValue();
}
}
return null;
}
/**
* Attempt to locate implementations of the specified ModuleSpecID.
* We'll use a process similar to the standard Jar SPI mechanism to
* perform the discovery. We do this in an on-demand basis to allow
* for natural dependency resolution between Modules.
*
* @param msid ModuleSpecID to search for
*/
private void locateModuleImplementations(ModuleSpecID msid) {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": discoverModuleImplementations(MSID=" + msid + ")");
}
List<ModuleImplAdvertisement> locatedAdvs = null;
try {
Enumeration<URL> allProviderLists =
getResources("META-INF/services/net.jxta.platform.Module");
for (URL providers : Collections.list(allProviderLists)) {
List<ModuleImplAdvertisement> located =
locateModuleImplementations(msid, providers);
if (located != null) {
if (locatedAdvs == null) {
locatedAdvs = new ArrayList<ModuleImplAdvertisement>();
}
locatedAdvs.addAll(located);
}
}
} catch (IOException ex) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Failed to locate provider lists", ex);
}
}
if (locatedAdvs == null) {
// Early out.
return;
}
for (ModuleImplAdvertisement mAdv : locatedAdvs) {
defineClass(mAdv);
}
}
/**
* Register instance classes given a URL to a file containing modules which
* must be found on the current class path. Each line of the file contains a
* module spec ID, the class name and the Module description. The fields are
* separated by whitespace. Comments are marked with a '#', the pound sign.
* Any text following # on any line in the file is ignored.
*
* @param specID ModuleSpecID that we are seeking implementations of
* @param providers URL to a resource containing a list of providers.
* @return list of discovered ModuleImplAdvertisements for the specified
* ModuleSpecID, or null if no results were found.
*/
private List<ModuleImplAdvertisement> locateModuleImplementations(
ModuleSpecID specID, URL providers) {
List<ModuleImplAdvertisement> result = null;
InputStream urlStream = null;
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest(hashHex() + ": discoverModuleImplementations(MSID="+ specID + ", URL=" + providers + ")");
}
try {
urlStream = providers.openStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(urlStream, "UTF-8"));
String provider;
while ((provider = reader.readLine()) != null) {
int comment = provider.indexOf('#');
if (comment != -1) {
provider = provider.substring(0, comment);
}
provider = provider.trim();
if (0 == provider.length()) {
continue;
}
try {
ModuleImplAdvertisement mAdv = null;
String[] parts = provider.split("\\s", 3);
if (parts.length == 1) {
// Standard Jar SPI format: Class name
mAdv = locateModuleImplAdvertisement(parts[0]);
} else if (parts.length == 3) {
// Current non-standard format: MSID, Class name, Description
ModuleSpecID msid = ModuleSpecID.create(URI.create(parts[0]));
String code = parts[1];
String description = parts[2];
if (!msid.equals(specID)) {
// Early-out here to prevent unnecessary work
continue;
}
mAdv = locateModuleImplAdvertisement(code);
if (mAdv == null) {
// Create one
mAdv = CompatibilityUtils.createModuleImplAdvertisement(msid, code, description);
}
} else {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, hashHex() + ": Failed to register \'" + provider + "\'");
}
}
if (mAdv != null) {
if (result == null) {
result = new ArrayList<ModuleImplAdvertisement>();
}
result.add(mAdv);
}
} catch (Exception allElse) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, hashHex() + ": Failed to register \'" + provider + "\'", allElse);
}
}
}
} catch (IOException ex) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, hashHex() + ": Failed to read provider list " + providers, ex);
}
} finally {
if (null != urlStream) {
try {
urlStream.close();
} catch (IOException ignored) {
}
}
}
return result;
}
/**
* Attempts to locate the ModuleImplAdvertisement of a module by
* the use of reflection.
*
* @param className class name to examine
* @return ModuleImplAdvertisement found by introspection, or null if
* the ModuleImplAdvertisement could not be discovered in this manner
*/
private ModuleImplAdvertisement locateModuleImplAdvertisement(String className) {
try {
Class<?> moduleClass = (Class<?>) Class.forName(className);
Class<? extends Module> modClass = verifyAndCast(moduleClass);
Method getImplAdvMethod = modClass.getMethod("getDefaultModuleImplAdvertisement");
return (ModuleImplAdvertisement) getImplAdvMethod.invoke(null);
} catch(Exception ex) {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.log(Level.FINEST, hashHex() + ": Could not introspect Module for MIA: " + className, ex);
}
}
return null;
}
/**
* Checks that a class is a Module. If not, it raises a an exception.
* If it is, it casts the generic class to the subtype.
*
* @param clazz generic class to verify
* @return Module subtype class
* @throws ClassNotFoundException if class was not of the proper type
*/
private Class<? extends Module> verifyAndCast(Class<?> clazz)
throws ClassNotFoundException {
try {
return clazz.asSubclass(Module.class);
} catch (ClassCastException ccx) {
throw(new ClassNotFoundException(
"Class found but was not a Module class: " + clazz));
}
}
/**
* Determines the location that we should use to store the retrieved
* Content. This location follows the current standards for Module
* information persistance in the store home.
*
* @param service ContentService instance
*/
private File getContentFile(ContentID forContentID) {
ModuleSpecID groupMSID =
group.getPeerGroupAdvertisement().getModuleSpecID();
URI storeHomeURI = group.getStoreHome();
File storeHome = new File(storeHomeURI);
File grpHome = new File(storeHome,
group.getPeerGroupID().getUniqueValue().toString());
File modHome = new File(grpHome, groupMSID.getUniqueValue().toString());
File svcHome = new File(modHome, getClass().getSimpleName());
if (!svcHome.isDirectory() && !svcHome.mkdirs()) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, hashHex()
+ ": Could not create Content dir: " + svcHome);
}
}
File result =
new File(svcHome, forContentID.getUniqueValue().toString());
return result;
}
/**
* Attempts to retrieve the remote Content which is assumed to be a
* jar. The Content will be stored in the PeerGroup's store home, ensuring
* that it's our own private copy. If the Content cannot be retrieved in
* a reasonable amount of time this method will throw a ClassNotFound
* exception and attempt to carry on.
*
* @param contentID ID of the content to retrieve
* @return URI to the local Content, once it has been retrieved. Never
* returns {@code null}.
* @throws ClassNotFoundException if the Content cannot be retrieved in a
* reasonable amount of time
*/
private URI retrieveContent(ContentID contentID)
throws ClassNotFoundException {
if (group == null) {
throw(new ClassNotFoundException(
"Loading of ContentID is only possible when JxtaLoader "
+ "is constructed with a PeerGroup reference"));
}
// Get the storage location for this Content
File file = getContentFile(contentID);
if (file.exists()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(hashHex()
+ ": Using previously retrieved package Content: "
+ file);
}
return file.toURI();
}
// Obtain and check content service instances from local and parent
ContentService groupService = group.getContentService();
PeerGroup parentGroup = group.getParentGroup();
ContentService parentService;
if (parentGroup == null) {
parentService = null;
} else {
parentService = parentGroup.getContentService();
}
if (groupService == null && parentService == null) {
throw(new ClassNotFoundException(
"No ContentService instance found in either the local "
+ "group or the parent group"));
}
// Commence the transfer, creating an aggregation as necessary
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(hashHex()
+ ": Starting retrieval of Module package Content: "
+ contentID);
}
ContentTransfer xfer;
if (groupService == null) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.fine(hashHex() + ": Using only the parent ContentService");
}
xfer = parentService.retrieveContent(contentID);
} else if (parentService == null) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.fine(hashHex() + ": Using only the local ContentService");
}
xfer = groupService.retrieveContent(contentID);
} else {
/*
* We have both group and parent ContentServices. Aggregate them
* but prefer the local group over the parent. We use an
* aggregation to allow the ContentProviders in either of the
* groups to respond immediately if the Content is being shared
* by the local peer in either of the groups.
*/
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.fine(hashHex()
+ ": Using both the local and parent ContentService");
}
List<ContentTransfer> toAgg = new ArrayList<ContentTransfer>();
xfer = groupService.retrieveContent(contentID);
if (xfer != null) {
toAgg.add(xfer);
}
xfer = parentService.retrieveContent(contentID);
if (xfer != null) {
toAgg.add(xfer);
}
if (toAgg.size() == 0) {
throw(new ClassNotFoundException(
"No Content providers were able to load content ID: "
+ contentID));
} else if (toAgg.size() == 1) {
// No reason to use an aggregation.
xfer = toAgg.get(0);
} else {
// Use an aggregation
xfer = new TransferAggregator(null, toAgg);
}
}
attachDebugListeners(xfer);
Content content = null;
try {
xfer.startTransfer(file);
xfer.waitFor(MAX_XFER_TIME);
if (!xfer.getTransferState().isFinished()) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Transfer did not complete in "
+ "maximum allotted time");
}
xfer.cancel();
}
content = xfer.getContent();
} catch (InterruptedException intx) {
xfer.cancel();
file.delete();
throw(new ClassNotFoundException(
"Thread was interrupted during transfer attempt", intx));
} catch (TransferException xferx) {
xfer.cancel();
file.delete();
throw(new ClassNotFoundException(
"Package Content transfer failed", xferx));
}
// If the Content is not stored in the file, persist it
if (!file.exists()) {
// In-memory content. Persist it to disk.
try {
FileOutputStream fileOut = new FileOutputStream(file);
content.getDocument().sendToStream(fileOut);
fileOut.close();
} catch (IOException iox) {
file.delete();
throw(new ClassNotFoundException(
"Could not persist Content", iox));
}
}
return file.toURI();
}
/**
* Attaches listeners to the provider transfer for the purpose of
* debug logging.
*
* @param xfer transfer to attach listeners to
*/
private void attachDebugListeners(ContentTransfer xfer) {
if (!(Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST))) {
// Early out.
return;
}
final String prefix = hashHex();
final ContentTransferListener xListener = new ContentTransferListener() {
public void contentLocationStateUpdated(ContentTransferEvent event) {
LOG.finest(prefix + ": Received event: " + event);
}
public void contentTransferStateUpdated(ContentTransferEvent event) {
LOG.finest(prefix + ": Received event: " + event);
}
public void contentTransferProgress(ContentTransferEvent event) {
LOG.finest(prefix + ": Received event: " + event);
}
};
final ContentTransferAggregatorListener xaListener =
new ContentTransferAggregatorListener() {
public void selectedContentTransfer(ContentTransferAggregatorEvent event) {
LOG.finest(prefix + ": Received event: " + event);
}
public void updatedContentTransferList(ContentTransferAggregatorEvent event) {
LOG.finest(prefix + ": Received event: " + event);
}
};
LOG.finest(hashHex() + ": Attaching ContentTransferListener to: " + xfer);
xfer.addContentTransferListener(xListener);
if (xfer instanceof ContentTransferAggregator) {
ContentTransferAggregator xferAgg = (ContentTransferAggregator) xfer;
LOG.finest(hashHex() + ": Attaching ContentTransferAggregatorListener to: " + xfer);
xferAgg.addContentTransferAggregatorListener(xaListener);
// Recurse...
for (ContentTransfer child : xferAgg.getContentTransferList()) {
attachDebugListeners(child);
}
}
}
/**
* Returns the hashCode value in hex.
*
* @return hex hashCode value
*/
private String hashHex() {
return Integer.toString(hashCode(), 16);
}
}