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/04/16 13:06:21 UTC

svn commit: r1468366 - in /ace/trunk: org.apache.ace.client.repository.itest/src/org/apache/ace/it/repositoryadmin/ org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ org.apache.ace.client.repository/src/org/apache/ace/c...

Author: bramk
Date: Tue Apr 16 11:06:20 2013
New Revision: 1468366

URL: http://svn.apache.org/r1468366
Log:
ACE-316 BundleFileStore now uses hierarchical layout based on BSN parts (splitted by dot)
ACE-317 Rest API POST now returns "201 - Created" with the actual location the resource

Modified:
    ace/trunk/org.apache.ace.client.repository.itest/src/org/apache/ace/it/repositoryadmin/TemplateProcessorTest.java
    ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java
    ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java
    ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/BundleServlet.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/BundleStore.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/BundleServletTest.java
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/MockBundleStore.java
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/BundleFileStoreTest.java

Modified: ace/trunk/org.apache.ace.client.repository.itest/src/org/apache/ace/it/repositoryadmin/TemplateProcessorTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository.itest/src/org/apache/ace/it/repositoryadmin/TemplateProcessorTest.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.client.repository.itest/src/org/apache/ace/it/repositoryadmin/TemplateProcessorTest.java (original)
+++ ace/trunk/org.apache.ace.client.repository.itest/src/org/apache/ace/it/repositoryadmin/TemplateProcessorTest.java Tue Apr 16 11:06:20 2013
@@ -233,7 +233,7 @@ public class TemplateProcessorTest exten
 
         String simpleTemplate = "<Attribute content=\"http://$context.name\"/>";
         String simpleTemplateProcessed = "<Attribute content=\"http://mydistribution\"/>";
