/*
* Copyright (c) 2001 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 the
* Sun Microsystems, Inc. for Project JXTA."
* 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.
* ====================================================================
*
* 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 java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import net.jxta.content.Content;
import net.jxta.content.ContentID;
import net.jxta.content.ContentProvider;
import net.jxta.content.ContentProviderSPI;
import net.jxta.content.ContentService;
import net.jxta.content.ContentShare;
import net.jxta.content.ContentTransfer;
import net.jxta.content.TransferException;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.BinaryDocument;
import net.jxta.document.Document;
import net.jxta.document.MimeMediaType;
import net.jxta.document.StructuredDocumentFactory;
import net.jxta.document.XMLElement;
import net.jxta.id.ID;
import net.jxta.id.IDFactory;
import net.jxta.impl.loader.RefJxtaLoaderTest;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.NetworkConfigurator;
import net.jxta.platform.NetworkManager;
import net.jxta.platform.NetworkManager.ConfigMode;
import net.jxta.protocol.ContentShareAdvertisement;
import net.jxta.test.util.DelegateClassLoader;
import net.jxta.test.util.TempDir;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* General tests to apply to ContentProviders to verify compliance and
* functionality.
*/
public abstract class AbstractContentProviderTest {
private static Logger LOG =
Logger.getLogger(AbstractContentProviderTest.class.getName());
static TempDir home;
static NetworkManager netMan;
static PeerGroup pg;
static ContentService service;
static URL testJar;
private final ContentProviderSPI provider;
/**
* Due to the DelegateClassLoader usage, this class is loaded in a
* totally separate ClassLoader from where it is used. This helps work
* around having to use tons of reflection calls but we need to make
* sure that the info coming into and out of this class are in a
* ClassLoader-nuetral form (i.e., system classes only).
*/
public static class ContentSharer implements ContentSharerSPI {
NetworkManager nm;
ContentService service;
String targetProvClass;
public ContentSharer(final File tempDir, final String className)
throws Exception {
LOG.info("Constructing ContentSharer for class: " + className);
targetProvClass = className;
nm = new NetworkManager(
ConfigMode.EDGE, "TestNet", tempDir.toURI());
nm.setInstanceHome(tempDir.toURI());
nm.setUseDefaultSeeds(false);
NetworkConfigurator nc = nm.getConfigurator();
nc.setTcpStartPort(60000);
nc.setTcpEndPort(65535);
nc.setHttpEnabled(false);
nc.setUseMulticast(false);
nc.setRendezvousSeeds(Collections.singleton("tcp://127.0.0.1:9701"));
LOG.info("Created NM: " + nm + " (" + nm.getClass().getClassLoader() + ")");
}
public void init() {
try {
LOG.info("Initializing: " + this);
PeerGroup netPeerGroup = nm.startNetwork();
nm.waitForRendezvousConnection(15000);
LOG.info("Am RDV? " + netPeerGroup.isRendezvous());
LOG.info("Got RDV? " + netPeerGroup.getRendezVousService().isConnectedToRendezVous());
LOG.info("I'm in: " + netPeerGroup);
LOG.info("I am : " + netPeerGroup.getPeerID());
Enumeration<ID> rdvPeers =
netPeerGroup.getRendezVousService().getConnectedRendezVous();
while (rdvPeers.hasMoreElements()) {
LOG.info("RDV : " + rdvPeers.nextElement());
}
Thread.sleep(1000);
service = netPeerGroup.getContentService();
List<? extends ContentProvider> provs =
service.getContentProviders();
for (ContentProvider prov : provs) {
if (prov.getClass().getName().equals(targetProvClass)) {
LOG.info("Keeping provider: " + prov);
} else {
LOG.info("Removing untargeted provider: " + prov);
service.removeContentProvider(prov);
}
}
assertEquals(1, service.getContentProviders().size());
assertEquals(1, service.getActiveContentProviders().size());
} catch (Exception exc) {
// Rethrow as unchecked
throw(new RuntimeException(
"Could not init: " + exc.getMessage(), exc));
}
}
public byte[] share(URI id, byte[] data, String mimeType, boolean pub) {
try {
MimeMediaType mType = MimeMediaType.valueOf(mimeType);
BinaryDocument bDoc = new BinaryDocument(data, mType);
ContentID cID = (ContentID) IDFactory.fromURI(id);
Content content = new Content(cID, null, bDoc);
LOG.info("ContentSharer: Sharing content: " + content);
List<ContentShare> shares = service.shareContent(content);
assertNotNull(shares);
assertEquals(1, shares.size());
ContentShare share = shares.get(0);
ContentShareAdvertisement adv =
share.getContentShareAdvertisement();
if (pub) {
nm.getNetPeerGroup().getDiscoveryService().publish(adv);
}
LOG.info("ContentSharer created share adv:\n" + adv);
Document advDoc = adv.getDocument(MimeMediaType.XMLUTF8);
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
advDoc.sendToStream(byteOut);
return byteOut.toByteArray();
} catch (Exception exc) {
// Rethrow as unchecked
throw(new RuntimeException(
"Could not share: " + exc.getMessage(), exc));
}
}
public void destroy() {
LOG.info("Destroying: " + this);
nm.stopNetwork();
nm = null;
service = null;
}
}
/**
* Default constructor.
*
* @param spi implementation to test
*/
public AbstractContentProviderTest(ContentProviderSPI spi) {
provider = spi;
}
/**
* Starts a local JXTA instance in preparation for testing.
*
* @throws java.lang.Exception on error
*/
@BeforeClass
public static void setupClass() throws Exception {
LOG.info("============ Begin setupClass");
home = new TempDir();
netMan = new NetworkManager(NetworkManager.ConfigMode.SUPER, "test");
netMan.setInstanceHome(home.toURI());
netMan.setUseDefaultSeeds(false);
NetworkConfigurator nc = netMan.getConfigurator();
nc.setHttpEnabled(false);
nc.setUseMulticast(false);
nc.setTcpPort(9701);
pg = netMan.startNetwork();
LOG.info("Am RDV? " + pg.isRendezvous());
LOG.info("Got RDV? " + pg.getRendezVousService().isConnectedToRendezVous());
LOG.info("I'm in: " + pg);
LOG.info("I am : " + pg.getPeerID());
Enumeration<ID> rdvPeers =
pg.getRendezVousService().getConnectedRendezVous();
while (rdvPeers.hasMoreElements()) {
LOG.info("RDV : " + rdvPeers.nextElement());
}
testJar = RefJxtaLoaderTest.class.getResource("/TestJar.jar");
assertNotNull("TestJar could not be located", testJar);
service = pg.getContentService();
assertNotNull("ContentService not present in peer group", service);
LOG.info("============ End setupClass");
}
/**
* Tears down the local JXTA instance.
*
* @throws java.lang.InterruptedException on error
*/
@AfterClass
public static void tearDownClass() throws InterruptedException {
LOG.info("============ Begin tearDownClass");
home.delete();
netMan.stopNetwork();
LOG.info("============ End tearDownClass");
System.err.flush();
System.out.flush();
Thread.sleep(500);
}
/**
* Mark the beginning of a test.
*/
@Before
public void setup() {
LOG.info("============ Begin test");
}
/**
* Mark the end of a test.
*/
@After
public void tearDown() {
LOG.info("============ End test");
Thread.yield();
System.out.flush();
System.err.flush();
}
/**
* This test checks to see that the provider can successfully retrieve
* content if provided an unpublished ContentShareAdvertisement.
*
* @throws Throwable on test error
*/
@Test
public void retrieveByAdvertisement() throws Throwable {
ContentSharerSPI spi = createContentSharer();
try {
spi.init();
ContentID cID = IDFactory.newContentID(pg.getPeerGroupID(), true);
byte[] sharedData = new String("This is the test content: "
+ toString()).getBytes();
ContentShareAdvertisement shareAdv =
wrappedShare(spi, cID, sharedData, MimeMediaType.XMLUTF8, false);
LOG.finest("Share produced advertisement:\n" + shareAdv);
ContentTransfer xfer = service.retrieveContent(shareAdv);
File dest = new File(home, "received");
LOG.fine("Starting transfer");
xfer.startTransfer(dest);
try {
xfer.waitFor(30000);
if (!xfer.getTransferState().isFinished()) {
fail("Transfer timed out");
}
Content received = xfer.getContent();
LOG.fine("Received Content: " + received);
assertEqual(contentOf(cID, sharedData, MimeMediaType.XMLUTF8),
received);
} catch (TransferException xferx) {
LOG.log(Level.SEVERE, "Transfer failed");
fail("Caught transfer exception: " + xferx);
}
} catch (Throwable thr) {
LOG.log(Level.WARNING, "Caught throwable", thr);
throw(thr);
} finally {
spi.destroy();
}
}
/**
* This test checks to see that the provider can successfully retrieve
* content if provided the ContentID of a published ContentShare.
*
* @throws Throwable on test error
*/
@Test
public void retrieveByID() throws Throwable {
ContentSharerSPI spi = createContentSharer();
try {
spi.init();
ContentID cID = IDFactory.newContentID(pg.getPeerGroupID(), true);
byte[] sharedData = new String("This is the test content: "
+ toString()).getBytes();
ContentShareAdvertisement shareAdv =
wrappedShare(spi, cID, sharedData, MimeMediaType.XMLUTF8, true);
LOG.finest("Share produced advertisement:\n" + shareAdv);
ContentTransfer xfer = service.retrieveContent(cID);
File dest = new File(home, "received");
LOG.fine("Starting transfer");
xfer.startTransfer(dest);
try {
xfer.waitFor(30000);
if (!xfer.getTransferState().isFinished()) {
fail("Transfer timed out");
}
Content received = xfer.getContent();
LOG.fine("Received Content: " + received);
assertEqual(contentOf(cID, sharedData, MimeMediaType.XMLUTF8),
received);
} catch (TransferException xferx) {
LOG.log(Level.SEVERE, "Transfer failed");
fail("Caught transfer exception: " + xferx);
}
} catch (Throwable thr) {
LOG.log(Level.WARNING, "Caught throwable", thr);
throw(thr);
} finally {
spi.destroy();
}
}
///////////////////////////////////////////////////////////////////////////
// Private methods:
/**
* Creates another JXTA instance within this JVM which will serve Content
* when requested to do so. This additional instance is loaded within
* a separate ClassLoader structure and must be interacted with via
* standard java Classes and the specially handled ContentSharerSPI
* interface.
*
* @return content sharer instance
*/
private ContentSharerSPI createContentSharer() {
try {
DelegateClassLoader dcl = new DelegateClassLoader(
AbstractContentProviderTest.class.getClassLoader());
dcl.addClassRedefinePattern(Pattern.compile(".*"));
dcl.addClassNeverRedefinePattern(Pattern.compile("org.junit.*"));
dcl.addClassNeverRedefinePattern(Pattern.compile(
ContentSharerSPI.class.getName()));
Class fc = dcl.loadClass(ContentSharer.class.getName());
Constructor constructor = fc.getDeclaredConstructor(
File.class, String.class);
File sharerHome = new File(home, "sharerHome");
Object obj = constructor.newInstance(
sharerHome, provider.getClass().getName());
assertTrue(obj instanceof ContentSharerSPI);
return (ContentSharerSPI) obj;
} catch (Exception exc) {
// Rethrow as unchecked
throw(new RuntimeException("Caught exception", exc));
}
}
/**
* Requests a content sharer to share a content built from the specified
* components, then has it communicate the ContentShareAdvertisement of
* the newly shared Content in a form which can be understood from the
* calling ClassLoader context.
*
* @param spi sharer to have share the Content
* @param cID ContentID of the content to be shared
* @param data data of the Content to be shared
* @param mimeType MIME media type of the Content to be shared
* @return ContentShareAdvertisement of the shared Content
* @throws java.io.IOException on error
*/
private ContentShareAdvertisement wrappedShare(
ContentSharerSPI spi, ContentID cID, byte[] data,
MimeMediaType mimeType, boolean doPublish)
throws IOException {
byte[] advData = spi.share(cID.toURI(), data, mimeType.getMimeMediaType(), false);
BinaryDocument advDoc = new BinaryDocument(advData, MimeMediaType.XMLUTF8);
XMLElement elem = (XMLElement) StructuredDocumentFactory.newStructuredDocument(
advDoc.getMimeType(), advDoc.getStream());
ContentShareAdvertisement shareAdv = (ContentShareAdvertisement)
AdvertisementFactory.newAdvertisement(elem);
return shareAdv;
}
private Content contentOf(
ContentID cID, byte[] data, MimeMediaType mimeType) {
BinaryDocument bDoc = new BinaryDocument(data, mimeType);
return new Content(cID, null, bDoc);
}
/**
* Compares two Content documents for equality.
*/
private void assertEqual(Content expected, Content actual) throws IOException {
// Get the trivial cases out of the way
if (expected == null && actual == null) {
return;
} else if (expected == null || actual == null) {
// This will catch the failure
assertEqual(expected, actual);
} else if (expected == actual) {
return;
}
assertEquals(expected.getContentID(), actual.getContentID());
assertEquals(expected.getMetaID(), actual.getMetaID());
// Compare the documents
Document eDoc = expected.getDocument();
Document aDoc = actual.getDocument();
assertEquals(eDoc.getFileExtension(), aDoc.getFileExtension());
// The MIME type parameters are not transferred
assertEquals(eDoc.getMimeType().getMimeMediaType(),
aDoc.getMimeType().toString());
// Compare the data
ByteArrayOutputStream eOut = new ByteArrayOutputStream();
eDoc.sendToStream(eOut);
ByteArrayOutputStream aOut = new ByteArrayOutputStream();
aDoc.sendToStream(aOut);
byte[] eBytes = eOut.toByteArray();
byte[] aBytes = aOut.toByteArray();
assertEquals(new String(eBytes), new String(aBytes));
}
}