You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2013/12/18 09:19:23 UTC

[02/50] [abbrv] Refactoring of the Cave modules. Complete HTTP service wrapper.

http://git-wip-us.apache.org/repos/asf/karaf-cave/blob/94949ca7/server/storage/pom.xml
----------------------------------------------------------------------
diff --git a/server/storage/pom.xml b/server/storage/pom.xml
new file mode 100644
index 0000000..cf2b5c0
--- /dev/null
+++ b/server/storage/pom.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.cave</groupId>
+        <artifactId>org.apache.karaf.cave.server</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.cave.server</groupId>
+    <artifactId>org.apache.karaf.cave.server.storage</artifactId>
+    <name>Apache Karaf :: Cave :: Server :: Storage</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.cave.server</groupId>
+            <artifactId>org.apache.karaf.cave.server.api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.bundlerepository</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient-osgi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Import-Package>
+                            org.apache.karaf.cave.server.api;version="${project.version}",
+                            org.slf4j*;resolution:=optional,
+                            org.apache.felix.bundlerepository*;version="[2,3)",
+                            org.osgi.framework,
+                            org.osgi.service.blueprint,
+                            org.osgi.service.log,
+                            org.osgi.service.url,
+                            org.apache.http*;version="[4,5)",
+                            org.jsoup*;version="[1.6,2)",
+                            org.apache.commons.io*;version="[2,3)",
+                            !org.apache.felix.shell,
+                            !org.apache.felix.bundlerepository.impl.wrapper,
+                            !org.osgi.service.obr,
+                            !javax.xml.stream,
+                        </Import-Package>
+                        <Private-Package>
+                            org.kxml2.io,
+                            org.xmlpull.v1,
+                            org.apache.felix.utils*,
+                            org.apache.felix.bundlerepository.impl*
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf-cave/blob/94949ca7/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryImpl.java
----------------------------------------------------------------------
diff --git a/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryImpl.java b/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryImpl.java
new file mode 100644
index 0000000..7a26948
--- /dev/null
+++ b/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryImpl.java
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.server.storage;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.felix.bundlerepository.Resource;
+import org.apache.felix.bundlerepository.impl.DataModelHelperImpl;
+import org.apache.felix.bundlerepository.impl.RepositoryImpl;
+import org.apache.felix.bundlerepository.impl.ResourceImpl;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.karaf.cave.server.api.CaveRepository;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.net.URL;
+
+/**
+ * Default implementation of a Karaf Cave repository.
+ */
+public class CaveRepositoryImpl extends CaveRepository {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(CaveRepositoryImpl.class);
+
+    private RepositoryImpl obrRepository;
+
+    public CaveRepositoryImpl(String name, String location, boolean scan) throws Exception {
+        super();
+
+        this.setName(name);
+        this.setLocation(location);
+
+        this.createRepositoryDirectory();
+        if (scan) {
+            this.scan();
+        }
+    }
+
+    /**
+     * Check if the repository folder exists and create it if not.
+     */
+    private void createRepositoryDirectory() throws Exception {
+        LOGGER.debug("Create Karaf Cave repository {} folder.", this.getName());
+        File locationFile = new File(this.getLocation());
+        if (!locationFile.exists()) {
+            locationFile.mkdirs();
+            LOGGER.debug("Karaf Cave repository {} location has been created.", this.getName());
+            LOGGER.debug(locationFile.getAbsolutePath());
+        }
+        File repositoryXml = new File(locationFile, "repository.xml");
+        if (repositoryXml.exists()) {
+            obrRepository = (RepositoryImpl) new DataModelHelperImpl().repository(repositoryXml.toURI().toURL());
+        } else {
+            obrRepository = new RepositoryImpl();
+            obrRepository.setName(this.getName());
+        }
+    }
+
+    /**
+     * Generate the repository.xml with the artifact at the given URL.
+     *
+     * @throws Exception in case of repository.xml update failure.
+     */
+    private void generateRepositoryXml() throws Exception {
+        File repositoryXml = this.getRepositoryXmlFile();
+        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(repositoryXml));
+        new DataModelHelperImpl().writeRepository(obrRepository, writer);
+        writer.flush();
+        writer.close();
+    }
+
+    /**
+     * Add a resource in the OBR repository.
+     *
+     * @param resource the resource to add.
+     * @throws Exception in case of failure.
+     */
+    private void addResource(ResourceImpl resource) throws Exception {
+        if (resource != null) {
+            this.useResourceRelativeUri(resource);
+            obrRepository.addResource(resource);
+            obrRepository.setLastModified(System.currentTimeMillis());
+        }
+    }
+
+    /**
+     * Upload an artifact from the given URL.
+     *
+     * @param url the URL of the artifact.
+     * @throws Exception in case of upload failure.
+     */
+    public void upload(URL url) throws Exception {
+        LOGGER.debug("Upload new artifact from {}", url);
+        String artifactName = "artifact-" + System.currentTimeMillis();
+        File temp = new File(new File(this.getLocation()), artifactName);
+        FileUtils.copyURLToFile(url, temp);
+        // update the repository.xml
+        ResourceImpl resource = (ResourceImpl) new DataModelHelperImpl().createResource(temp.toURI().toURL());
+        if (resource == null) {
+            temp.delete();
+            LOGGER.warn("The {} artifact source is not a valid OSGi bundle", url);
+            return;
+        }
+        File destination = new File(new File(this.getLocation()), resource.getSymbolicName() + "-" + resource.getVersion() + ".jar");
+        FileUtils.moveFile(temp, destination);
+        resource = (ResourceImpl) new DataModelHelperImpl().createResource(destination.toURI().toURL());
+        this.addResource(resource);
+        this.generateRepositoryXml();
+    }
+
+    /**
+     * Scan the content of the whole repository to update the repository.xml.
+     *
+     * @throws Exception in case of scan failure.
+     */
+    public void scan() throws Exception {
+        this.scan(new File(this.getLocation()));
+        this.generateRepositoryXml();
+    }
+
+    /**
+     * Recursive method to traverse all files in the repository.
+     *
+     * @param entry the
+     * @throws Exception
+     */
+    private void scan(File entry) throws Exception {
+        if (entry.isDirectory()) {
+            File[] children = entry.listFiles();
+            for (int i = 0; i < children.length; i++) {
+                scan(children[i]);
+            }
+        } else {
+            // populate the repository
+            try {
+                ResourceImpl resource = (ResourceImpl) new DataModelHelperImpl().createResource(entry.toURI().toURL());
+                this.addResource(resource);
+            } catch (IllegalArgumentException e) {
+                LOGGER.warn(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Proxy an URL (by adding repository.xml OBR information) in the Karaf Cave repository.
+     *
+     * @param url the URL to proxyFilesystem. the URL to proxyFilesystem.
+     * @throws Exception
+     */
+    public void proxy(URL url) throws Exception {
+        if (url.getProtocol().equals("file")) {
+            // filesystem proxyFilesystem (to another folder)
+            File proxyFolder = new File(url.toURI());
+            this.proxyFilesystem(proxyFolder);
+        }
+        if (url.getProtocol().equals("http")) {
+            // HTTP proxyFilesystem
+            this.proxyHttp(url.toExternalForm());
+        }
+        this.generateRepositoryXml();
+    }
+
+    /**
+     * Proxy a local filesystem (folder).
+     * @param entry the filesystem to proxyFilesystem.
+     * @throws Exception in case of proxyFilesystem failure
+     */
+    private void proxyFilesystem(File entry) throws Exception {
+        LOGGER.debug("Proxying filesystem {}", entry.getAbsolutePath());
+        if (entry.isDirectory()) {
+            File[] children = entry.listFiles();
+            for (int i = 0; i < children.length; i++) {
+                proxyFilesystem(children[i]);
+            }
+        } else {
+            try {
+                Resource resource = new DataModelHelperImpl().createResource(entry.toURI().toURL());
+                if (resource != null) {
+                    obrRepository.addResource(resource);
+                    obrRepository.setLastModified(System.currentTimeMillis());
+                }
+            } catch (IllegalArgumentException e) {
+                LOGGER.warn(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Proxy a HTTP URL locally.
+     *
+     * @param url the HTTP URL to proxy.
+     * @throws Exception in case of proxy failure.
+     */
+    private void proxyHttp(String url) throws Exception {
+        LOGGER.debug("Proxying HTTP URL {}", url);
+        HttpClient httpClient = new DefaultHttpClient();
+
+        HttpGet httpGet = new HttpGet(url);
+        HttpResponse response = httpClient.execute(httpGet);
+        HttpEntity entity = response.getEntity();
+
+        if (entity != null) {
+            if (entity.getContentType().getValue().equals("application/java-archive")
+                    || entity.getContentType().getValue().equals("application/octet-stream")) {
+                // I have a jar/binary, potentially a resource
+                try {
+                    Resource resource = new DataModelHelperImpl().createResource(new URL(url));
+                    if (resource != null) {
+                        obrRepository.addResource(resource);
+                        obrRepository.setLastModified(System.currentTimeMillis());
+                    }
+                } catch (IllegalArgumentException e) {
+                    LOGGER.warn(e.getMessage());
+                }
+            } else {
+                // try to find link to "browse"
+                Document document = Jsoup.connect(url).get();
+
+                Elements links = document.select("a");
+                if (links.size() > 1) {
+                    for (int i = 1; i < links.size(); i++) {
+                        Element link = links.get(i);
+                        String absoluteHref = link.attr("abs:href");
+                        this.proxyHttp(absoluteHref);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Populate an URL into the Karaf Cave repository, and eventually update the OBR information.
+     *
+     * @param url the URL to copy.
+     * @param update if true the OBR information is updated, false else.
+     * @throws Exception in case of populate failure.
+     */
+    public void populate(URL url, boolean update) throws Exception {
+        if (url.getProtocol().equals("file")) {
+            // populate the Karaf Cave repository from a filesystem folder
+            File populateFolder = new File(url.toURI());
+            this.populateFromFilesystem(populateFolder, update);
+        }
+        if (url.getProtocol().equals("http")) {
+            // populate the Karaf Cave repository from a HTTP URL
+            this.populateFromHttp(url.toExternalForm(), update);
+        }
+        if (update) {
+            this.generateRepositoryXml();
+        }
+    }
+
+    /**
+     * Populate the Karaf Cave repository using a filesystem directory.
+     *
+     * @param filesystem the "source" directory.
+     * @param update if true, the resources are added into the OBR metadata, false else.
+     * @throws Exception in case of populate failure.
+     */
+    private void populateFromFilesystem(File filesystem, boolean update) throws Exception {
+        LOGGER.debug("Populating from filesystem {}", filesystem.getAbsolutePath());
+        if (filesystem.isDirectory()) {
+            File[] children = filesystem.listFiles();
+            for (int i = 0; i < children.length; i++) {
+                populateFromFilesystem(children[i], update);
+            }
+        } else {
+            try {
+                ResourceImpl resource = (ResourceImpl) new DataModelHelperImpl().createResource(filesystem.toURI().toURL());
+                if (resource != null) {
+                    // copy the resource
+                    File destination = new File(new File(this.getLocation()), filesystem.getName());
+                    LOGGER.debug("Copy from {} to {}", filesystem.getAbsolutePath(), destination.getAbsolutePath());
+                    FileUtils.copyFile(filesystem, destination);
+                    if (update) {
+                        resource = (ResourceImpl) new DataModelHelperImpl().createResource(destination.toURI().toURL());
+                        LOGGER.debug("Update the OBR metadata with {}", resource.getId());
+                        this.addResource(resource);
+                    }
+                }
+            } catch (IllegalArgumentException e) {
+                LOGGER.warn(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Populate the Karaf Cave repository using the given URL.
+     *
+     * @param url the "source" HTTP URL.
+     * @param update true if the OBR metadata should be updated, false else.
+     * @throws Exception in case of populate failure.
+     */
+    private void populateFromHttp(String url, boolean update) throws Exception {
+        LOGGER.debug("Populating from HTTP URL {}", url);
+        HttpClient httpClient = new DefaultHttpClient();
+
+        HttpGet httpGet = new HttpGet(url);
+        HttpResponse response = httpClient.execute(httpGet);
+        HttpEntity entity = response.getEntity();
+
+        if (entity != null) {
+            if (entity.getContentType().getValue().equals("application/java-archive")
+                    || entity.getContentType().getValue().equals("application/octet-stream")) {
+                // I have a jar/binary, potentially a resource
+                try {
+                    ResourceImpl resource = (ResourceImpl) new DataModelHelperImpl().createResource(new URL(url));
+                    if (resource != null) {
+                        LOGGER.debug("Copy {} into the Karaf Cave repository storage", url);
+                        int index = url.lastIndexOf("/");
+                        if (index > 0) {
+                            url = url.substring(index);
+                        }
+                        File destination = new File(new File(this.getLocation()), url);
+                        FileOutputStream outputStream = new FileOutputStream(destination);
+                        entity.writeTo(outputStream);
+                        outputStream.flush();
+                        outputStream.close();
+                        if (update) {
+                            resource = (ResourceImpl) new DataModelHelperImpl().createResource(destination.toURI().toURL());
+                            LOGGER.debug("Update OBR metadata with {}", resource.getId());
+                            this.addResource(resource);
+                        }
+                    }
+                } catch (IllegalArgumentException e) {
+                    LOGGER.warn(e.getMessage());
+                }
+            } else {
+                // try to find link to "browse"
+                Document document = Jsoup.connect(url).get();
+
+                Elements links = document.select("a");
+                if (links.size() > 1) {
+                    for (int i = 1; i < links.size(); i++) {
+                        Element link = links.get(i);
+                        String absoluteHref = link.attr("abs:href");
+                        this.populateFromHttp(absoluteHref, update);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Convert the Resource absolute URI to an URI relative to the repository one.
+     *
+     * @param resource the Resource to manipulate.
+     * @throws Exception in cave of URI convertion failure.
+     */
+    private void useResourceRelativeUri(ResourceImpl resource) throws Exception {
+        String resourceURI = resource.getURI();
+        String locationURI = "file:" + this.getLocation();
+        LOGGER.debug("Converting resource URI {} relatively to repository URI {}", resourceURI, locationURI);
+        if (resourceURI.startsWith(locationURI)) {
+            resourceURI = resourceURI.substring(locationURI.length() + 1);
+            LOGGER.debug("Resource URI converted to " + resourceURI);
+            resource.put(Resource.URI, resourceURI);
+        }
+
+    }
+
+    /**
+     * Get the File object of the OBR repository.xml file.
+     *
+     * @return the File corresponding to the OBR repository.xml.
+     * @throws Exception
+     */
+    private File getRepositoryXmlFile() throws Exception {
+        return new File(new File(this.getLocation()), "repository.xml");
+    }
+
+    public void getResourceByUri(String uri) {
+        // construct the file starting from the repository URI
+
+    }
+
+    /**
+     * Return the OBR repository.xml corresponding to this Karaf Cave repository.
+     *
+     * @return the URL of the OBR repository.xml.
+     * @throws Exception in case of lookup failure.
+     */
+    public URL getRepositoryXml() throws Exception {
+        File repositoryXml = this.getRepositoryXmlFile();
+        return repositoryXml.toURI().toURL();
+    }
+
+    /**
+     * Delete the repository storage folder.
+     *
+     * @throws Exception in case of destroy failure.
+     */
+    public void cleanup() throws Exception {
+        FileUtils.deleteDirectory(new File(this.getLocation()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf-cave/blob/94949ca7/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryServiceImpl.java
----------------------------------------------------------------------
diff --git a/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryServiceImpl.java b/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryServiceImpl.java
new file mode 100644
index 0000000..fdeee1c
--- /dev/null
+++ b/server/storage/src/main/java/org/apache/karaf/cave/server/storage/CaveRepositoryServiceImpl.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.server.storage;
+
+import org.apache.felix.bundlerepository.RepositoryAdmin;
+import org.apache.karaf.cave.server.api.CaveRepository;
+import org.apache.karaf.cave.server.api.CaveRepositoryService;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Default implementation of the Cave Repository Service.
+ */
+public class CaveRepositoryServiceImpl implements CaveRepositoryService {
+
+    private File storageLocation;
+    private RepositoryAdmin repositoryAdmin;
+
+    private Map<String, CaveRepository> repositories = new HashMap<String, CaveRepository>();
+
+    public File getStorageLocation() {
+        return this.storageLocation;
+    }
+
+    public void setStorageLocation(File storageLocation) {
+        this.storageLocation = storageLocation;
+    }
+
+    public RepositoryAdmin getRepositoryAdmin() {
+        return this.repositoryAdmin;
+    }
+
+    public void setRepositoryAdmin(RepositoryAdmin repositoryAdmin) {
+        this.repositoryAdmin = repositoryAdmin;
+    }
+
+    /**
+     * Create a new Karaf Cave repository.
+     *
+     * @param name the name of the repository
+     * @param scan if true, the repository is scanned at creation time.
+     * @return  the Karaf Cave repository.
+     * @throws Exception in case of creation failure.
+     */
+    public synchronized CaveRepository createRepository(String name, boolean scan) throws Exception {
+        File location = new File(storageLocation, name);
+        return this.createRepository(name, location.getAbsolutePath(), scan);
+    }
+
+    /**
+     * Create a new Karaf Cave repository.
+     *
+     * @param name the name of the repository.
+     * @param location the storage location of the repository.
+     * @param scan if true, the repostory is scanned at creation time.
+     * @return the Karaf Cave repository.
+     * @throws Exception in case of creation failure.
+     */
+    public synchronized CaveRepository createRepository(String name, String location, boolean scan) throws Exception {
+        if (repositories.get(name) != null) {
+            throw new IllegalArgumentException("Cave repository " + name + " already exists.");
+        }
+        CaveRepository repository = new CaveRepositoryImpl(name, location, scan);
+        repositories.put(name, repository);
+        return repository;
+    }
+
+    /**
+     * Destroy a Karaf Cave repository.
+     *
+     * @param name the name of Karaf Cave repository to destroy.
+     * @throws Exception in case of destroy failure.
+     */
+    public synchronized void destroy(String name) throws Exception {
+        CaveRepository repository = this.getRepository(name);
+        if (repository != null) {
+            repository.cleanup();
+            repositories.remove(name);
+        }
+    }
+
+    /**
+     * Register a Karaf Cave repository in the OBR service.
+     *
+     * @param name the name of the Karaf Cave repository.
+     * @throws Exception in case of registration failure.
+     */
+    public synchronized void register(String name) throws Exception {
+        CaveRepository caveRepository = this.getRepository(name);
+        repositoryAdmin.addRepository(caveRepository.getRepositoryXml());
+    }
+
+    /**
+     * Get the list of all Karaf Cave repositories.
+     *
+     * @return the list of all Karaf Cave repositories.
+     */
+    public synchronized CaveRepository[] getRepositories() {
+        return repositories.values().toArray(new CaveRepository[0]);
+    }
+
+    /**
+     * Get the Karaf Cave repository identified by name.
+     *
+     * @param name the name of the Karaf Cave repository to look for.
+     * @return the corresponding Karaf Cave repository.
+     */
+    public synchronized CaveRepository getRepository(String name) {
+        return repositories.get(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf-cave/blob/94949ca7/server/storage/src/main/resources/OSGI-INF/blueprint/cave-storage.xml
----------------------------------------------------------------------
diff --git a/server/storage/src/main/resources/OSGI-INF/blueprint/cave-storage.xml b/server/storage/src/main/resources/OSGI-INF/blueprint/cave-storage.xml
new file mode 100644
index 0000000..f5dab07
--- /dev/null
+++ b/server/storage/src/main/resources/OSGI-INF/blueprint/cave-storage.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
+           xmlns:cxf="http://cxf.apache.org/blueprint/core"
+           xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
+           xsi:schemaLocation="
+            http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
+            http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
+            http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd
+           "
+           default-activation="lazy">
+
+    <bean id="caveRepositoryService" class="org.apache.karaf.cave.server.storage.CaveRepositoryServiceImpl">
+        <property name="storageLocation" value="${storage.location}"/>
+        <property name="repositoryAdmin" ref="repositoryAdmin"/>
+    </bean>
+
+    <reference id="repositoryAdmin" interface="org.apache.felix.bundlerepository.RepositoryAdmin"/>
+
+    <service ref="caveRepositoryService" interface="org.apache.karaf.cave.server.api.CaveRepositoryService"/>
+
+    <!-- use the cm of Cave filesystem backend -->
+    <cm:property-placeholder persistent-id="org.apache.karaf.cave.server.storage" update-strategy="reload">
+        <cm:default-properties>
+            <cm:property name="storage.location" value="cave"/>
+        </cm:default-properties>
+    </cm:property-placeholder>
+
+    <!-- start the JAX-RS server -->
+    <jaxrs:server id="caveRepositoryJaxRsServer" address="/cave">
+        <jaxrs:serviceBeans>
+            <ref component-id="caveRepositoryService"/>
+        </jaxrs:serviceBeans>
+    </jaxrs:server>
+
+</blueprint>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf-cave/blob/94949ca7/server/storage/src/test/java/org/apache/karaf/cave/server/storage/CaveRepositoryImplTest.java
----------------------------------------------------------------------
diff --git a/server/storage/src/test/java/org/apache/karaf/cave/server/storage/CaveRepositoryImplTest.java b/server/storage/src/test/java/org/apache/karaf/cave/server/storage/CaveRepositoryImplTest.java
new file mode 100644
index 0000000..b04cb7e
--- /dev/null
+++ b/server/storage/src/test/java/org/apache/karaf/cave/server/storage/CaveRepositoryImplTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.server.storage;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.net.URL;
+
+/**
+ * Unit test of the Cave Repository Implementation.
+ */
+@RunWith(JUnit4.class)
+public class CaveRepositoryImplTest {
+
+    private CaveRepositoryImpl repository;
+
+    @Before
+    public void setUp() throws Exception {
+        repository = new CaveRepositoryImpl("test", "target/test-repository", false);
+    }
+
+    @Test
+    public void testUploadBundleFromURL() throws Exception {
+        repository.upload(new URL("http://repo1.maven.org/maven2/org/apache/servicemix/bundles/org.apache.servicemix.bundles.commons-beanutils/1.8.2_2/org.apache.servicemix.bundles.commons-beanutils-1.8.2_2.jar"));
+        repository.upload(new URL("http://repo1.maven.org/maven2/org/apache/servicemix/bundles/org.apache.servicemix.bundles.commons-lang/2.4_5/org.apache.servicemix.bundles.commons-lang-2.4_5.jar"));
+    }
+
+    @Test
+    public void testUploadNonBundleFromURL() throws Exception {
+        repository.upload(new URL("http://repo1.maven.org/maven2/commons-vfs/commons-vfs/1.0/commons-vfs-1.0.jar"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf-cave/blob/94949ca7/server/storage/src/test/resources/log4j.xml
----------------------------------------------------------------------
diff --git a/server/storage/src/test/resources/log4j.xml b/server/storage/src/test/resources/log4j.xml
new file mode 100644
index 0000000..a3140de
--- /dev/null
+++ b/server/storage/src/test/resources/log4j.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
+
+    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+        <param name="threshold" value="DEBUG"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%-5p - %-30c{1} - %m%n"/>
+        </layout>
+    </appender>
+
+    <root>
+        <level value="DEBUG"/>
+        <appender-ref ref="CONSOLE"/>
+    </root>
+
+</log4j:configuration>
\ No newline at end of file