-        File simpleTemplateFile = createFileWithContents("template", "xml", xmlHeader + simpleTemplate + xmlFooter);
+        File simpleTemplateFile = createFileWithContents("template", ".xml", xmlHeader + simpleTemplate + xmlFooter);
 
         // create some tree from artifacts to a target
         FeatureObject go = runAndWaitForEvent(new Callable<FeatureObject>() {

Modified: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java (original)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java Tue Apr 16 11:06:20 2013
@@ -19,20 +19,13 @@
 package org.apache.ace.client.repository.helper.base;
 
 import java.io.Closeable;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
-import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLConnection;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
 
 import org.apache.ace.client.repository.helper.ArtifactPreprocessor;
 import org.apache.ace.connectionfactory.ConnectionFactory;
@@ -47,7 +40,6 @@ public abstract class ArtifactPreprocess
     protected static final int BUFFER_SIZE = 64 * 1024;
 
     protected final ConnectionFactory m_connectionFactory;
-    private final ExecutorService m_executor;
 
     /**
      * Creates a new {@link ArtifactPreprocessorBase} instance.
@@ -57,7 +49,6 @@ public abstract class ArtifactPreprocess
      */
     protected ArtifactPreprocessorBase(ConnectionFactory connectionFactory) {
         m_connectionFactory = connectionFactory;
-        m_executor = Executors.newCachedThreadPool();
     }
 
     /**
@@ -93,42 +84,6 @@ public abstract class ArtifactPreprocess
     }
 
     /**
-     * Gets a stream to write an artifact to, which will be uploaded asynchronously to the OBR.
-     * 
-     * @param name
-     *            The name of the artifact.
-     * @param obrBase
-     *            The base URL of the obr to which this artifact should be written.
-     * @param inputStream
-     *            the input stream with data to upload.
-     */
-    protected final Future<URL> uploadAsynchronously(final String name, final URL obrBase, final InputStream inputStream) {
-        return m_executor.submit(new Callable<URL>() {
-            public URL call() throws IOException {
-                return upload(inputStream, name, obrBase);
-            }
-        });
-    }
-
-    /**
-     * Converts a given URL to a {@link File} object.
-     * 
-     * @param url
-     *            the URL to convert, cannot be <code>null</code>.
-     * @return a {@link File} object, never <code>null</code>.
-     */
-    protected final File urlToFile(URL url) {
-        File file;
-        try {
-            file = new File(url.toURI());
-        }
-        catch (URISyntaxException e) {
-            file = new File(url.getPath());
-        }
-        return file;
-    }
-
-    /**
      * Uploads an artifact synchronously to an OBR.
      * 
      * @param input
@@ -142,7 +97,7 @@ public abstract class ArtifactPreprocess
      *             If there was an error reading from <code>input</code>, or if there was a problem communicating with
      *             the OBR.
      */
-    private URL upload(InputStream input, String name, URL obrBase) throws IOException {
+    String upload(InputStream input, String name, String mimeType, URL obrBase) throws IOException {
         if (obrBase == null) {
             throw new IOException("There is no storage available for this artifact.");
         }
@@ -150,79 +105,23 @@ public abstract class ArtifactPreprocess
             throw new IllegalArgumentException("None of the parameters can be null.");
         }
 
-        URL url = null;
-        try {
-            url = determineNewUrl(name, obrBase);
-
-            if (!urlPointsToExistingFile(url)) {
-                if ("file".equals(url.getProtocol())) {
-                    uploadToFile(input, url);
-                }
-                else {
-                    uploadToRemote(input, url);
-                }
-            }
-        }
-        catch (IOException ioe) {
-            throw new IOException("Error uploading " + name + ": " + ioe.getMessage());
-        }
-        finally {
-            silentlyClose(input);
-        }
-
-        return url;
-    }
-
-    /**
-     * Uploads an artifact to a local file location.
-     * 
-     * @param input
-     *            the input stream of the (local) artifact to upload.
-     * @param url
-     *            the URL of the (file) artifact to upload to.
-     * @throws IOException
-     *             in case of I/O problems.
-     */
-    private void uploadToFile(InputStream input, URL url) throws IOException {
-        File file = urlToFile(url);
-
         OutputStream output = null;
-
+        String location = null;
         try {
-            output = new FileOutputStream(file);
-
-            byte[] buffer = new byte[BUFFER_SIZE];
-            for (int count = input.read(buffer); count != -1; count = input.read(buffer)) {
-                output.write(buffer, 0, count);
-            }
-        }
-        finally {
-            silentlyClose(output);
-        }
-    }
 
-    /**
-     * Uploads an artifact to a remote location.
-     * 
-     * @param input
-     *            the input stream of the (local) artifact to upload.
-     * @param url
-     *            the URL of the (remote) artifact to upload to.
-     * @throws IOException
-     *             in case of I/O problems, or when the upload was refused by the remote.
-     */
-    private void uploadToRemote(InputStream input, URL url) throws IOException {
-        OutputStream output = null;
-
-        try {
+            URL url = new URL(obrBase, "?filename=" + name);
             URLConnection connection = m_connectionFactory.createConnection(url);
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+            connection.setUseCaches(false);
+
+            connection.setRequestProperty("Content-Type", mimeType);
             if (connection instanceof HttpURLConnection) {
                 // ACE-294: enable streaming mode causing only small amounts of memory to be
                 // used for this commit. Otherwise, the entire input stream is cached into
                 // memory prior to sending it to the server...
                 ((HttpURLConnection) connection).setChunkedStreamingMode(8192);
             }
-            connection.setDoOutput(true);
 
             output = connection.getOutputStream();
 
@@ -235,7 +134,8 @@ public abstract class ArtifactPreprocess
             if (connection instanceof HttpURLConnection) {
                 int responseCode = ((HttpURLConnection) connection).getResponseCode();
                 switch (responseCode) {
-                    case HttpURLConnection.HTTP_OK:
+                    case HttpURLConnection.HTTP_CREATED:
+                        location = connection.getHeaderField("Location");
                         break;
                     case HttpURLConnection.HTTP_CONFLICT:
                         throw new IOException("Artifact already exists in storage.");
@@ -250,55 +150,6 @@ public abstract class ArtifactPreprocess
         finally {
             silentlyClose(output);
         }
-    }
-
-    /**
-     * Determines whether the given URL points to an existing file.
-     * 
-     * @param url
-     *            the URL to test, cannot be <code>null</code>.
-     * @return <code>true</code> if the given URL points to an existing file, <code>false</code> otherwise.
-     */
-    private boolean urlPointsToExistingFile(URL url) {
-        boolean result = false;
-
-        if ("file".equals(url.getProtocol())) {
-            result = urlToFile(url).exists();
-        }
-        else {
-            try {
-                URLConnection connection = m_connectionFactory.createConnection(url);
-
-                if (connection instanceof HttpURLConnection) {
-                    HttpURLConnection hc = (HttpURLConnection) connection;
-
-                    // Perform a HEAD on the file, to see whether it exists...
-                    hc.setRequestMethod("HEAD");
-                    try {
-                        int responseCode = hc.getResponseCode();
-                        result = (responseCode == HttpURLConnection.HTTP_OK);
-                    }
-                    finally {
-                        hc.disconnect();
-                    }
-                }
-                else {
-                    // In all other scenario's: try to read a single byte from the input
-                    // stream, if this succeeds, we can assume the file exists...
-                    InputStream is = connection.getInputStream();
-                    try {
-                        is.read();
-                    }
-                    finally {
-                        silentlyClose(is);
-                    }
-                }
-            }
-            catch (IOException e) {
-                // Ignore; assume file does not exist...
-            }
-        }
-
-        return result;
+        return location;
     }
 }

Modified: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java (original)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java Tue Apr 16 11:06:20 2013
@@ -33,8 +33,11 @@ import java.security.NoSuchAlgorithmExce
 import java.util.Arrays;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.ace.client.repository.helper.PropertyResolver;
+import org.apache.ace.client.repository.helper.configuration.ConfigurationHelper;
 import org.apache.ace.connectionfactory.ConnectionFactory;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.Velocity;
@@ -45,6 +48,9 @@ import org.apache.velocity.app.Velocity;
  */
 public class VelocityArtifactPreprocessor extends ArtifactPreprocessorBase {
 
+    // matches a valid OSGi version
+    private final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+)([\\.-]([\\w-]+))?)?)?");
+
     private static Object m_initLock = new Object();
     private static boolean m_velocityInitialized = false;
 
@@ -58,7 +64,6 @@ public class VelocityArtifactPreprocesso
      */
     public VelocityArtifactPreprocessor(ConnectionFactory connectionFactory) {
         super(connectionFactory);
-
         try {
             m_md5 = MessageDigest.getInstance("MD5");
         }
@@ -72,13 +77,11 @@ public class VelocityArtifactPreprocesso
 
     @Override
     public boolean needsNewVersion(String url, PropertyResolver props, String targetID, String fromVersion) {
-        // get the template
+
         byte[] input = null;
         byte[] result = null;
-        
         try {
             init();
-            
             input = getArtifactAsBytes(url);
             result = process(input, props);
         }
@@ -88,7 +91,6 @@ public class VelocityArtifactPreprocesso
             return true;
         }
 
-        // process the template
         // first check: did we need any processing at all?
         if (Arrays.equals(result, input)) {
             return false;
@@ -102,7 +104,8 @@ public class VelocityArtifactPreprocesso
 
         // Note: we do not cache any previously created processed templates, since the call that asks us to approve a new version
         // may cross a pending needsNewVersion call.
-        return !newHash.equals(oldHash);
+        boolean answer = !newHash.equals(oldHash);
+        return answer;
     }
 
     @Override
@@ -119,14 +122,18 @@ public class VelocityArtifactPreprocesso
             // template isn't modified; use direct URL instead...
             return url;
         }
-        
         setHashForVersion(url, targetID, version, hash(result));
-        
         String name = getFilename(url, targetID, version);
-        
-        uploadAsynchronously(name, obrBase, new ByteArrayInputStream(result));
 
-        return determineNewUrl(name, obrBase).toString();
+        String location = null;
+        if(obrBase.getProtocol().equals("http")){
+            // upload the new resource to the OBR
+            location = upload(new ByteArrayInputStream(result), name, ConfigurationHelper.MIMETYPE, obrBase);
+        } else {
+            // this is only to support the unit tests
+            location = obrBase + name;
+        }
+        return location;
     }
 
     /**
@@ -156,63 +163,70 @@ public class VelocityArtifactPreprocesso
     }
 
     /**
-     * @param url
-     * @param targetID
-     * @param version
-     * @return
-     */
-    private String getFilename(String url, String targetID, String version) {
+     * Creates a new filename for a processed template.
+     * 
+     * @param url the original url
+     * @param targetID the targetID
+     * @param version the version
+     * @return a new filename
+     */
+    private String getFilename(String url, String targetID, String targetVersion) {
+
+        String fileName = "";
+        String fileExtension = "";
+        String fileVersion = "";
+        
         int indexOfLastSlash = url.lastIndexOf('/');
         if (indexOfLastSlash != -1) {
-            url = url.substring(indexOfLastSlash + 1);
+            fileName = url.substring(indexOfLastSlash + 1);
+        }
+        int indexOfLastDot = fileName.lastIndexOf('.');
+        if (indexOfLastDot != -1) {
+            fileExtension = fileName.substring(indexOfLastDot);
+            fileName = fileName.substring(0, indexOfLastDot);
+        }
+
+        int dashIndex = fileName.indexOf('-');
+        while (dashIndex != -1 && fileVersion.equals("")) {
+            String versionCandidate = fileName.substring(dashIndex + 1);
+            Matcher versionMatcher = VERSION_PATTERN.matcher(versionCandidate);
+            if(versionMatcher.matches()){
+                fileName = fileName.substring(0, dashIndex);
+                fileVersion = versionCandidate;
+            } else {
+                dashIndex = fileName.indexOf(fileName, dashIndex);                
+            }
         }
-        return url + "-" + targetID + "-" + version;
-    }
 
-    /**
-     * @param url
-     * @param targetID
-     * @param version
-     * @return
-     */
-    private String getFullUrl(String url, String targetID, String version) {
-        return url + "-" + targetID + "-" + version;
+        fileName = fileName + ".target-" + targetID + "-" + targetVersion + fileExtension;
+        return fileName;
     }
 
     /**
-     * @param url
-     * @param target
-     * @param version
-     * @return
+     * Creates a hash for caching.
+     * 
+     * @param url the url
+     * @param target the target
+     * @param version the version
+     * @return a hash
      */
     private String getHashForVersion(String url, String target, String version) {
         String key = createHashKey(url, target, version);
-
         Reference<String> ref = m_cachedHashes.get(key);
         String hash = (ref != null) ? ref.get() : null;
-        if (hash == null) {
-            try {
-                hash = hash(getBytesFromUrl(getFullUrl(url, target, version)));
-
-                m_cachedHashes.put(key, new WeakReference<String>(hash));
-            }
-            catch (IOException e) {
-                // we cannot retrieve the artifact, so we cannot say anything about it.
-            }
-        }
-
         return hash;
     }
 
     /**
-     * @param url
-     * @param target
-     * @param version
-     * @param hash
+     * Adds a hash to the cache.
+     * 
+     * @param url the url
+     * @param target the target
+     * @param version the version
+     * @param hash the hash
      */
     private void setHashForVersion(String url, String target, String version, String hash) {
         String key = createHashKey(url, target, version);
-
         m_cachedHashes.put(key, new WeakReference<String>(hash));
     }
 
@@ -227,7 +241,6 @@ public class VelocityArtifactPreprocesso
     private byte[] process(byte[] input, PropertyResolver props) throws IOException {
         VelocityContext context = new VelocityContext();
         context.put("context", props);
-
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             Writer writer = new OutputStreamWriter(baos);
@@ -249,7 +262,6 @@ public class VelocityArtifactPreprocesso
      */
     private byte[] getArtifactAsBytes(String url) throws IOException {
         byte[] result = null;
-
         Reference<byte[]> ref = m_cachedArtifacts.get(url);
         if (ref == null || ((result = ref.get()) == null)) {
             result = getBytesFromUrl(url);
@@ -271,20 +283,16 @@ public class VelocityArtifactPreprocesso
         InputStream in = m_connectionFactory.createConnection(new URL(url)).getInputStream();
         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
             byte[] buf = new byte[BUFFER_SIZE];
             for (int count = in.read(buf); count != -1; count = in.read(buf)) {
                 baos.write(buf, 0, count);
             }
-            
             result = baos.toByteArray();
-            
             m_cachedArtifacts.put(url, new WeakReference<byte[]>(result));
         }
         finally {
             silentlyClose(in);
         }
-
         return result;
     }
 

Modified: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java (original)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java Tue Apr 16 11:06:20 2013
@@ -121,14 +121,6 @@ public class ArtifactRepositoryImpl exte
     ArtifactObjectImpl createNewInhabitant(Map<String, String> attributes, Map<String, String> tags) {
         ArtifactHelper helper = getHelper(attributes.get(ArtifactObject.KEY_MIMETYPE));
         ArtifactObjectImpl ao = new ArtifactObjectImpl(helper.checkAttributes(attributes), helper.getMandatoryAttributes(), tags, this, this);
-        if ((ao.getAttribute("upload") != null) && (m_obrBase != null)) {
-            try {
-                ao.addAttribute(ArtifactObject.KEY_URL, new URL(m_obrBase, ao.getDefinition() + ao.getAttribute("upload")).toString());
-            }
-            catch (MalformedURLException e) {
-                throw new IllegalStateException(e);
-            }
-        }
         return ao;
     }
 
@@ -353,34 +345,19 @@ public class ArtifactRepositoryImpl exte
         }
 
         String artifactURL = artifact.toString();
-
         attributes.put(ArtifactObject.KEY_URL, artifactURL);
 
         if (upload) {
-            attributes.put("upload", recognizer.getExtension(resource));
-        }
-
-        ArtifactObject result = create(attributes, tags);
-
-        if (upload) {
             try {
-                upload(artifact, result.getDefinition() + attributes.get("upload"), mimetype);
+                String location = upload(artifact, attributes.get("filename"), mimetype);
+                attributes.put(ArtifactObject.KEY_URL, location);
             }
             catch (IOException ex) {
-                remove(result);
                 throw ex;
             }
-            finally {
-                try {
-                    attributes.remove("upload");
-                }
-                catch (Exception ex) {
-                    // Not much we can do
-                }
-            }
         }
+        ArtifactObject result = create(attributes, tags);
         return result;
-
     }
 
     /**
@@ -427,25 +404,29 @@ public class ArtifactRepositoryImpl exte
      * 
      * @param artifact
      *            URL pointing to the local artifact.
+     * @param filename
+     *            The filenmame parameter, may be <code>null</code>.
      * @param mimetype
      *            The mimetype of this artifact.
-     * @return The persistent URL of this artifact.
+     * @return The persistent location of this artifact.
      * @throws IOException
      *             for any problem uploading the artifact.
      */
-    private URL upload(URL artifact, String definition, String mimetype) throws IOException {
+    private String upload(URL artifact, String filename, String mimetype) throws IOException {
         if (m_obrBase == null) {
             throw new IOException("There is no storage available for this artifact.");
         }
 
         InputStream input = null;
         OutputStream output = null;
-        URL url = null;
+        URL url = m_obrBase;
+        String location = null;
         try {
             input = openInputStream(artifact);
 
-            url = new URL(m_obrBase, definition);
-
+            if(filename != null){
+                url = new URL(m_obrBase,"?filename=" + filename);
+            } 
             URLConnection connection = m_connectionFactory.createConnection(url);
 
             connection.setDoOutput(true);
@@ -472,7 +453,8 @@ public class ArtifactRepositoryImpl exte
             if (connection instanceof HttpURLConnection) {
                 int responseCode = ((HttpURLConnection) connection).getResponseCode();
                 switch (responseCode) {
-                    case HttpURLConnection.HTTP_OK:
+                    case HttpURLConnection.HTTP_CREATED:
+                        location = connection.getHeaderField("Location");
                         break;
                     case HttpURLConnection.HTTP_CONFLICT:
                         throw new IOException("Artifact already exists in storage.");
@@ -505,7 +487,7 @@ public class ArtifactRepositoryImpl exte
             }
         }
 
-        return url;
+        return location;
     }
 
     public void setObrBase(URL obrBase) {

Modified: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/BundleServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/BundleServlet.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/BundleServlet.java (original)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/BundleServlet.java Tue Apr 16 11:06:20 2013
@@ -18,6 +18,7 @@
  */
 package org.apache.ace.obr.servlet;
 
+import static javax.servlet.http.HttpServletResponse.SC_CREATED;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
@@ -64,6 +65,7 @@ public class BundleServlet extends HttpS
     private volatile BundleStore m_store; /* will be injected by dependencymanager */
     private volatile AuthenticationService m_authService;
 
+    private volatile String m_servletEndpoint = "/";
     private volatile boolean m_useAuth = false;
 
     @Override
@@ -79,8 +81,18 @@ public class BundleServlet extends HttpS
                 throw new ConfigurationException(KEY_USE_AUTHENTICATION, "Missing or invalid value!");
             }
             boolean useAuth = Boolean.parseBoolean(useAuthString);
-
             m_useAuth = useAuth;
+            
+            m_servletEndpoint = (String) settings.get("org.apache.ace.server.servlet.endpoint");
+            if(m_servletEndpoint == null){
+                m_servletEndpoint = "/";
+            }
+            if(!m_servletEndpoint.startsWith("/")){
+                m_servletEndpoint = "/" + m_servletEndpoint;
+            }
+            if(!m_servletEndpoint.endsWith("/")){
+                m_servletEndpoint = m_servletEndpoint + "/";
+            }
         }
         else {
             m_useAuth = false;
@@ -101,36 +113,32 @@ public class BundleServlet extends HttpS
     }
 
     /**
-     * Responds to POST requests sent to http://host:port/obr/resource by writing the received data to the bundle store.
-     * Will send out a response that contains one of the following status codes:
+     * Responds to POST requests sent to http://host:port/obr by writing the received data to the bundle store and
+     * returning the persistent location. Will send out a response that contains one of the following status codes:
      * <ul>
      * <li><code>HttpServletResponse.SC_BAD_REQUEST</code> - if no resource was specified</li>
      * <li><code>HttpServletResponse.SC_CONFLICT</code> - if the resource already exists</li>
      * <li><code>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</code> - if there was a problem storing the resource</li>
-     * <li><code>HttpServletResponse.SC_OK</code> - if all went fine</li>
+     * <li><code>HttpServletResponse.SC_CREATED</code> - if all went fine</li>
      * </ul>
      */
     @Override
     protected void doPost(HttpServletRequest request, HttpServletResponse response) {
-        String path = request.getPathInfo();
-        if ((path == null) || (path.length() <= 1)) {
-            sendResponse(response, SC_BAD_REQUEST);
-        }
-        else {
-            String id = path.substring(1);
-            try {
-                if (m_store.put(id, request.getInputStream())) {
-                    sendResponse(response, SC_OK);
-                }
-                else {
-                    sendResponse(response, SC_CONFLICT);
-                }
+
+        String fileName = request.getParameter("filename");
+        try {
+            String storePath = m_store.put(request.getInputStream(), fileName);
+            if (storePath != null) {
+                sendCreated(request, response, storePath);
             }
-            catch (IOException e) {
-                m_log.log(LogService.LOG_WARNING, "Exception handling request: " + request.getRequestURL(), e);
-                sendResponse(response, SC_INTERNAL_SERVER_ERROR);
+            else {
+                sendResponse(response, SC_CONFLICT);
             }
         }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_WARNING, "Exception handling request: " + request.getRequestURL(), e);
+            sendResponse(response, SC_INTERNAL_SERVER_ERROR);
+        }
     }
 
     /**
@@ -315,6 +323,18 @@ public class BundleServlet extends HttpS
         }
     }
 
+    // send a created response with location header
+    private void sendCreated(HttpServletRequest request, HttpServletResponse response, String relativePath) {
+        StringBuilder locationBuiler = new StringBuilder(request.getScheme()).append("://").append(request.getServerName());
+        boolean ignorePort = (request.getScheme().equals("http") && request.getServerPort() == 80) | (request.getScheme().equals("https") && request.getServerPort() == 443);
+        if(!ignorePort){
+            locationBuiler.append(":" + request.getServerPort());
+        }
+        locationBuiler.append(m_servletEndpoint).append(relativePath);
+        response.setHeader("Location", locationBuiler.toString());
+        response.setStatus(SC_CREATED);
+    }
+
     // send a response with the specified status code
     private void sendResponse(HttpServletResponse response, int statusCode) {
         sendResponse(response, statusCode, "");

Modified: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/BundleStore.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/BundleStore.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/BundleStore.java (original)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/BundleStore.java Tue Apr 16 11:06:20 2013
@@ -29,28 +29,31 @@ public interface BundleStore extends Man
     /**
      * Returns an <code>InputStream</code> to the data of the specified resource.
      *
-     * @param fileName Identifier of the requested resource.
+     * @param filePath Relative path of the the resource.
      * @return <code>InputStream</code> to the requested resource or <code>null</code> if no such resource is available.
      * @throws java.io.IOException If there was a problem returning the requested resource.
      */
-    public InputStream get(String fileName) throws IOException;
+    public InputStream get(String filePath) throws IOException;
 
     /**
-     * Stores the specified resource in the store.
+     * Stores the specified resource in the store. For non OSGi bundles a valid filename must be specified that may
+     * contain a valid OSGi version.
+     * <br/><br/>
+     * Filename pattern: <code>&lt;filename&gt;[-&lt;version&gt;].&lt;extension&gt;<code>
      *
-     * @param fileName Identifier of the resource.
+     * @param fileName name of the resource, ignored if the resource is an OSGi bundle
      * @param data The actual data of the resource.
-     * @return <code>true</code> if the resource was successfully stored, <code>false</code> if the resource already existed
+     * @return the filePath if the resource was successfully stored, <code>null</code> if the resource already existed
      * @throws java.io.IOException If there was a problem reading or writing the data of the resource.
      */
-    public boolean put(String fileName, InputStream data) throws IOException;
-
+    public String put(InputStream data, String fileName) throws IOException;
+    
     /**
      * Removes the specified resource from the store.
      *
-     * @param filename Identifier of the resource.
+     * @param filePath Relative path of the the resource.
      * @return <code>true</code> if the resource was successfully removed, <code>false</code> if the resource was not present to begin with
      * @throws java.io.IOException If there was a problem removing the data of the resource from the store.
      */
-    public boolean remove(String filename) throws IOException;
+    public boolean remove(String filePath) throws IOException;
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java (original)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java Tue Apr 16 11:06:20 2013
@@ -29,10 +29,16 @@ import java.nio.channels.FileChannel;
 import java.util.Dictionary;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.ace.obr.metadata.MetadataGenerator;
 import org.apache.ace.obr.storage.BundleStore;
 import org.apache.ace.obr.storage.file.constants.OBRFileStoreConstants;
+import org.osgi.framework.Constants;
 import org.osgi.service.cm.ConfigurationException;
 import org.osgi.service.cm.ManagedService;
 import org.osgi.service.log.LogService;
@@ -43,6 +49,9 @@ import org.osgi.service.log.LogService;
  */
 public class BundleFileStore implements BundleStore, ManagedService {
 
+    // matches a valid OSGi version
+    private final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+)([\\.-]([\\w-]+))?)?)?");
+    
     private static int BUFFER_SIZE = 8 * 1024;
     private static final String REPOSITORY_XML = "repository.xml";
 
@@ -79,28 +88,32 @@ public class BundleFileStore implements 
 		return result;
     }
 
