/*
* The Sun Project JXTA(TM) Software License
*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* 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.content;
import net.jxta.content.*;
import net.jxta.document.Advertisement;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.ID;
import net.jxta.logging.Logging;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.ModuleSpecID;
import net.jxta.protocol.ContentShareAdvertisement;
import net.jxta.protocol.ModuleImplAdvertisement;
import net.jxta.protocol.ModuleSpecAdvertisement;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Reference implementation of the ContentService. This implementation
* manages the listener list, tracks active shares, and uses the Jar
* service provider interface to locate transfer provider implementations
* which will perform the real work.
*/
public class ContentServiceImpl implements ContentService {
/**
* Well known service spec identifier: reference implementation of the
* ContentService.
*/
public final static ModuleSpecID MODULE_SPEC_ID =
ModuleSpecID.create(URI.create(
"urn:jxta:uuid-DDC5CA55578E4AB99A0AA81D2DC6EF3F"
+ "3F7E9F18B5D84DD58D21CE9E37E19E6C06"));
/**
* Logger.
*/
private static final Logger LOG = Logger.getLogger(
ContentServiceImpl.class.getName());
/**
* List of all currently registered providers, used only for providing
* programmatic access to the API user (hence the use of the
* ContentProvider super-interface versus the SPI interface).
*/
private final List<ContentProvider> providers =
new CopyOnWriteArrayList<ContentProvider>();
/**
* List of providers which are started and ready for use.
*/
private final List<ContentProviderSPI> active =
new CopyOnWriteArrayList<ContentProviderSPI>();
/**
* List of our listeners.
*/
private final List<ContentProviderListener> listeners =
new CopyOnWriteArrayList<ContentProviderListener>();
/**
* Lifecycle manager responsible for getting provider instances
* into the correct operational state.
*/
private final ModuleLifecycleManager<ContentProviderSPI> manager =
new ModuleLifecycleManager<ContentProviderSPI>();
/**
* List of providers which are registered and waiting for the
* service to be initialized before being added to the lifecycle
* manager. After initialization, this list is nulled.
*/
private List<ContentProviderSPI> waitingForInit = locateProviders();
/**
* Implementation adv given to us via init().
*/
private ModuleImplAdvertisement implAdv = null;
/**
* Peer group given to us via init().
*/
private PeerGroup group;
/**
* Object to lock against when accessing member vars.
*/
private final Object lock = new Object();
/**
* Flag indicatin that this instancce has been initialized.
*/
private boolean initialized = false;
/**
* Flag indicating that this instance has been started.
*/
private volatile boolean started = false;
/**
* Default constructor.
*/
public ContentServiceImpl() {
// Track available providers indirectly via the lifecycle manager
manager.addModuleLifecycleListener(
new ModuleLifecycleListener() {
/**
* {@inheritDoc}
*/
public void unhandledPeerGroupException(
ModuleLifecycleTracker subject, PeerGroupException mlcx) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Uncaught exception", mlcx);
}
}
/**
* {@inheritDoc}
*/
public void moduleLifecycleStateUpdated(
ModuleLifecycleTracker subject,
ModuleLifecycleState newState) {
ContentProviderSPI provider =
(ContentProviderSPI) subject.getModule();
LOG.fine("Content provider lifecycle state update: "
+ provider + " --> " + newState);
if (newState == ModuleLifecycleState.STARTED) {
active.add(provider);
} else {
active.remove(provider);
}
}
});
}
//////////////////////////////////////////////////////////////////////////
// Module interface methods:
/**
* {@inheritDoc}
*/
public void init(
PeerGroup group, ID assignedID, Advertisement adv) {
List<ContentProviderSPI> toAdd;
synchronized(lock) {
if (initialized) {
return;
}
initialized = true;
this.group = group;
this.implAdv = (ModuleImplAdvertisement) adv;
toAdd = waitingForInit;
waitingForInit = null;
}
if (Logging.SHOW_CONFIG && LOG.isLoggable(Level.CONFIG)) {
StringBuilder configInfo = new StringBuilder();
configInfo.append("Configuring Content Service : ").append(assignedID);
configInfo.append( "\n\tImplementation :" );
if (implAdv != null) {
configInfo.append("\n\t\tModule Spec ID: ").append(implAdv.getModuleSpecID());
configInfo.append("\n\t\tImpl Description : ").append(implAdv.getDescription());
configInfo.append("\n\t\tImpl URI : ").append(implAdv.getUri());
configInfo.append("\n\t\tImpl Code : ").append(implAdv.getCode());
}
configInfo.append( "\n\tGroup Params :" );
configInfo.append("\n\t\tGroup : ").append(group.getPeerGroupName());
configInfo.append("\n\t\tGroup ID : ").append(group.getPeerGroupID());
configInfo.append("\n\t\tPeer ID : ").append(group.getPeerID());
configInfo.append( "\n\tProviders: ");
for (ContentProviderSPI provider : toAdd) {
configInfo.append("\n\t\tProvider: ").append(provider);
}
LOG.config( configInfo.toString() );
}
// Provider initialization
for (ContentProviderSPI provider : toAdd) {
addContentProvider(provider);
}
manager.init();
}
/**
* {@inheritDoc}
*/
public int startApp(String args[]) {
synchronized(lock) {
if (started) {
return START_OK;
}
started = true;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine( "Content Service started.");
}
return START_OK;
}
/**
* {@inheritDoc}
*/
public void stopApp() {
synchronized(lock) {
if (!started) {
return;
}
started = false;
}
manager.stop();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine( "Content Service stopped.");
}
}
//////////////////////////////////////////////////////////////////////////
// Service interface methods:
/**
* {@inheritDoc}
*/
public Advertisement getImplAdvertisement() {
synchronized(lock) {
return implAdv;
}
}
/**
* {@inheritDoc}
*/
public ContentService getInterface() {
return (ContentService) ModuleWrapperFactory.newWrapper(
new Class[] { ContentService.class },
this);
}
//////////////////////////////////////////////////////////////////////////
// ContentService interface methods:
/**
* {@inheritDoc}
*/
public void addContentProvider(ContentProviderSPI provider) {
boolean addToManager = false;
providers.add(provider);
synchronized(lock) {
if (initialized) {
// Add to manager and let the manager event add to list
addToManager = true;
} else {
// Add to pending list
waitingForInit.add(provider);
}
}
if (addToManager) {
// We try to be as correct and complete as possible here...
Advertisement adv = provider.getImplAdvertisement();
ID asgnID;
if (adv instanceof ModuleSpecAdvertisement) {
ModuleSpecAdvertisement specAdv =
(ModuleSpecAdvertisement) adv;
asgnID = specAdv.getModuleSpecID();
} else if (adv instanceof ModuleImplAdvertisement) {
ModuleImplAdvertisement mimpAdv =
(ModuleImplAdvertisement) adv;
asgnID = mimpAdv.getModuleSpecID();
} else {
asgnID = adv.getID();
}
manager.addModule(provider, group, asgnID, adv, null);
}
}
/**
* {@inheritDoc}
*/
public void removeContentProvider(ContentProvider provider) {
if (!(provider instanceof ContentProviderSPI)) {
/*
* Can't cast so we can't use. Note that the add/remove
* asymmetry is intentional since getContentProviders()
* returns the ContentProvider sub-interface to prevent
* user access to SPI methods.
*/
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.finer("Cannot remove provider which is not a full SPI: "
+ provider);
}
return;
}
providers.remove(provider);
ContentProviderSPI spi = (ContentProviderSPI) provider;
boolean removeFromManager = false;
synchronized(lock) {
if (initialized) {
// List is maintained via manager
removeFromManager = true;
} else {
// Remove from pending list
waitingForInit.remove(provider);
}
}
if (removeFromManager) {
manager.removeModule(spi, true);
}
}
/**
* {@inheritDoc}
*/
public List<ContentProvider> getContentProviders() {
return Collections.unmodifiableList(providers);
}
/**
* {@inheritDoc}
*/
public List<ContentProvider> getActiveContentProviders() {
checkStart();
/*
* NOTE mcumings 20061120: Note that this could also be implemented
* using Collections.unmodifiableList(), but having the returned list
* run the potential of effectively changing over time (it would be
* read-through) led me to select a full copy instead.
*/
List<ContentProvider> result =
new ArrayList<ContentProvider>(active);
return result;
}
//////////////////////////////////////////////////////////////////////////
// ContentProvider interface methods:
/**
* {@inheritDoc}
*/
public void addContentProviderListener(
ContentProviderListener listener) {
checkStart();
listeners.add(listener);
}
/**
* {@inheritDoc}
*/
public void removeContentProviderListener(
ContentProviderListener listener) {
checkStart();
listeners.remove(listener);
}
/**
* {@inheritDoc}
*/
public ContentTransfer retrieveContent(ContentID contentID) {
checkStart();
try {
return new TransferAggregator(this, active, contentID);
} catch (TransferException transx) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Returning null due to exception", transx);
}
return null;
}
}
/**
* {@inheritDoc}
*/
public ContentTransfer retrieveContent(ContentShareAdvertisement adv) {
checkStart();
try {
return new TransferAggregator(this, active, adv);
} catch (TransferException transx) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Returning null due to exception", transx);
}
return null;
}
}
/**
* {@inheritDoc}
*/
public List<ContentShare> shareContent(Content content) {
checkStart();
List<ContentShare> result = null;
List<ContentShare> subShares;
for (ContentProvider provider : active) {
try {
subShares = provider.shareContent(content);
if (subShares == null) {
continue;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Content with ID '" + content.getContentID() +
"' being shared by provider: " + provider);
}
if (result == null) {
result = new ArrayList<ContentShare>();
}
result.addAll(subShares);
} catch (UnsupportedOperationException uox) {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.finest("Ignoring provider which doesn't support "
+ "share operation: " + provider);
}
}
}
if (result != null) {
fireContentShared(result);
}
return result;
}
/**
* {@inheritDoc}
*/
public boolean unshareContent(ContentID contentID) {
checkStart();
boolean unshared = false;
for (ContentProvider provider : active) {
unshared |= provider.unshareContent(contentID);
}
if (unshared) {
fireContentUnshared(contentID);
}
return unshared;
}
/**
* {@inheritDoc}
*/
public void findContentShares(
int maxNum, ContentProviderListener listener) {
checkStart();
List<ContentProviderListener> findListeners =
new ArrayList<ContentProviderListener>();
findListeners.add(listener);
EventAggregator aggregator =
new EventAggregator(findListeners, active);
aggregator.dispatchFindRequest(maxNum);
}
//////////////////////////////////////////////////////////////////////////
// Private methods:
/**
* Check to see if the ContentService has been started. If so, make
* sure that the provider implementations are started. If not, throw
* an illegal state exception. This method should be used in places
* where providers are about to be used, allowing their intialization to
* be deferred until the time of use.
*/
private void checkStart() {
synchronized(lock) {
if (!started) {
throw(new IllegalStateException(
"Service instance has not yet been started"));
}
}
manager.start();
}
/**
* Notify listeners of a new Content share.
*
* @param shares list of shares for the Content
*/
private void fireContentShared(List<ContentShare> shares) {
ContentProviderEvent event = null;
for (ContentProviderListener listener : listeners) {
try {
if (event == null) {
event = new ContentProviderEvent.Builder(this, shares)
.build();
}
listener.contentShared(event);
} catch (Throwable thr) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Uncaught throwable from listener", thr);
}
}
}
}
/**
* Notify listeners of a new Content share.
*
* @param id Content ID
*/
private void fireContentUnshared(ContentID id) {
ContentProviderEvent event = null;
for (ContentProviderListener listener : listeners) {
try {
if (event == null) {
event = new ContentProviderEvent.Builder(this, id)
.build();
}
listener.contentUnshared(event);
} catch (Throwable thr) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Uncaught throwable from listener", thr);
}
}
}
}
/**
* Locate all implementations of the ContentServiceProviderSPI interface
* using the Jar service provider interface mechanism.
*
* @return list of content provider implementations
*/
private List<ContentProviderSPI> locateProviders() {
ContentProviderSPI provider;
List<ContentProviderSPI> result =
new CopyOnWriteArrayList<ContentProviderSPI>();
ClassLoader loader = getClass().getClassLoader();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Locating providers");
}
Enumeration resources;
try {
resources = loader.getResources(
"META-INF/services/" + ContentProviderSPI.class.getName());
} catch (IOException iox) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Unable to enumerate ContentProviders", iox);
}
// Early-out.
return result;
}
// Create a Set of all unique class names
Set<String> provClassNames = new HashSet<String>();
while (resources.hasMoreElements()) {
URL resURL = (URL) resources.nextElement();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(" Provider services resource: " + resURL);
}
try {
InputStreamReader inReader =
new InputStreamReader(resURL.openStream());
BufferedReader reader = new BufferedReader(inReader);
String str;
while ((str = reader.readLine()) != null) {
int idx = str.indexOf('#');
if (idx >= 0) {
str = str.substring(0, idx);
}
str = str.trim();
if (str.length() == 0) {
// Probably a commented line
continue;
}
provClassNames.add(str);
}
} catch (IOException iox) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Could not parse ContentProvider services from: "
+ resURL, iox);
}
}
}
// Now attempt to instantiate all the providers we've found
for (String str : provClassNames) {
try {
Class cl = loader.loadClass(str);
provider = (ContentProviderSPI) cl.newInstance();
result.add(provider);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Added provider: " + str);
}
} catch (ClassNotFoundException cnfx) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE,
"Could not load service provider", cnfx);
}
// Continue to next provider class name
} catch (InstantiationException instx) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE,
"Could not load service provider", instx);
}
// Continue to next provider class name
} catch (IllegalAccessException iaccx) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE,
"Could not load service provider", iaccx);
}
// Continue to next provider class name
}
}
return result;
}
}