You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by br...@apache.org on 2013/06/24 17:49:57 UTC

svn commit: r1496102 - in /ace/sandbox/bramk/org.apache.ace.cli: src/org/apache/ace/cli/deployment/ test/org/apache/ace/cli/deployment/

Author: bramk
Date: Mon Jun 24 15:49:56 2013
New Revision: 1496102

URL: http://svn.apache.org/r1496102
Log:
[sandbox] Added custom repo to work around IO issues

Added:
    ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/CustomLocalIndexRepo.java
Modified:
    ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/ContinuousDeployer.java
    ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/DeployerUtil.java
    ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/ContinuousDeployerTest.java

Modified: ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/ContinuousDeployer.java
URL: http://svn.apache.org/viewvc/ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/ContinuousDeployer.java?rev=1496102&r1=1496101&r2=1496102&view=diff
==============================================================================
--- ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/ContinuousDeployer.java (original)
+++ ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/ContinuousDeployer.java Mon Jun 24 15:49:56 2013
@@ -9,6 +9,7 @@ import static org.apache.ace.cli.deploym
 import static org.apache.ace.cli.deployment.DeployerUtil.getString;
 import static org.apache.ace.cli.deployment.DeployerUtil.getVersion;
 import static org.apache.ace.cli.deployment.DeployerUtil.isSameBaseVersion;
+import static org.apache.ace.cli.deployment.DeployerUtil.isSnapshotVersion;
 import static org.apache.ace.cli.deployment.DeployerUtil.jarsDiffer;
 
 import java.io.File;