-    public boolean put(String fileName, InputStream data) throws IOException {
-        final File file = createFile(fileName);
+    public String put(InputStream data, String fileName) throws IOException {
 
-        boolean success = false;
-        if (!file.exists()) {
-            File folder = file.getAbsoluteFile().getParentFile();
-            if (!folder.isDirectory() && !folder.mkdirs()) {
-                throw new IOException("Could not create folder " + folder);
-            }
-            try {
-                // the reason for first writing to a temporary file is that we want to minimize
-                // the window where someone could be looking at a "partial" file that is still being
-                // uploaded
-                downloadToFile(data, file);
-                success = true;
-            }
-            catch (IOException e) {
-                // if anything goes wrong while reading from the input stream or
-                // moving the file, delete the temporary file
-            }
+        File tempFile = downloadToTempFile(data);
+
+        ResourceMetaData metaData = getBundleMetaData(tempFile);
+        if (metaData == null) {
+            metaData = getArtifactMetaData(fileName);
+        }
+        if (metaData == null) {
+            throw new IOException("Not a valid bundle and no filename found");
+        }
+
+        File storeLocation = getResourceFile(metaData);
+        if (storeLocation == null) {
+            throw new IOException("Failed to store resource");
+        }
+        if (storeLocation.exists()) {
+            return null;
         }
-        return success;
+
+        moveFile(tempFile, storeLocation);
+        String filePath = storeLocation.getAbsolutePath().substring(getWorkingDir().getAbsolutePath().length());
+        if(filePath.startsWith(File.separator)){
+            filePath = filePath.substring(1);
+        }
+        return filePath;
     }
 
     public boolean remove(String fileName) throws IOException {
@@ -188,20 +201,16 @@ public class BundleFileStore implements 
     }
 
     /**
-     * Downloads a given input stream to a temporary file and if done, moves it to its final location.
+     * Downloads a given input stream to a temporary file.
      * 
      * @param source the input stream to download;
-     * @param dest the destination to write the downloaded file to.
      * @throws IOException in case of I/O problems.
      */
-    private void downloadToFile(InputStream source, File dest) throws IOException {
+    private File downloadToTempFile(InputStream source) throws IOException {
         File tempFile = File.createTempFile("obr", ".tmp");
-
         FileOutputStream fos = null;
-
         try {
             fos = new FileOutputStream(tempFile);
-
             int read;
             byte[] buffer = new byte[BUFFER_SIZE];
             while ((read = source.read(buffer)) >= 0) {
@@ -209,18 +218,125 @@ public class BundleFileStore implements 
             }
             fos.flush();
             fos.close();
-
-            if (!tempFile.renameTo(dest)) {
-                if (!moveFile(tempFile, dest)) {
-                    throw new IOException("Failed to move file store to its destination!");
+            return tempFile;
+        }
+        finally {
+            closeQuietly(fos);
+        }
+    }
+    
+    /**
+     * Tries extract file metadata from a file assuming it is a valid OSGi bundle.
+     * 
+     * @param file the file to analyze
+     * @return the metadata, or <code>null</code> if the file is not a valid bundle.
+     */
+    private ResourceMetaData getBundleMetaData(File file) {
+        JarInputStream jis = null;
+        try {
+            jis = new JarInputStream(new FileInputStream(file));
+            Manifest manifest = jis.getManifest();
+            if (manifest != null) {
+                Attributes attributes = manifest.getMainAttributes();
+                if (attributes != null) {
+                    String bundleSymbolicName = attributes.getValue(Constants.BUNDLE_SYMBOLICNAME);
+                    String bundleVersion = attributes
+                        .getValue(Constants.BUNDLE_VERSION);
+                    if(bundleSymbolicName != null){
+                        if(bundleVersion == null){
+                            bundleVersion = "0.0.0";
+                        }
+                        return new ResourceMetaData(bundleSymbolicName, bundleVersion, "jar");
+                    }
                 }
             }
+            return null;
+        }
+        catch (Exception e) {
+            return null;
         }
         finally {
-            closeQuietly(fos);
+            closeQuietly(jis);
+        }
+    }
+        
+    /**
+     * Tries extract file metadata from a filename assuming a pattern. The version must be a valid OSGi version. If no
+     * version is found the default "0.0.0" is returned.
+     * <br/><br/>
+     * Filename pattern: <code>&lt;filename&gt;[-&lt;version&gt;][.&lt;extension&gt;]<code>
+     * 
+     * @param file the fileName to analyze
+     * @return the metadata, or <code>null</code> if the file is not a valid bundle.
+     */
+    private ResourceMetaData getArtifactMetaData(String fileName) {
+        
+        if (fileName == null || fileName.equals("")) {
+            return null;
+        }
+
+        String symbolicName = null;
+        String version = null;
+        String extension = null;
+
+        // determine extension
+        String[] fileNameParts = fileName.split("\\.");
+        if (fileNameParts.length > 1) {
+            extension = fileNameParts[fileNameParts.length - 1];
+            symbolicName = fileName.substring(0, fileName.lastIndexOf('.'));
+        } else {
+            symbolicName = fileName;
+        }
+
+        // determine version
+        int dashIndex = symbolicName.indexOf('-');
+        while (dashIndex != -1 && version == null) {
+            String versionCandidate = symbolicName.substring(dashIndex + 1);
+            Matcher versionMatcher = VERSION_PATTERN.matcher(versionCandidate);
+            if(versionMatcher.matches()){
+                symbolicName = symbolicName.substring(0, dashIndex);
+                version = versionCandidate;
+            } else {
+                dashIndex = symbolicName.indexOf('-', dashIndex + 1);                
+            }
+        }
+        
+        if(version == null){
+            version = "0.0.0";
+        }
+        return new ResourceMetaData(symbolicName, version, extension);
+    }
+    
+    /**
+     * Encapsulated the store layout strategy by creating the resource file based on the provided meta-data.
+     * 
+     * @param metaData the meta-data for the resource
+     * @return the resource file
+     * @throws IOException in case of I/O problems.
+     */
+    private File getResourceFile(ResourceMetaData metaData) throws IOException {
 
-            tempFile.delete();
+        File resourceFile = null;
+        String[] dirs = metaData.getSymmbolicName().split("\\.");
+        for (String subDir : dirs) {
+            if (resourceFile == null) {
+                resourceFile = new File(getWorkingDir(), subDir);
+            }
+            else {
+                resourceFile = new File(resourceFile, subDir);
+            }
         }
+        if(!resourceFile.exists() && !resourceFile.mkdirs()){
+            throw new IOException("Failed to create store directory");
+        }
+        
+        if (metaData.getExtension() != null && !metaData.getExtension().equals("")) {
+            resourceFile = new File(resourceFile, metaData.getSymmbolicName() + "-" + metaData.getVersion() + "." + metaData.getExtension());
+        }
+        else {
+            resourceFile = new File(resourceFile, metaData.getSymmbolicName() + "-" + metaData.getVersion());
+        }
+        return resourceFile;
     }
 
     /**
@@ -310,4 +426,32 @@ public class BundleFileStore implements 
     private File createFile(String fileName) {
         return new File(getWorkingDir(), fileName);
     }
+    
+    /**
+     * Wrapper that holds resource meta-data relevant to the store layout.
+     *
+     */
+    static class ResourceMetaData {
+        private final String m_bundleSymbolicName;
+        private final String m_version;
+        private final String m_extension;
+        
+        public ResourceMetaData(String bundleSymbolicName, String version, String extension){
+            m_bundleSymbolicName = bundleSymbolicName;
+            m_version = version;
+            m_extension = extension;
+        }
+        
+        public String getSymmbolicName(){
+            return m_bundleSymbolicName;
+        }
+        
+        public String getVersion(){
+            return m_version;
+        }
+        
+        public String getExtension(){
+            return m_extension;
+        }
+    }
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/BundleServletTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/BundleServletTest.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/BundleServletTest.java (original)
+++ ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/BundleServletTest.java Tue Apr 16 11:06:20 2013
@@ -65,6 +65,22 @@ public class BundleServletTest {
         TestUtils.configureObject(m_bundleServlet, BundleStore.class, m_store);
 
         m_request = TestUtils.createMockObjectAdapter(HttpServletRequest.class, new Object() {
+
+            @SuppressWarnings("unused")
+            public String getScheme() {
+                return "http";
+            }
+            
+            @SuppressWarnings("unused")
+            public String getServerName() {
+                return "localhost";
+            }
+            
+            @SuppressWarnings("unused")
+            public int getServerPort() {
+                return 9999;
+            }
+
             @SuppressWarnings("unused")
             public String getParameter(String param) {
                 return m_requestFile;
@@ -119,6 +135,11 @@ public class BundleServletTest {
             }
 
             @SuppressWarnings("unused")
+            public void setStatus(int status) {
+                m_status = status;
+            }
+
+            @SuppressWarnings("unused")
             public void sendError(int status) {
                 m_status = status;
             }
@@ -179,20 +200,10 @@ public class BundleServletTest {
     public void testPostResource() throws Exception {
         m_requestFile = "NewFile";
         m_bundleServlet.doPost(m_request, m_response);
-        assert m_status == HttpServletResponse.SC_OK;
+        assert m_status == HttpServletResponse.SC_CREATED;
         m_requestFile = "ExistingFile";
         m_bundleServlet.doPost(m_request, m_response);
         assert m_status == HttpServletResponse.SC_CONFLICT;
-        m_requestFile = "";
-        m_bundleServlet.doPost(m_request, m_response);
-        assert m_status == HttpServletResponse.SC_BAD_REQUEST;
-    }
-
-    @Test(groups = { UNIT })
-    public void testPostResourceInPath() throws Exception {
-        m_requestFile = "path/to/file";
-        m_bundleServlet.doPost(m_request, m_response);
-        assert m_status == HttpServletResponse.SC_OK;
     }
 
     @Test(groups = { UNIT })

Modified: ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/MockBundleStore.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/MockBundleStore.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/MockBundleStore.java (original)
+++ ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/MockBundleStore.java Tue Apr 16 11:06:20 2013
@@ -20,7 +20,6 @@ package org.apache.ace.obr.servlet;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.Dictionary;
 
 import org.apache.ace.obr.storage.BundleStore;
@@ -41,18 +40,14 @@ public class MockBundleStore implements 
         return m_outFile;
     }
 
-    public void put(String fileName, OutputStream data) throws IOException {
-        // TODO does nothing yet
-    }
-
-    public boolean put(String fileName, InputStream data) throws IOException {
+    public String put(InputStream data, String fileName) throws IOException {
         if (fileName.equals("NewFile")) {
-            return true;
+            return "NewFile";
         }
         if (fileName.equals("path/to/file")) {
-            return true;
+            return "path/to/file";
         }
-        return false;
+        return null;
     }
 
     public boolean remove(String fileName) throws IOException {

Modified: ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/BundleFileStoreTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/BundleFileStoreTest.java?rev=1468366&r1=1468365&r2=1468366&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/BundleFileStoreTest.java (original)
+++ ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/BundleFileStoreTest.java Tue Apr 16 11:06:20 2013
@@ -20,7 +20,6 @@ package org.apache.ace.obr.storage.file;
 
 import static org.apache.ace.test.utils.TestUtils.UNIT;
 
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -30,12 +29,15 @@ import java.io.OutputStream;
 import java.util.Arrays;
 import java.util.Properties;
 import java.util.Random;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
 
 import org.apache.ace.obr.metadata.MetadataGenerator;
-import org.apache.ace.obr.storage.BundleStore;
 import org.apache.ace.obr.storage.file.constants.OBRFileStoreConstants;
 import org.apache.ace.test.utils.FileUtils;
 import org.apache.ace.test.utils.TestUtils;
+import org.osgi.framework.Constants;
 import org.osgi.service.cm.ConfigurationException;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
@@ -43,7 +45,7 @@ import org.testng.annotations.Test;
 
 public class BundleFileStoreTest {
 
-    private BundleStore m_bundleStore;
+    private BundleFileStore m_bundleStore;
     private MockMetadataGenerator m_metadata;
 
     private File m_directory;
@@ -99,12 +101,12 @@ public class BundleFileStoreTest {
      */
     @Test(groups = { UNIT })
     public void getNonExistingBundle() throws Exception {
-    	assert m_bundleStore.get("blaat") == null : "Getting an non-existing file did not result in null?";
+        assert m_bundleStore.get("blaat") == null : "Getting an non-existing file did not result in null?";
     }
 
     /**
-     * Test whether retrieving the repository.xml results in a call to the (mock) metadata generator,
-     * and the original file should correspond with the retrieved file.
+     * Test whether retrieving the repository.xml results in a call to the (mock) metadata generator, and the original
+     * file should correspond with the retrieved file.
      */
     @Test(groups = { UNIT })
     public void getRepositoryFile() throws Exception {
@@ -122,8 +124,8 @@ public class BundleFileStoreTest {
     }
 
     /**
-     * Test whether the BundleStore notices the set of bundles has changed (bundle updated),
-     * and makes a call to the (mock) metadata generator.
+     * Test whether the BundleStore notices the set of bundles has changed (bundle updated), and makes a call to the
+     * (mock) metadata generator.
      */
     @Test(groups = { UNIT })
     public void updateBundle() throws Exception {
@@ -140,10 +142,9 @@ public class BundleFileStoreTest {
     }
 
     /**
-     * Test whether the BundleStore notices the set of bundles has changed (bundle added),
-     * and makes a call to the (mock) metadata generator. Also a call should be made when
-     * a bundle is replaced by another one (number of bundles stay the same, but one bundle
-     * is replaced by another).
+     * Test whether the BundleStore notices the set of bundles has changed (bundle added), and makes a call to the
+     * (mock) metadata generator. Also a call should be made when a bundle is replaced by another one (number of bundles
+     * stay the same, but one bundle is replaced by another).
      */
     @Test(groups = { UNIT })
     public void addBundle() throws Exception {
@@ -167,8 +168,8 @@ public class BundleFileStoreTest {
     }
 
     /**
-     * Test whether the BundleStore notices the set of bundles has not changed, and thus
-     * will not make a call to the (mock) metadata generator.
+     * Test whether the BundleStore notices the set of bundles has not changed, and thus will not make a call to the
+     * (mock) metadata generator.
      */
     @Test(groups = { UNIT })
     public void replaceWithSameBundle() throws Exception {
@@ -191,9 +192,8 @@ public class BundleFileStoreTest {
     }
 
     /**
-     * Test whether changing the directory where the bundles are stored, does not result in a call
-     * to the (mock) metadata generator, as the metadata will only be regenerated after getting
-     * a file.
+     * Test whether changing the directory where the bundles are stored, does not result in a call to the (mock)
+     * metadata generator, as the metadata will only be regenerated after getting a file.
      */
     @Test(groups = { UNIT })
     public void updateConfigurationWithValidConfiguration() throws Exception {
@@ -216,8 +216,8 @@ public class BundleFileStoreTest {
     }
 
     /**
-     * Test whether changing the directory where the bundles are stored to something that is not
-     * a directory, this should fail.
+     * Test whether changing the directory where the bundles are stored to something that is not a directory, this
+     * should fail.
      */
     @Test(groups = { UNIT })
     public void updateConfigurationWithIsNotDirectory() throws Exception {
@@ -243,68 +243,100 @@ public class BundleFileStoreTest {
 
     @Test(groups = { UNIT })
     public void putBundle() throws Exception {
-        String fileName = "filename";
-        m_bundleStore.put(fileName, new ByteArrayInputStream("a".getBytes()));
-        File file = new File(m_directory, fileName);
-        FileInputStream input = new FileInputStream(file);
-        assert input.read() == 'a';
-        assert input.read() == -1;
-        input.close();
+        File bundle = createTmpResource("foo.bar", "1.0.0");
+        String filePath = m_bundleStore.put(new FileInputStream(bundle), null);
+        assert filePath.equals("foo/bar/foo.bar-1.0.0.jar");
+        File file = new File(m_directory, filePath);
+        assert file.exists();
     }
 
     @Test(groups = { UNIT })
-    public void putBundleInPathAndRemoveItAgain() throws Exception {
-        String fileName = "path/to/filename";
-        m_bundleStore.put(fileName, new ByteArrayInputStream("a".getBytes()));
-        File file = new File(m_directory, fileName);
-        FileInputStream input = new FileInputStream(file);
-        assert input.read() == 'a';
-        assert input.read() == -1;
-        input.close();
-        assert m_bundleStore.remove(fileName);
+    public void putBundleDuplicate() throws Exception {
+        File bundle = createTmpResource("foo.bar", "1.0.0");
+        m_bundleStore.put(new FileInputStream(bundle), null);
+        String filePath2 = m_bundleStore.put(new FileInputStream(bundle), null);
+        assert filePath2 == null;
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IOException.class }, expectedExceptionsMessageRegExp = "Not a valid bundle and no filename found")
+    public void putBundleFail() throws Exception {
+        File bundle = createTmpResource(null, "1.0.0");
+        String filePath = m_bundleStore.put(new FileInputStream(bundle), null);
+        assert filePath.equals("foo/bar/foo.bar-1.0.0.jar");
+        File file = new File(m_directory, filePath);
+        assert file.exists();
     }
 
     @Test(groups = { UNIT })
-    public void putBundleInInvalidPathShouldFail() throws Exception {
-        String fileName = "path/to/name";
-        String fileName2 = "path/to/name/invalid";
-        m_bundleStore.put(fileName, new ByteArrayInputStream("a".getBytes()));
-        try {
-            m_bundleStore.put(fileName2, new ByteArrayInputStream("a".getBytes()));
-            assert false;
-        }
-        catch (IOException e) {
-            // we expect this to happen
-        }
+    public void putRemoveArtifact() throws Exception {
+        File bundle = createTmpResource(null, null);
+        String filePath = m_bundleStore.put(new FileInputStream(bundle), "foo.bar-2.3.7.test1.xxx");
+        assert filePath.equals("foo/bar/foo.bar-2.3.7.test1.xxx");
+        File file = new File(m_directory, filePath);
+        assert file.exists();
     }
 
-    
     @Test(groups = { UNIT })
-    public void removeExistingBundle() throws Exception {
-        m_bundleStore.put("filename", new InputStream() {
-            private int i = 0;
+    public void putArtifactDefaultVersion() throws Exception {
+        File bundle = createTmpResource(null, null);
+        String filePath = m_bundleStore.put(new FileInputStream(bundle), "foo.bar.xxx");
+        assert filePath.equals("foo/bar/foo.bar-0.0.0.xxx");
+        File file = new File(m_directory, filePath);
+        assert file.exists();
+    }
 
-            @Override
-            public int read() throws IOException {
-                if (i < 1) {
-                    i++;
-                    return 'a';
-                }
-                else {
-                    return -1;
-                }
-            }
-        });
-        File file = new File(m_directory, "filename");
+    @Test(groups = { UNIT })
+    public void putArtifactMavenVersion() throws Exception {
+        File bundle = createTmpResource(null, null);
+        String filePath = m_bundleStore.put(new FileInputStream(bundle), "foo.bar-2.3.7-test1.xxx");
+        assert filePath.equals("foo/bar/foo.bar-2.3.7-test1.xxx");
+        File file = new File(m_directory, filePath);
         assert file.exists();
-        m_bundleStore.remove("filename");
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IOException.class }, expectedExceptionsMessageRegExp = "Not a valid bundle and no filename found")
+    public void putArtifactFail1() throws Exception {
+        File bundle = createTmpResource(null, null);
+        m_bundleStore.put(new FileInputStream(bundle), null);
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IOException.class }, expectedExceptionsMessageRegExp = "Not a valid bundle and no filename found")
+    public void putArtifactFail2() throws Exception {
+        File bundle = createTmpResource(null, null);
+        m_bundleStore.put(new FileInputStream(bundle), "");
+    }
+
+    @Test(groups = { UNIT })
+    public void removeBundle() throws Exception {
+        File bundle = createTmpResource("foo.bar", "1.0.0");
+        String filePath = m_bundleStore.put(new FileInputStream(bundle), null);
+        File file = new File(m_directory, filePath);
+        assert file.exists();
+        assert m_bundleStore.remove(filePath);
+        assert !file.exists();
+    }
+
+    @Test(groups = { UNIT })
+    public void removeBundleFaill() throws Exception {
+        File file = new File(m_directory, "no/such/file");
+        assert !file.exists();
+        assert !m_bundleStore.remove("no/such/file");
+    }
+
+    @Test(groups = { UNIT })
+    public void removeArtifact() throws Exception {
+        File bundle = createTmpResource(null, null);
+        String filePath = m_bundleStore.put(new FileInputStream(bundle), "foo.bar-2.3.7.test1.xxx");
+        assert filePath.equals("foo/bar/foo.bar-2.3.7.test1.xxx");
+        File file = new File(m_directory, filePath);
+        assert file.exists();
+        assert m_bundleStore.remove("foo/bar/foo.bar-2.3.7.test1.xxx");
         assert !file.exists();
     }
 
     /**
-     * Test whether not configuring the directory (so retrieving the directory returns null),
-     * results in a ConfigurationException. Updating with null as dictionary should only clean up
-     * things, and nothing else.
+     * Test whether not configuring the directory (so retrieving the directory returns null), results in a
+     * ConfigurationException. Updating with null as dictionary should only clean up things, and nothing else.
      */
     @Test(groups = { UNIT })
     public void updateConfigurationWithNull() throws Exception {
@@ -320,7 +352,6 @@ public class BundleFileStoreTest {
         assert exceptionThrown : "Reconfiguring directory succeeded but should fail, as property is supposed to be missing";
         assert !m_metadata.generated() : "After changing the directory, the metadata should not be regenerated.";
 
-
         exceptionThrown = false;
         try {
             m_bundleStore.updated(null);
@@ -333,8 +364,8 @@ public class BundleFileStoreTest {
     }
 
     /**
-     * Test whether not configuring the directory (so retrieving the directory returns null),
-     * results in a ConfigurationException.
+     * Test whether not configuring the directory (so retrieving the directory returns null), results in a
+     * ConfigurationException.
      */
     @Test(groups = { UNIT })
     public void updateConfigurationWithSameDirectory() throws Exception {
@@ -350,6 +381,23 @@ public class BundleFileStoreTest {
         assert !m_metadata.generated() : "After changing the directory, the metadata should not be regenerated.";
     }
 
+    private File createTmpResource(String symbolicName, String version) throws IOException {
+        File tmpFile = File.createTempFile("tmpbundle-", "jar");
+        tmpFile.deleteOnExit();
+        
+        Manifest manifest = new Manifest();
+        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+        if (symbolicName != null) {
+            manifest.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, symbolicName);
+        }
+        if (version != null) {
+            manifest.getMainAttributes().putValue(Constants.BUNDLE_VERSION, version);
+        }
+        JarOutputStream target = new JarOutputStream(new FileOutputStream(tmpFile), manifest);
+        target.close();
+        return tmpFile;
+    }
+
     private File createFileWithContent(File baseDir, String filename, int size) throws IOException {
         OutputStream fileOut = null;
         File file = new File(baseDir, filename);
@@ -359,7 +407,6 @@ public class BundleFileStoreTest {
             Random randomContentCreator = new Random();
             randomContentCreator.nextBytes(byteArray);
             fileOut.write(byteArray);
-
             return file;
         }
         finally {
@@ -373,6 +420,4 @@ public class BundleFileStoreTest {
             }
         }
     }
-
-
 }