@@ -35,7 +36,7 @@ public class ContinuousDeployer {
     }
 
     /**
-     * Deploys all resources from the development respsitory into the deployment repository.
+     * Deploys all resources from the development repository into the deployment repository.
      * 
      * @return
      * @throws Exception
@@ -43,7 +44,6 @@ public class ContinuousDeployer {
     public List<Resource> deployResources() throws Exception {
         List<Resource> resources = findResources(m_developmentRepo, "*", "*");
         for (Resource resource : resources) {
-            System.out.println("Deploying resource:  " + getString(resource));
             deployResource(resource);
         }
         return resources;
@@ -61,11 +61,9 @@ public class ContinuousDeployer {
         List<Resource> releaseResources = findResources(m_releaseRepo, getIdentity(resource), getVersion(resource).toString());
         boolean isReleased = releaseResources.size() > 0;
         if (isReleased) {
-            System.out.println("Deploying release:  " + getString(resource));
             deployReleasedResource(resource);
         }
         else {
-            System.out.println("Deploying snapshot:  " + getString(resource));
             deploySnapshotResource(resource);
         }
     }
@@ -144,7 +142,9 @@ public class ContinuousDeployer {
         List<Resource> resources = findResources(m_deploymentRepo, getIdentity(resource));
         Resource matchedResource = null;
         for (Resource candidateResource : resources) {
-            if (isSameBaseVersion(getVersion(candidateResource), base)
+            Version candidateVersion = getVersion(candidateResource);
+
+            if (isSnapshotVersion(candidateVersion) && isSameBaseVersion(getVersion(candidateResource), base)
                 && (matchedResource == null || getVersion(matchedResource).compareTo(getVersion(candidateResource)) < 0)) {
                 matchedResource = candidateResource;
             }

Modified: ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/DeployerUtil.java
URL: http://svn.apache.org/viewvc/ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/DeployerUtil.java?rev=1496102&r1=1496101&r2=1496102&view=diff
==============================================================================
--- ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/DeployerUtil.java (original)
+++ ace/sandbox/bramk/org.apache.ace.cli/src/org/apache/ace/cli/deployment/DeployerUtil.java Mon Jun 24 15:49:56 2013
@@ -287,6 +287,14 @@ public final class DeployerUtil {
         return left.getMajor() == right.getMajor() && left.getMinor() == right.getMinor() && left.getMicro() == right.getMicro();
     }
 
+    public static boolean isSnapshotVersion(Version version) {
+        if (version.getQualifier() == null || version.getQualifier().equals("")) {
+            return false;
+        }
+        Matcher qualifierMatcher = QUALIFIER_PATTERN.matcher(version.getQualifier());
+        return qualifierMatcher.matches();
+    }
+
     private static Map<String, Object> getNamespaceAttributes(Resource resource, String namespace) {
         List<Capability> caps = resource.getCapabilities(namespace);
         if (caps.isEmpty())

Modified: ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/ContinuousDeployerTest.java
URL: http://svn.apache.org/viewvc/ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/ContinuousDeployerTest.java?rev=1496102&r1=1496101&r2=1496102&view=diff
==============================================================================
--- ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/ContinuousDeployerTest.java (original)
+++ ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/ContinuousDeployerTest.java Mon Jun 24 15:49:56 2013
@@ -10,8 +10,6 @@ import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Map.Entry;
-import java.util.jar.JarFile;
 
 import junit.framework.TestCase;
 
@@ -20,14 +18,13 @@ import org.osgi.framework.Constants;
 import aQute.bnd.deployer.repository.LocalIndexedRepo;
 import aQute.bnd.osgi.Builder;
 import aQute.bnd.osgi.Jar;
-import aQute.bnd.service.Strategy;
 
 public class ContinuousDeployerTest extends TestCase {
 
     File m_workDir;
-    LocalIndexedRepo m_developmentRepo;
-    LocalIndexedRepo m_releaseRepo;
-    LocalIndexedRepo m_deploymentRepo;
+    CustomLocalIndexRepo m_developmentRepo;
+    CustomLocalIndexRepo m_releaseRepo;
+    CustomLocalIndexRepo m_deploymentRepo;
     ContinuousDeployer m_deployer;
 
     public void setUp() throws Exception {
@@ -57,8 +54,7 @@ public class ContinuousDeployerTest exte
     public void testInitialSnapshotDeployment() throws Exception {
 
         // an initial unreleased bundle from development
-        deployBundle(m_developmentRepo, createBundle("foo.bar", "1.0.0"));
-        Thread.sleep(100);
+        deployBundle(m_developmentRepo, createBundle("foo.bar", "1.0.0", Constants.BUNDLE_COPYRIGHT, "Bundle 0"));
 
         // should have receive the correct snapshot version
         m_deployer.deployResources();
@@ -69,41 +65,63 @@ public class ContinuousDeployerTest exte
         assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "0.0.0.CDS000").size());
 
         // re-deploy of changed bundle should increment snapshot
+        deployBundle(m_developmentRepo, createBundle("foo.bar", "1.0.0", Constants.BUNDLE_COPYRIGHT, "Bundle 1"));
+        m_deployer.deployResources();
+        assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "0.0.0.CDS001").size());
+    }
 
-        // FIXME bug start
-
-        // create a new bundle and list manifest
-        File update = createBundle("foo.bar", "1.0.0", Constants.BUNDLE_COPYRIGHT, "Bundle 1", "bla", "bla");
-        listManifest(update);
-
-        // deploy, retrieve and list manifest
-        deployBundle(m_developmentRepo, update);
-        File check = m_developmentRepo.get("foo.bar", "1.0.0", Strategy.EXACT, null);
-        listManifest(check);
+//    FIXME fails
+//    
+//    public void testSnapshotTakesCorrectBaseVersion() throws Exception {
+//
+//        // an initial unreleased bundle from development
+//        deployBundle(m_developmentRepo, createBundle("foo.bar", "1.0.0", Constants.BUNDLE_COPYRIGHT, "Bundle 1.0"));
+//        deployBundle(m_developmentRepo, createBundle("foo.bar", "2.0.0", Constants.BUNDLE_COPYRIGHT, "Bundle 2.0"));
+//        copyResources(m_developmentRepo, m_releaseRepo, "foo.bar");
+//        m_deployer.deployResources();
+//
+//        deployBundle(m_developmentRepo, createBundle("foo.bar", "1.0.1", Constants.BUNDLE_COPYRIGHT, "Bundle 1.0.1"));
+//        m_deployer.deployResources();
+//
+//        // should have receive the correct snapshot version
+//        assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "1.0.0.CDS000").size());
+//
+//        deployBundle(m_developmentRepo, createBundle("foo.bar", "2.1.0", Constants.BUNDLE_COPYRIGHT, "Bundle 2.1.0"));
+//        m_deployer.deployResources();
+//
+//        // should have receive the correct snapshot version
+//        assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "2.0.0.CDS000").size());
+//    }
 
-        // Why do these manifest differ??? Unless in debug... there is a race condition in the BND IO?
+    public void testInitialSnapshotThenReleaseDeployment() throws Exception {
 
-        // FIXME bug end
+        // an initial unreleased bundle from development
+        deployBundle(m_developmentRepo, createBundle("foo.bar", "1.0.0", Constants.BUNDLE_NAME, "Snapshot bundle"));
 
+        // should have receive the correct snapshot version
         m_deployer.deployResources();
+        assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "0.0.0.CDS000").size());
 
-        // assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "0.0.0.CDS000").size());
-        assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "0.0.0.CDS001").size());
+        // release of changed bundle
+        deployBundle(m_developmentRepo, createBundle("foo.bar", "1.0.0", Constants.BUNDLE_NAME, "Release 1"));
+        copyResources(m_developmentRepo, m_releaseRepo, "foo.bar", "1.0.0");
+        m_deployer.deployResources();
+        assertEquals(1, findResources(m_deploymentRepo, "foo.bar", "1.0.0").size());
     }
 
-    private LocalIndexedRepo createRepo(File root, String name) throws Exception {
+    private CustomLocalIndexRepo createRepo(File root, String name) throws Exception {
         if (!(root.exists() || root.mkdir()) || !root.isDirectory())
             throw new Exception("Can not access root: " + root.getAbsolutePath());
         Map<String, String> props = new HashMap<String, String>();
         props.put(LocalIndexedRepo.PROP_LOCAL_DIR, root.getAbsolutePath());
         props.put(LocalIndexedRepo.PROP_PRETTY, "true");
         props.put(LocalIndexedRepo.PROP_OVERWRITE, "true");
-        LocalIndexedRepo repo = new LocalIndexedRepo();
+        CustomLocalIndexRepo repo = new CustomLocalIndexRepo();
         repo.setProperties(props);
         return repo;
     }
 
-    private void deployBundle(LocalIndexedRepo repo, InputStream stream) throws Exception {
+    private void deployBundle(CustomLocalIndexRepo repo, InputStream stream) throws Exception {
         try {
             repo.put(stream, null);
             stream.close();
@@ -113,22 +131,22 @@ public class ContinuousDeployerTest exte
         }
     }
 
-    private void deployBundle(LocalIndexedRepo repo, File file) throws Exception {
+    private void deployBundle(CustomLocalIndexRepo repo, File file) throws Exception {
         deployBundle(repo, new FileInputStream(file));
     }
 
-    private void listManifest(File file) throws Exception {
-        JarFile j = new JarFile(file);
-        System.out.println("--------------------------");
-        System.out.println("Manifest for " + file.getAbsolutePath());
-        System.out.println("--------------------------");
-        for (Entry<Object, Object> entry : j.getManifest().getMainAttributes().entrySet()) {
-            System.out.println(" " + entry.getKey() + ": " + entry.getValue());
-        }
-        j.close();
-        System.out.println("--------------------------");
-
-    }
+    // private void listManifest(File file) throws Exception {
+    // JarFile j = new JarFile(file);
+    // System.out.println("--------------------------");
+    // System.out.println("Manifest for " + file.getAbsolutePath());
+    // System.out.println("--------------------------");
+    // for (Entry<Object, Object> entry : j.getManifest().getMainAttributes().entrySet()) {
+    // System.out.println(" " + entry.getKey() + ": " + entry.getValue());
+    // }
+    // j.close();
+    // System.out.println("--------------------------");
+    //
+    // }
 
     private InputStream generateBundle(final String bsn, final String version, final String... headers) throws Exception {
         final PipedInputStream in = new PipedInputStream();

Added: ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/CustomLocalIndexRepo.java
URL: http://svn.apache.org/viewvc/ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/CustomLocalIndexRepo.java?rev=1496102&view=auto
==============================================================================
--- ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/CustomLocalIndexRepo.java (added)
+++ ace/sandbox/bramk/org.apache.ace.cli/test/org/apache/ace/cli/deployment/CustomLocalIndexRepo.java Mon Jun 24 15:49:56 2013
@@ -0,0 +1,435 @@
+package org.apache.ace.cli.deployment;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.coordinator.Participant;
+import org.osgi.service.log.LogService;
+
+import aQute.bnd.deployer.repository.FixedIndexedRepo;
+import aQute.bnd.deployer.repository.api.IRepositoryContentProvider;
+import aQute.bnd.filerepo.FileRepo;
+import aQute.bnd.osgi.Jar;
+import aQute.bnd.osgi.Verifier;
+import aQute.bnd.service.Refreshable;
+import aQute.bnd.service.RepositoryListenerPlugin;
+import aQute.bnd.version.Version;
+import aQute.bnd.version.VersionRange;
+import aQute.lib.io.IO;
+
+/**
+ * Copy from bnd LocalIndexedRepo to work around some IO issues. Also note that it does not keep qualifier versions. A
+ * version 1.0.0.X will overwrite version 1.0.0 :(
+ * 
+ */
+public class CustomLocalIndexRepo extends FixedIndexedRepo implements Refreshable, Participant {
+
+    private static final String CACHE_PATH = ".cache";
+    public static final String PROP_LOCAL_DIR = "local";
+    public static final String PROP_READONLY = "readonly";
+    public static final String PROP_PRETTY = "pretty";
+    public static final String PROP_OVERWRITE = "overwrite";
+
+    private static final VersionRange RANGE_ANY = new VersionRange(Version.LOWEST.toString());
+
+    private FileRepo storageRepo;
+    private boolean readOnly;
+    private boolean pretty = false;
+    private boolean overwrite = true;
+    private File storageDir;
+
+    // @GuardedBy("newFilesInCoordination")
+    private final List<URI> newFilesInCoordination = new LinkedList<URI>();
+
+    @Override
+    public synchronized void setProperties(Map<String, String> map) {
+        super.setProperties(map);
+
+        // Load essential properties
+        String localDirPath = map.get(PROP_LOCAL_DIR);
+        if (localDirPath == null)
+            throw new IllegalArgumentException(String.format("Attribute '%s' must be set on %s plugin.",
+                PROP_LOCAL_DIR, getClass().getName()));
+
+        storageDir = new File(localDirPath);
+        if (storageDir.exists() && !storageDir.isDirectory())
+            throw new IllegalArgumentException(String.format("Local path '%s' exists and is not a directory.",
+                localDirPath));
+
+        readOnly = Boolean.parseBoolean(map.get(PROP_READONLY));
+        pretty = Boolean.parseBoolean(map.get(PROP_PRETTY));
+        overwrite = map.get(PROP_OVERWRITE) == null ? true : Boolean.parseBoolean(map.get(PROP_OVERWRITE));
+
+        // Configure the storage repository
+        storageRepo = new FileRepo(storageDir);
+
+        // Set the local index and cache directory locations
+        cacheDir = new File(storageDir, CACHE_PATH);
+        if (cacheDir.exists() && !cacheDir.isDirectory())
+            throw new IllegalArgumentException(String.format(
+                "Cannot create repository cache: '%s' already exists but is not directory.",
+                cacheDir.getAbsolutePath()));
+    }
+
+    @Override
+    protected synchronized List<URI> loadIndexes() throws Exception {
+        Collection<URI> remotes = super.loadIndexes();
+        List<URI> indexes = new ArrayList<URI>(remotes.size() + generatingProviders.size());
+
+        for (IRepositoryContentProvider contentProvider : generatingProviders) {
+            File indexFile = getIndexFile(contentProvider);
+            try {
+                if (indexFile.exists()) {
+                    indexes.add(indexFile.toURI());
+                }
+                else {
+                    if (contentProvider.supportsGeneration()) {
+                        generateIndex(indexFile, contentProvider);
+                        indexes.add(indexFile.toURI());
+                    }
+                }
+            }
+            catch (Exception e) {
+                logService.log(LogService.LOG_ERROR, String.format(
+                    "Unable to load/generate index file '%s' for repository type %s", indexFile,
+                    contentProvider.getName()), e);
+            }
+        }
+
+        indexes.addAll(remotes);
+        return indexes;
+    }
+
+    private File getIndexFile(IRepositoryContentProvider contentProvider) {
+        String indexFileName = contentProvider.getDefaultIndexName(pretty);
+        File indexFile = new File(storageDir, indexFileName);
+        return indexFile;
+    }
+
+    private synchronized void regenerateAllIndexes() {
+        for (IRepositoryContentProvider provider : generatingProviders) {
+            if (!provider.supportsGeneration()) {
+                logService.log(LogService.LOG_WARNING,
+                    String.format("Repository type '%s' does not support index generation.", provider.getName()));
+                continue;
+            }
+            File indexFile = getIndexFile(provider);
+            try {
+                generateIndex(indexFile, provider);
+            }
+            catch (Exception e) {
+                logService.log(LogService.LOG_ERROR, String.format(
+                    "Unable to regenerate index file '%s' for repository type %s", indexFile, provider.getName()),
+                    e);
+            }
+        }
+    }
+
+    private synchronized void generateIndex(File indexFile, IRepositoryContentProvider provider) throws Exception {
+        if (indexFile.exists() && !indexFile.isFile())
+            throw new IllegalArgumentException(String.format(
+                "Cannot create file: '%s' already exists but is not a plain file.", indexFile.getAbsoluteFile()));
+
+        Set<File> allFiles = new HashSet<File>();
+        gatherFiles(allFiles);
+
+        FileOutputStream out = null;
+        try {
+            if (!storageDir.exists() && !storageDir.mkdirs()) {
+                throw new IOException("Could not create directory " + storageDir);
+            }
+            out = new FileOutputStream(indexFile);
+
+            URI rootUri = storageDir.getCanonicalFile().toURI();
+            provider.generateIndex(allFiles, out, this.getName(), rootUri, pretty, registry, logService);
+        }
+        finally {
+            IO.close(out);
+        }
+    }
+
+    private void gatherFiles(Set<File> allFiles) throws Exception {
+        if (!storageDir.isDirectory())
+            return;
+
+        List<String> bsns = storageRepo.list(null);
+        if (bsns != null)
+            for (String bsn : bsns) {
+                File[] files = storageRepo.get(bsn, RANGE_ANY);
+                if (files != null)
+                    for (File file : files) {
+                        allFiles.add(file.getCanonicalFile());
+                    }
+            }
+    }
+
+    @Override
+    public boolean canWrite() {
+        return !readOnly;
+    }
+
+    private synchronized void finishPut() throws Exception {
+        reset();
+        regenerateAllIndexes();
+
+        List<URI> clone = new ArrayList<URI>(newFilesInCoordination);
+        synchronized (newFilesInCoordination) {
+            newFilesInCoordination.clear();
+        }
+        for (URI entry : clone) {
+            File file = new File(entry);
+            fireBundleAdded(file);
+        }
+    }
+
+    public synchronized void ended(Coordination coordination) throws Exception {
+        finishPut();
+    }
+
+    public void failed(Coordination coordination) throws Exception {
+        ArrayList<URI> clone;
+        synchronized (newFilesInCoordination) {
+            clone = new ArrayList<URI>(newFilesInCoordination);
+            newFilesInCoordination.clear();
+        }
+        for (URI entry : clone) {
+            try {
+                new File(entry).delete();
+            }
+            catch (Exception e) {
+                reporter.warning("Failed to remove repository entry %s on coordination rollback: %s", entry, e);
+            }
+        }
+    }
+
+    protected File putArtifact(File tmpFile) throws Exception {
+        assert (tmpFile != null);
+        assert (tmpFile.isFile());
+
+        init();
+
+        Jar jar = new Jar(tmpFile);
+        try {
+            String bsn = jar.getBsn();
+            if (bsn == null || !Verifier.isBsn(bsn))
+                throw new IllegalArgumentException("Jar does not have a Bundle-SymbolicName manifest header");
+
+            File dir = new File(storageDir, bsn);
+            if (dir.exists() && !dir.isDirectory())
+                throw new IllegalArgumentException("Path already exists but is not a directory: "
+                    + dir.getAbsolutePath());
+            if (!dir.exists() && !dir.mkdirs()) {
+                throw new IOException("Could not create directory " + dir);
+            }
+
+            String versionString = jar.getVersion();
+            if (versionString == null)
+                versionString = "0";
+            else if (!Verifier.isVersion(versionString))
+                throw new IllegalArgumentException("Invalid version " + versionString + " in file " + tmpFile);
+
+            Version version = Version.parseVersion(versionString);
+            String fName = bsn + "-" + version.getWithoutQualifier() + ".jar";
+            File file = new File(dir, fName);
+
+            // check overwrite policy
+            if (!overwrite && file.exists())
+                return null;
+
+            // An open jar on file will fail rename on windows
+            jar.close();
+
+            // listManifest(tmpFile);
+            // IO.rename(tmpFile, file);
+            IO.copy(tmpFile, file);
+            Thread.sleep(1000);
+            // IO.copy(tmpFile, file);
+            // listManifest(file);
+
+            synchronized (newFilesInCoordination) {
+                newFilesInCoordination.add(file.toURI());
+            }
+
+            Coordinator coordinator = (registry != null) ? registry.getPlugin(Coordinator.class) : null;
+            if (!(coordinator != null && coordinator.addParticipant(this))) {
+                finishPut();
+            }
+            return file;
+        }
+        finally {
+            jar.close();
+        }
+    }
+
+    // public static final long ONE_KB = 1024;
+    // public static final long ONE_MB = ONE_KB * ONE_KB;
+    // private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
+    //
+    // private void copyFile(final File srcFile, final File destFile) throws IOException {
+    //
+    // FileInputStream fis = null;
+    // FileOutputStream fos = null;
+    // FileChannel input = null;
+    // FileChannel output = null;
+    // try {
+    // fis = new FileInputStream(srcFile);
+    // fos = new FileOutputStream(destFile);
+    // input = fis.getChannel();
+    // output = fos.getChannel();
+    // final long size = input.size(); // TODO See IO-386
+    // long pos = 0;
+    // long count = 0;
+    // while (pos < size) {
+    // final long remain = size - pos;
+    // count = remain > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : remain;
+    // final long bytesCopied = output.transferFrom(input, pos, count);
+    // if (bytesCopied == 0) { // IO-385 - can happen if file is truncated after caching the size
+    // break; // ensure we don't loop forever
+    // }
+    // pos += bytesCopied;
+    // }
+    // } finally {
+    // output.close();
+    // fos.close();
+    // input.close();
+    // fis.close();
+    // }
+    //
+    // final long srcLen = srcFile.length(); // TODO See IO-386
+    // final long dstLen = destFile.length(); // TODO See IO-386
+    // if (srcLen != dstLen) {
+    // throw new IOException("Failed to copy full contents from '" +
+    // srcFile + "' to '" + destFile + "' Expected length: " + srcLen +" Actual: " + dstLen);
+    // }
+    // }
+    //
+    // private void listManifest(File file) throws Exception {
+    // JarFile j = new JarFile(file);
+    // System.out.println("--------------------------");
+    // System.out.println("Manifest for " + file.getAbsolutePath());
+    // System.out.println("--------------------------");
+    // for (Entry<Object, Object> entry : j.getManifest().getMainAttributes().entrySet()) {
+    // System.out.println(" " + entry.getKey() + ": " + entry.getValue());
+    // }
+    // j.close();
+    // System.out.println("--------------------------");
+    //
+    // }
+
+    /* NOTE: this is a straight copy of FileRepo.put */
+    @Override
+    public synchronized PutResult put(InputStream stream, PutOptions options) throws Exception {
+        /* determine if the put is allowed */
+        if (readOnly) {
+            throw new IOException("Repository is read-only");
+        }
+
+        if (options == null)
+            options = DEFAULTOPTIONS;
+
+        /* both parameters are required */
+        if (stream == null)
+            throw new IllegalArgumentException("No stream and/or options specified");
+
+        /* the root directory of the repository has to be a directory */
+        if (!storageDir.isDirectory()) {
+            throw new IOException("Repository directory " + storageDir + " is not a directory");
+        }
+
+        /*
+         * setup a new stream that encapsulates the stream and calculates (when needed) the digest
+         */
+        DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("SHA-1"));
+
+        File tmpFile = null;
+        try {
+            /*
+             * copy the artifact from the (new/digest) stream into a temporary file in the root directory of the
+             * repository
+             */
+            tmpFile = IO.createTempFile(storageDir, "put", ".bnd");
+            IO.copy(dis, tmpFile);
+
+            /* beforeGet the digest if available */
+            byte[] disDigest = dis.getMessageDigest().digest();
+
+            if (options.digest != null && !Arrays.equals(options.digest, disDigest))
+                throw new IOException("Retrieved artifact digest doesn't match specified digest");
+
+            /* put the artifact into the repository (from the temporary file) */
+            File file = putArtifact(tmpFile);
+
+            PutResult result = new PutResult();
+            if (file != null) {
+                result.digest = disDigest;
+                result.artifact = file.toURI();
+            }
+
+            return result;
+        }
+        finally {
+            if (tmpFile != null && tmpFile.exists()) {
+                IO.delete(tmpFile);
+            }
+        }
+    }
+
+    public boolean refresh() {
+        reset();
+        return true;
+    }
+
+    public synchronized File getRoot() {
+        return storageDir;
+    }
+
+    protected void fireBundleAdded(File file) {
+        if (registry == null)
+            return;
+        List<RepositoryListenerPlugin> listeners = registry.getPlugins(RepositoryListenerPlugin.class);
+        Jar jar = null;
+        for (RepositoryListenerPlugin listener : listeners) {
+            try {
+                if (jar == null)
+                    jar = new Jar(file);
+                listener.bundleAdded(this, jar, file);
+            }
+            catch (Exception e) {
+                if (reporter != null)
+                    reporter.warning("Repository listener threw an unexpected exception: %s", e);
+            }
+            finally {
+                if (jar != null)
+                    jar.close();
+            }
+        }
+    }
+
+    @Override
+    public synchronized String getLocation() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(storageDir.getAbsolutePath());
+
+        String otherPaths = super.getLocation();
+        if (otherPaths != null && otherPaths.length() > 0)
+            builder.append(", ").append(otherPaths);
+
+        return builder.toString();
+    }
+
+}