You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2013/04/02 15:26:44 UTC

svn commit: r1463529 [1/3] - in /ace/trunk: org.apache.ace.obr.metadata/ org.apache.ace.obr.servlet/ org.apache.ace.obr.storage/ org.apache.ace.obr/ org.apache.ace.obr/.settings/ org.apache.ace.obr/src/ org.apache.ace.obr/src/org/ org.apache.ace.obr/sr...

Author: marrs
Date: Tue Apr  2 13:26:43 2013
New Revision: 1463529

URL: http://svn.apache.org/r1463529
Log:
ACE-333 Initial merge of OBR projects.

Added:
    ace/trunk/org.apache.ace.obr/
    ace/trunk/org.apache.ace.obr/.classpath
    ace/trunk/org.apache.ace.obr/.project
    ace/trunk/org.apache.ace.obr/.settings/
    ace/trunk/org.apache.ace.obr/.settings/org.eclipse.jdt.core.prefs
    ace/trunk/org.apache.ace.obr/bnd.bnd
    ace/trunk/org.apache.ace.obr/build.xml
    ace/trunk/org.apache.ace.obr/metadata.bnd
    ace/trunk/org.apache.ace.obr/servlet.bnd
    ace/trunk/org.apache.ace.obr/src/
    ace/trunk/org.apache.ace.obr/src/org/
    ace/trunk/org.apache.ace.obr/src/org/apache/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/MetadataGenerator.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/Activator.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/BIndexMetadataGenerator.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/packageinfo
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/Activator.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/
    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/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/Activator.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/constants/
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/constants/OBRFileStoreConstants.java
    ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/packageinfo
    ace/trunk/org.apache.ace.obr/src/org/osgi/
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/Index.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/packageinfo
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/BundleInfo.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/FilterImpl.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/Manifest.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/ManifestEntry.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/Parameter.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/RepositoryImpl.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/RequirementImpl.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/ResourceImpl.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/StringSet.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/Tag.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/VersionRange.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/packageinfo
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/Capability.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/CapabilityProvider.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/Repository.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/RepositoryAdmin.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/RepositoryPermission.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/Requirement.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/Resolver.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/Resource.java
    ace/trunk/org.apache.ace.obr/src/org/osgi/service/obr/packageinfo
    ace/trunk/org.apache.ace.obr/storage.bnd
    ace/trunk/org.apache.ace.obr/test/
    ace/trunk/org.apache.ace.obr/test/org/
    ace/trunk/org.apache.ace.obr/test/org/apache/
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/metadata/
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/metadata/bindeximpl/
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/metadata/bindeximpl/BindexMetadataTest.java
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/metadata/bindeximpl/VersionRangeTest.java
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/servlet/
    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/
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/BundleFileStoreTest.java
    ace/trunk/org.apache.ace.obr/test/org/apache/ace/obr/storage/file/MockMetadataGenerator.java
Removed:
    ace/trunk/org.apache.ace.obr.metadata/
    ace/trunk/org.apache.ace.obr.servlet/
    ace/trunk/org.apache.ace.obr.storage/

Added: ace/trunk/org.apache.ace.obr/.classpath
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/.classpath?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/.classpath (added)
+++ ace/trunk/org.apache.ace.obr/.classpath Tue Apr  2 13:26:43 2013
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" output="bin_test" path="test"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="aQute.bnd.classpath.container"/>
+	<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>

Added: ace/trunk/org.apache.ace.obr/.project
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/.project?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/.project (added)
+++ ace/trunk/org.apache.ace.obr/.project Tue Apr  2 13:26:43 2013
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.apache.ace.obr</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>bndtools.core.bndbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>bndtools.core.bndnature</nature>
+	</natures>
+</projectDescription>

Added: ace/trunk/org.apache.ace.obr/.settings/org.eclipse.jdt.core.prefs
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/.settings/org.eclipse.jdt.core.prefs?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/.settings/org.eclipse.jdt.core.prefs (added)
+++ ace/trunk/org.apache.ace.obr/.settings/org.eclipse.jdt.core.prefs Tue Apr  2 13:26:43 2013
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6

Added: ace/trunk/org.apache.ace.obr/bnd.bnd
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/bnd.bnd?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/bnd.bnd (added)
+++ ace/trunk/org.apache.ace.obr/bnd.bnd Tue Apr  2 13:26:43 2013
@@ -0,0 +1,12 @@
+-sub: *.bnd
+-buildpath: osgi.core,\
+	osgi.cmpn,\
+	org.apache.felix.dependencymanager,\
+	javax.servlet,\
+	org.apache.ace.authentication.api;version=latest,\
+	org.apache.ace.test;version=latest,\
+	org.apache.ace.deployment.provider.api;version=latest,\
+	org.apache.ace.deployment.provider.base;version=latest,\
+	kxml2;version=2.3.0
+	
+	

Added: ace/trunk/org.apache.ace.obr/build.xml
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/build.xml?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/build.xml (added)
+++ ace/trunk/org.apache.ace.obr/build.xml Tue Apr  2 13:26:43 2013
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="project" default="build">
+
+	<!-- -->
+
+	<import file="../cnf/build.xml" />
+</project>

Added: ace/trunk/org.apache.ace.obr/metadata.bnd
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/metadata.bnd?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/metadata.bnd (added)
+++ ace/trunk/org.apache.ace.obr/metadata.bnd Tue Apr  2 13:26:43 2013
@@ -0,0 +1,15 @@
+Private-Package: org.apache.ace.obr.metadata.bindex,\
+	org.kxml2.io,\
+	org.kxml2.kdom,\
+	org.kxml2.wap,\
+	org.kxml2.wap.syncml,\
+	org.kxml2.wap.wml,\
+	org.kxml2.wap.wv,\
+	org.xmlpull.v1,\
+	org.osgi.impl.bundle.bindex,\
+	org.osgi.impl.bundle.obr.resource
+Bundle-Activator: org.apache.ace.obr.metadata.bindex.Activator
+Export-Package: org.apache.ace.obr.metadata,\
+	org.osgi.impl.bundle.obr.resource,\
+	org.osgi.service.obr
+Bundle-Version: 1.0.0

Added: ace/trunk/org.apache.ace.obr/servlet.bnd
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/servlet.bnd?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/servlet.bnd (added)
+++ ace/trunk/org.apache.ace.obr/servlet.bnd Tue Apr  2 13:26:43 2013
@@ -0,0 +1,3 @@
+Private-Package: org.apache.ace.obr.servlet
+Bundle-Activator: org.apache.ace.obr.servlet.Activator
+Bundle-Version: 1.0.0

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/MetadataGenerator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/MetadataGenerator.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/MetadataGenerator.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/MetadataGenerator.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,37 @@
+/*
+ * 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.ace.obr.metadata;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface MetadataGenerator
+{
+
+    /**
+     * Generates the repository.xml based upon the new set of Bundles in the given directory. The xml is created
+     * as result of this method in the given directory in a file called repository.xml.
+     * This methods creates the file in an atomic fashion (this includes retrying to overwrite an existing file until success).
+     *
+     * @param directory the location where to store the newly created repository.xml
+     *
+     * @throws java.io.IOException If I/O problems occur when generating the new meta data index file.
+     */
+    public void generateMetadata(File directory) throws IOException;
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/Activator.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/Activator.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/Activator.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,43 @@
+/*
+ * 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.ace.obr.metadata.bindex;
+
+import org.apache.ace.obr.metadata.MetadataGenerator;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createComponent()
+            .setInterface(MetadataGenerator.class.getName(), null)
+            .setImplementation(BIndexMetadataGenerator.class)
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to be done
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/BIndexMetadataGenerator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/BIndexMetadataGenerator.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/BIndexMetadataGenerator.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/bindex/BIndexMetadataGenerator.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,163 @@
+/*
+ * 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.ace.obr.metadata.bindex;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+import org.apache.ace.obr.metadata.MetadataGenerator;
+import org.osgi.impl.bundle.bindex.Index;
+import org.osgi.service.log.LogService;
+
+public class BIndexMetadataGenerator implements MetadataGenerator {
+
+    private static final String INDEX_FILENAME = "repository";
+    private static final String INDEX_EXTENSION = ".xml";
+
+    private volatile LogService m_log; /* will be injected by dependencymanager */
+
+    public void generateMetadata(File directory) throws IOException {
+        if (directory.isDirectory()) {
+            File tempIndex;
+            File index = new File(directory, INDEX_FILENAME + INDEX_EXTENSION);
+            try {
+                tempIndex = File.createTempFile("repo", INDEX_EXTENSION, directory);
+                Index.main(new String[] { "-q", "-a", "-r", tempIndex.getAbsolutePath(), directory.getAbsolutePath() });
+                renameFile(tempIndex, index);
+            }
+            catch (IOException e) {
+                if (m_log != null) {
+                    m_log.log(LogService.LOG_ERROR, "Unable to create temporary file for new repository index.", e);
+                }
+                throw e;
+            }
+            catch (InterruptedException e) {
+                if (m_log != null) {
+                    m_log.log(LogService.LOG_ERROR, "Waiting for next attempt to move temporary repository index failed.", e);
+                }
+                // Make sure the thread's administration remains correct...
+                Thread.currentThread().interrupt();
+            }
+            catch (Exception e) {
+                if (m_log != null) {
+                    m_log.log(LogService.LOG_ERROR, "Failed to generate new repository index.", e);
+                }
+                throw new IOException("Failed to generate new repository index. + (" + e.getMessage() + ")");
+            }
+        }
+    }
+
+    /**
+     * Renames a given source file to a new destination file, using Commons-IO.
+     * <p>This avoids the problem mentioned in ACE-155.</p>
+     * 
+     * @param source the file to rename;
+     * @param dest the file to rename to.
+     */
+    private void renameFile(File source, File dest) throws IOException, InterruptedException {
+        boolean renameOK = false;
+        int attempts = 0;
+        while (!renameOK && (attempts++ < 10)) {
+            try {
+                renameOK = moveFile(source, dest);
+            }
+            catch (IOException e) {
+                // In all other cases, we assume the source file is still locked and cannot be removed;
+                Thread.sleep(1000);
+            }
+        }
+
+        if (!renameOK) {
+            if (m_log != null) {
+                m_log.log(LogService.LOG_ERROR, "Unable to move new repository index to it's final location.");
+            }
+            throw new IOException("Could not move temporary index file (" + source.getAbsolutePath() + ") to it's final location (" + dest.getAbsolutePath() + ")");
+        }
+    }
+
+    /**
+     * Moves a given source file to a destination location, effectively resulting in a rename.
+     * 
+     * @param source the source file to move;
+     * @param dest the destination file to move the file to.
+     * @return <code>true</code> if the move succeeded.
+     * @throws IOException in case of I/O problems.
+     */
+    private boolean moveFile(File source, File dest) throws IOException {
+        final int bufferSize = 1024 * 1024; // 1MB
+
+        FileInputStream fis = null;
+        FileOutputStream fos = null;
+        FileChannel input = null;
+        FileChannel output = null;
+
+        try {
+            fis = new FileInputStream(source);
+            input = fis.getChannel();
+
+            fos = new FileOutputStream(dest);
+            output = fos.getChannel();
+
+            long size = input.size();
+            long pos = 0;
+            while (pos < size) {
+                pos += output.transferFrom(input, pos, Math.min(size - pos, bufferSize));
+            }
+        }
+        finally {
+            closeQuietly(fos);
+            closeQuietly(fis);
+            closeQuietly(output);
+            closeQuietly(input);
+        }
+
+        if (source.length() != dest.length()) {
+            throw new IOException("Failed to move file! Not all contents from '" + source + "' copied to '" + dest + "'!");
+        }
+
+        dest.setLastModified(source.lastModified());
+
+        if (!source.delete()) {
+            dest.delete();
+            throw new IOException("Failed to move file! Source file (" + source + ") locked?");
+        }
+
+        return true;
+    }
+
+    /**
+     * Safely closes a given resource, ignoring any I/O exceptions that might occur by this.
+     * 
+     * @param resource the resource to close, can be <code>null</code>.
+     */
+    private void closeQuietly(Closeable resource) {
+        try {
+            if (resource != null) {
+                resource.close();
+            }
+        }
+        catch (IOException e) {
+            // Ignored...
+        }
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/packageinfo?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/packageinfo (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/metadata/packageinfo Tue Apr  2 13:26:43 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/Activator.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/Activator.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/Activator.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,52 @@
+/*
+ * 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.ace.obr.servlet;
+
+import javax.servlet.Servlet;
+
+import org.apache.ace.obr.storage.BundleStore;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+    public static final String PID = "org.apache.ace.obr.servlet";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createComponent()
+            .setInterface(Servlet.class.getName(), null)
+            .setImplementation(BundleServlet.class)
+            .add(createConfigurationDependency()
+                .setPropagate(true)
+                .setPid(PID))
+            .add(createServiceDependency()
+                .setService(BundleStore.class)
+                .setRequired(true))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // do nothing
+    }
+}
\ No newline at end of file

Added: 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=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/BundleServlet.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/servlet/BundleServlet.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,332 @@
+/*
+ * 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.ace.obr.servlet;
+
+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;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.ace.authentication.api.AuthenticationService;
+import org.apache.ace.obr.storage.BundleStore;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Provides access to the OBR through a REST-ish API.
+ */
+public class BundleServlet extends HttpServlet implements ManagedService {
+    private static final long serialVersionUID = 1L;
+
+    /** A boolean denoting whether or not authentication is enabled. */
+    private static final String KEY_USE_AUTHENTICATION = "authentication.enabled";
+
+    private static final int COPY_BUFFER_SIZE = 4096;
+
+    public static final String TEXT_MIMETYPE = "text/plain";
+
+    private volatile DependencyManager m_dm; // injected by Dependency Manager
+    private volatile LogService m_log; /* will be injected by dependencymanager */
+    private volatile BundleStore m_store; /* will be injected by dependencymanager */
+    private volatile AuthenticationService m_authService;
+
+    private volatile boolean m_useAuth = false;
+
+    @Override
+    public String getServletInfo() {
+        return "Apache ACE OBR Servlet";
+    }
+
+    public void updated(Dictionary settings) throws ConfigurationException {
+        if (settings != null) {
+            String useAuthString = (String) settings.get(KEY_USE_AUTHENTICATION);
+            if (useAuthString == null
+                || !("true".equalsIgnoreCase(useAuthString) || "false".equalsIgnoreCase(useAuthString))) {
+                throw new ConfigurationException(KEY_USE_AUTHENTICATION, "Missing or invalid value!");
+            }
+            boolean useAuth = Boolean.parseBoolean(useAuthString);
+
+            m_useAuth = useAuth;
+        }
+        else {
+            m_useAuth = false;
+        }
+    }
+
+    /**
+     * Called by Dependency Manager upon initialization of this component.
+     * 
+     * @param comp the component to initialize, cannot be <code>null</code>.
+     */
+    protected void init(Component comp) {
+        comp.add(m_dm.createServiceDependency()
+            .setService(AuthenticationService.class)
+            .setRequired(m_useAuth)
+            .setInstanceBound(true)
+            );
+    }
+
+    /**
+     * 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:
+     * <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>
+     * </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);
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_WARNING, "Exception handling request: " + request.getRequestURL(), e);
+                sendResponse(response, SC_INTERNAL_SERVER_ERROR);
+            }
+        }
+    }
+
+    /**
+     * Responds to DELETE requests sent to http://host:port/obr/resource by deleting the file specified.
+     * Will send out a response that contains one of the following status codes:
+     * <br>
+     * <li><code>HttpServletResponse.SC_BAD_REQUEST</code> - if no resource was specified
+     * <li><code>HttpServletResponse.SC_NOT_FOUND</code> - if the specified resource did not exist
+     * <li><code>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</code> - if there was a problem deleting the resource
+     * <li><code>HttpServletResponse.SC_OK</code> - if all went fine
+     */
+    @Override
+    protected void doDelete(HttpServletRequest request, HttpServletResponse response) {
+        String path = request.getPathInfo();
+        if ((path == null) || (path.length() <= 1)) {
+            sendResponse(response, SC_BAD_REQUEST);
+        }
+        else {
+            // Remove leading slash...
+            String id = path.substring(1);
+            try {
+                if (m_store.remove(id)) {
+                    sendResponse(response, SC_OK);
+                }
+                else {
+                    sendResponse(response, SC_NOT_FOUND);
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_WARNING, "Exception handling request: " + request.getRequestURL(), e);
+                sendResponse(response, SC_INTERNAL_SERVER_ERROR);
+            }
+        }
+    }
+
+    /**
+     * Responds to GET requests sent to http://host:port/obr/resource with a stream to the specified filename.
+     * Will send out a response that contains one of the following status codes:
+     * <br>
+     * <li><code>HttpServletResponse.SC_BAD_REQUEST</code> - if no resource is specified
+     * <li><code>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</code> - if there was a problem storing the resource
+     * <li><code>HttpServletResponse.SC_OK</code> - if all went fine
+     * <br>
+     * The response will only contain the data of the requested resource if the status code of the response is
+     * <code>HttpServletResponse.SC_OK</code>.
+     */
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+        String path = request.getPathInfo();
+        if ((path == null) || (path.length() <= 1)) {
+            sendResponse(response, SC_BAD_REQUEST);
+        }
+        else {
+            // Remove leasing slash...
+            String id = path.substring(1);
+
+            ServletOutputStream output = null;
+            InputStream fileStream = null;
+            try {
+                fileStream = m_store.get(id);
+                if (fileStream == null) {
+                    sendResponse(response, HttpServletResponse.SC_NOT_FOUND);
+                }
+                else {
+                    // send the bundle as stream to the caller
+                    response.setContentType(TEXT_MIMETYPE);
+
+                    output = response.getOutputStream();
+                    byte[] buffer = new byte[COPY_BUFFER_SIZE];
+                    for (int bytes = fileStream.read(buffer); bytes != -1; bytes = fileStream.read(buffer)) {
+                        output.write(buffer, 0, bytes);
+                    }
+                }
+            }
+            catch (EOFException ex) {
+                // ACE-260: lower log-level of this exception; as it is probably because the remote hung up early...
+                m_log.log(LogService.LOG_DEBUG, "EOF Exception in request: " + request.getRequestURL()
+                    + "; probably the remote hung up early.");
+            }
+            catch (IOException ex) {
+                // ACE-260: all other exception are logged, as we might have a possible resource leak...
+                m_log.log(LogService.LOG_WARNING, "Exception in request: " + request.getRequestURL(), ex);
+                sendResponse(response, SC_INTERNAL_SERVER_ERROR);
+            }
+            finally {
+                closeSafely(fileStream, request);
+                closeSafely(output, request);
+            }
+        }
+    }
+    
+    /**
+     * Responds to HEAD requests sent to http://host:port/obr/resource with a stream to the specified filename.
+     * Will send out a response that contains one of the following status codes:
+     * <br>
+     * <li><code>HttpServletResponse.SC_BAD_REQUEST</code> - if no resource is specified
+     * <li><code>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</code> - if there was a problem storing the resource
+     * <li><code>HttpServletResponse.SC_NOT_FOUND</code> - if the requested resource does not exist
+     * <li><code>HttpServletResponse.SC_OK</code> - if all went fine
+     * <br>
+     * The response is empty in all situations.
+     */
+    @Override
+    protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        String path = request.getPathInfo();
+        if ((path == null) || (path.length() <= 1)) {
+            sendResponse(response, SC_BAD_REQUEST);
+        }
+        else {
+            // Remove leasing slash...
+            String id = path.substring(1);
+
+            ServletOutputStream output = null;
+            InputStream fileStream = null;
+            try {
+            	// TODO extend the store with an exists method???
+                fileStream = m_store.get(id);
+                if (fileStream == null) {
+                    sendResponse(response, HttpServletResponse.SC_NOT_FOUND);
+                }
+                else {
+                    sendResponse(response, HttpServletResponse.SC_OK);
+                }
+            }
+            catch (IOException ex) {
+                // ACE-260: all other exception are logged, as we might have a possible resource leak...
+                m_log.log(LogService.LOG_WARNING, "Exception in request: " + request.getRequestURL(), ex);
+                sendResponse(response, SC_INTERNAL_SERVER_ERROR);
+            }
+            finally {
+                closeSafely(fileStream, request);
+                closeSafely(output, request);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        if (!authenticate(req)) {
+            // Authentication failed; don't proceed with the original request...
+            resp.sendError(SC_UNAUTHORIZED);
+        } else {
+            // Authentication successful, proceed with original request...
+            super.service(req, resp);
+        }
+    }
+
+    /**
+     * Authenticates, if needed the user with the information from the given request.
+     * 
+     * @param request the request to obtain the credentials from, cannot be <code>null</code>.
+     * @return <code>true</code> if the authentication was successful, <code>false</code> otherwise.
+     */
+    private boolean authenticate(HttpServletRequest request) {
+        if (m_useAuth) {
+            User user = m_authService.authenticate(request);
+            if (user == null) {
+                m_log.log(LogService.LOG_INFO, "Authentication failure!");
+            }
+            return (user != null);
+        }
+        return true;
+    }
+
+    private void closeSafely(Closeable resource, HttpServletRequest request) {
+        if (resource != null) {
+            try {
+                resource.close();
+            }
+            catch (EOFException ex) {
+                // ACE-260: lower log-level of this exception; as it is probably because the remote hung up early...
+                m_log.log(LogService.LOG_DEBUG, "EOF Exception trying to close stream: " + request.getRequestURL()
+                    + "; probably the remote hung up early.");
+            }
+            catch (Exception ex) {
+                // ACE-260: all other exception are logged, as we might have a possible resource leak...
+                m_log.log(LogService.LOG_WARNING, "Exception trying to close stream: " + request.getRequestURL(), ex);
+            }
+        }
+    }
+
+    // send a response with the specified status code
+    private void sendResponse(HttpServletResponse response, int statusCode) {
+        sendResponse(response, statusCode, "");
+    }
+
+    // send a response with the specified status code and description
+    private void sendResponse(HttpServletResponse response, int statusCode, String description) {
+        try {
+            response.sendError(statusCode, description);
+        }
+        catch (Exception e) {
+            m_log.log(LogService.LOG_WARNING, "Unable to send response with status code '" + statusCode + "'", e);
+        }
+    }
+}
\ No newline at end of file

Added: 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=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/BundleStore.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/BundleStore.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,56 @@
+package org.apache.ace.obr.storage;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.osgi.service.cm.ManagedService;
+
+/*
+ * 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.
+ */
+
+public interface BundleStore extends ManagedService {
+
+    /**
+     * Returns an <code>InputStream</code> to the data of the specified resource.
+     *
+     * @param fileName Identifier of the requested 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;
+
+    /**
+     * Stores the specified resource in the store.
+     *
+     * @param fileName Identifier of the resource.
+     * @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
+     * @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;
+
+    /**
+     * Removes the specified resource from the store.
+     *
+     * @param filename Identifier of 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;
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/Activator.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/Activator.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/Activator.java Tue Apr  2 13:26:43 2013
@@ -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.ace.obr.storage.file;
+
+import org.apache.ace.obr.metadata.MetadataGenerator;
+import org.apache.ace.obr.storage.BundleStore;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+    public static final String PID = "org.apache.ace.obr.storage.file";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createComponent()
+            .setInterface(BundleStore.class.getName(), null)
+            .setImplementation(BundleFileStore.class)
+            .add(createConfigurationDependency()
+                .setPid(PID))
+            .add(createServiceDependency()
+                .setService(MetadataGenerator.class)
+                .setRequired(true))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do here
+    }
+}
\ No newline at end of file

Added: 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=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/BundleFileStore.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,313 @@
+/*
+ * 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.ace.obr.storage.file;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+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.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+
+/**
+ * This BundleStore retrieves the files from the file system. Via the Configurator the relative path is set, and all bundles and
+ * the repository.xml should be retrievable from that path (which will internally be converted to an absolute path).
+ */
+public class BundleFileStore implements BundleStore, ManagedService {
+
+    private static int BUFFER_SIZE = 8 * 1024;
+    private static final String REPOSITORY_XML = "repository.xml";
+
+    private final Map<String, Long> m_foundFiles = new ConcurrentHashMap<String, Long>();
+    private final Object m_dirLock = new Object();
+
+    private volatile MetadataGenerator m_metadata; /* will be injected by dependencymanager */
+    private volatile LogService m_log; /* will be injected by dependencymanager */
+
+    /** protected by m_dirLock. */
+    private File m_dir;
+
+    public void generateMetadata() throws IOException {
+        File dir = getWorkingDir();
+        File[] files = dir.listFiles();
+
+        m_metadata.generateMetadata(dir);
+
+        for (File current : files) {
+            m_foundFiles.put(current.getAbsolutePath(), current.lastModified() ^ current.length());
+        }
+    }
+
+    public InputStream get(String fileName) throws IOException {
+        if (REPOSITORY_XML.equals(fileName) && directoryChanged(getWorkingDir())) {
+            generateMetadata(); // might be called too often
+        }
+        FileInputStream result = null;
+        try {
+			result = new FileInputStream(createFile(fileName));
+		} catch (FileNotFoundException e) {
+			// Resource does not exist; notify caller by returning null...
+		}
+		return result;
+    }
+
+    public boolean put(String fileName, InputStream data) throws IOException {
+        final File file = createFile(fileName);
+
+        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
+            }
+        }
+        return success;
+    }
+
+    public boolean remove(String fileName) throws IOException {
+        File file = createFile(fileName);
+
+        if (file.exists()) {
+            if (file.delete()) {
+                return true;
+            }
+            else {
+                throw new IOException("Unable to delete file (" + file.getAbsolutePath() + ")");
+            }
+        }
+
+        return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void updated(Dictionary dict) throws ConfigurationException {
+        if (dict != null) {
+            String path = (String) dict.get(OBRFileStoreConstants.FILE_LOCATION_KEY);
+            if (path == null) {
+                throw new ConfigurationException(OBRFileStoreConstants.FILE_LOCATION_KEY, "Missing property");
+            }
+
+            File newDir = new File(path);
+            File curDir = getWorkingDir();
+
+            if (!newDir.equals(curDir)) {
+                if (!newDir.exists()) {
+                    newDir.mkdirs();
+                }
+                else if (!newDir.isDirectory()) {
+                    throw new ConfigurationException(OBRFileStoreConstants.FILE_LOCATION_KEY, "Is not a directory: " + newDir);
+                }
+
+                synchronized (m_dirLock) {
+                    m_dir = newDir;
+                }
+
+                m_foundFiles.clear();
+            }
+        }
+        else {
+            // clean up after getting a null as dictionary, as the service is going to be pulled afterwards
+            m_foundFiles.clear();
+        }
+    }
+
+    /**
+     * Called by dependencymanager upon start of this component.
+     */
+    protected void start() {
+        try {
+            generateMetadata();
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Could not generate initial meta data for bundle repository");
+        }
+    }
+
+    @SuppressWarnings("boxing")
+    private boolean directoryChanged(File dir) {
+        File[] files = dir.listFiles();
+
+        // if number of files changed, create new metadata
+        if (files.length != m_foundFiles.size()) {
+            return true;
+        }
+
+        // iterate over the current files
+        for (File current : files) {
+            Long modifiedDateAndLengthXOR = m_foundFiles.get(current.getAbsolutePath());
+            // if one of the current files is not in the old set of files, create new metadata
+            if (modifiedDateAndLengthXOR == null) {
+                return true;
+            }
+            // else if of one of the files the size or the date has been changed, create new metadata
+            if ((current.lastModified() ^ current.length()) != modifiedDateAndLengthXOR) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Downloads a given input stream to a temporary file and if done, moves it to its final location.
+     * 
+     * @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 {
+        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) {
+                fos.write(buffer, 0, read);
+            }
+            fos.flush();
+            fos.close();
+
+            if (!tempFile.renameTo(dest)) {
+                if (!moveFile(tempFile, dest)) {
+                    throw new IOException("Failed to move file store to its destination!");
+                }
+            }
+        }
+        finally {
+            closeQuietly(fos);
+
+            tempFile.delete();
+        }
+    }
+
+    /**
+     * @return the working directory of this file store.
+     */
+    private File getWorkingDir() {
+        final File dir;
+        synchronized (m_dirLock) {
+            dir = m_dir;
+        }
+        return dir;
+    }
+
+    /**
+     * Moves a given source file to a destination location, effectively resulting in a rename.
+     * 
+     * @param source the source file to move;
+     * @param dest the destination file to move the file to.
+     * @return <code>true</code> if the move succeeded.
+     * @throws IOException in case of I/O problems.
+     */
+    private boolean moveFile(File source, File dest) throws IOException {
+        final int bufferSize = 1024 * 1024; // 1MB
+
+        FileInputStream fis = null;
+        FileOutputStream fos = null;
+        FileChannel input = null;
+        FileChannel output = null;
+
+        try {
+            fis = new FileInputStream(source);
+            input = fis.getChannel();
+
+            fos = new FileOutputStream(dest);
+            output = fos.getChannel();
+
+            long size = input.size();
+            long pos = 0;
+            while (pos < size) {
+                pos += output.transferFrom(input, pos, Math.min(size - pos, bufferSize));
+            }
+        }
+        finally {
+            closeQuietly(fos);
+            closeQuietly(fis);
+            closeQuietly(output);
+            closeQuietly(input);
+        }
+
+        if (source.length() != dest.length()) {
+            throw new IOException("Failed to move file! Not all contents from '" + source + "' copied to '" + dest + "'!");
+        }
+
+        dest.setLastModified(source.lastModified());
+
+        if (!source.delete()) {
+            dest.delete();
+            throw new IOException("Failed to move file! Source file (" + source + ") locked?");
+        }
+
+        return true;
+    }
+
+    /**
+     * Safely closes a given resource, ignoring any I/O exceptions that might occur by this.
+     * 
+     * @param resource the resource to close, can be <code>null</code>.
+     */
+    private void closeQuietly(Closeable resource) {
+        try {
+            if (resource != null) {
+                resource.close();
+            }
+        }
+        catch (IOException e) {
+            // Ignored...
+        }
+    }
+
+    /**
+     * Creates a {@link File} object with the given file name in the current working directory.
+     * 
+     * @param fileName the name of the file.
+     * @return a {@link File} object, never <code>null</code>.
+     * @see #getWorkingDir()
+     */
+    private File createFile(String fileName) {
+        return new File(getWorkingDir(), fileName);
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/constants/OBRFileStoreConstants.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/constants/OBRFileStoreConstants.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/constants/OBRFileStoreConstants.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/file/constants/OBRFileStoreConstants.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,24 @@
+/*
+ * 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.ace.obr.storage.file.constants;
+
+public interface OBRFileStoreConstants
+{
+    public static final String FILE_LOCATION_KEY = "fileLocation";
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/packageinfo?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/packageinfo (added)
+++ ace/trunk/org.apache.ace.obr/src/org/apache/ace/obr/storage/packageinfo Tue Apr  2 13:26:43 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/Index.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/Index.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/Index.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/Index.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,308 @@
+/*
+ * $Id: Index.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $
+ *
+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.
+ *
+ * Licensed 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.osgi.impl.bundle.bindex;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.osgi.impl.bundle.obr.resource.BundleInfo;
+import org.osgi.impl.bundle.obr.resource.RepositoryImpl;
+import org.osgi.impl.bundle.obr.resource.ResourceImpl;
+import org.osgi.impl.bundle.obr.resource.Tag;
+
+/**
+ * Iterate over a set of given bundles and convert them to resources. When -a is
+ * specified, other resources than bundles will be included too. After
+ * this, convert an local urls (file systems, JAR file) to relative URLs and
+ * create a ZIP file with the complete content. This ZIP file can be used in an
+ * OSGi Framework to map to an http service or it can be expanded on the web
+ * server's file system.
+ *
+ * @version $Revision: 44 $
+ */
+public class Index
+{
+	static String			repositoryFileName	= "repository.xml";
+	static URL				licenseURL			= null;
+	static boolean			quiet				= false;
+	static String			name				= "Untitled";
+    static boolean          all                 = false;
+	static String			urlTemplate			= null;
+	static File				rootFile			= new File("")
+														.getAbsoluteFile();
+	static RepositoryImpl	repository;
+	static String			root;
+
+	/**
+	 * Main entry. See -help for options.
+	 *
+	 * @param args
+	 * @throws Exception
+	 */
+	public static void main(String args[]) throws Exception {
+		System.err.println("Bundle Indexer | v2.2");
+		System.err.println("(c) 2007 OSGi, All Rights Reserved");
+
+		Set resources = new HashSet();
+		root = rootFile.toURL().toString();
+		repository = new RepositoryImpl(rootFile.toURL());
+
+		for (int i = 0; i < args.length; i++) {
+            try {
+				if (args[i].startsWith("-n")) {
+                    name = args[++i];
+                }
+                else
+					if (args[i].startsWith("-r")) {
+						repositoryFileName = args[++i];
+						repository = new RepositoryImpl(new File(
+								repositoryFileName).getAbsoluteFile().toURL());
+					}
+					else
+						if (args[i].startsWith("-q")) {
+                            quiet = true;
+                        }
+                        else
+							if (args[i].startsWith("-t")) {
+                                urlTemplate = args[++i];
+                            }
+                            else
+								if (args[i].startsWith("-l")) {
+									licenseURL = new URL(new File("").toURL(),
+											args[++i]);
+								}
+								else
+									if (args[i].startsWith("-help")) {
+										System.err
+												.println("bindex [-t \"%s\" symbolic name \"%v\" version \"%f\" filename \"%p\" dirpath ] [ -r repository.(xml|zip) ] [-help] [-l file:license.html ] [-quiet] [-all] <jar file>*");
+									}
+									else
+									    if (args[i].startsWith("-a")) {
+                                            all = true;
+                                        }
+                                        else {
+	    									recurse(resources, new File(args[i]));
+		    							}
+			}
+			catch (Exception e) {
+				System.err.println("Error in " + args[i] + " : " +
+						e.getMessage());
+				e.printStackTrace();
+			}
+        }
+
+		List sorted = new ArrayList(resources);
+		Collections.sort(sorted, new Comparator() {
+			public int compare(Object r1, Object r2) {
+				String s1 = getName((ResourceImpl) r1);
+				String s2 = getName((ResourceImpl) r2);
+				return s1.compareTo(s2);
+			}
+		});
+
+		Tag tag = doIndex(sorted);
+		if (repositoryFileName != null) {
+			ByteArrayOutputStream out = new ByteArrayOutputStream();
+			PrintWriter pw = new PrintWriter(new OutputStreamWriter(out,
+					"UTF-8"));
+
+			pw.println("<?xml version='1.0' encoding='utf-8'?>");
+			pw
+					.println("<?xml-stylesheet type='text/xsl' href='http://www2.osgi.org/www/obr2html.xsl'?>");
+
+			tag.print(0, pw);
+			pw.close();
+			byte buffer[] = out.toByteArray();
+			String name = "repository.xml";
+			FileOutputStream fout = new FileOutputStream(repositoryFileName);
+
+			if (repositoryFileName.endsWith(".zip")) {
+				ZipOutputStream zip = new ZipOutputStream(fout);
+				CRC32 checksum = new CRC32();
+				checksum.update(buffer);
+				ZipEntry ze = new ZipEntry(name);
+				ze.setSize(buffer.length);
+				ze.setCrc(checksum.getValue());
+				zip.putNextEntry(ze);
+				zip.write(buffer, 0, buffer.length);
+				zip.closeEntry();
+				zip.close();
+			}
+			else {
+				fout.write(buffer);
+			}
+			fout.close();
+		}
+
+		if (!quiet) {
+			PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
+			pw.println("<?xml version='1.0' encoding='utf-8'?>");
+			pw
+					.println("<?xml-stylesheet type='text/xsl' href='http://www2.osgi.org/www/obr2html.xsl'?>");
+			tag.print(0, pw);
+			pw.close();
+		}
+	}
+
+	static String getName(ResourceImpl impl) {
+		String s = impl.getSymbolicName();
+		if (s != null) {
+            return s;
+        }
+        else {
+			return "no-symbolic-name";
+		}
+	}
+
+	static void recurse(Set resources, File path) throws Exception {
+		if (path.isDirectory()) {
+			String list[] = path.list();
+			for (int i = 0; i < list.length; i++) {
+				recurse(resources, new File(path, list[i]));
+			}
+		}
+		else {
+		    if (path.getName().equals("repository.xml") || path.getName().equals(new File(repositoryFileName).getName())) {
+		        // do not index our repository.xml, nor the file we are working on now.
+		        return;
+		    }
+			if (path.getName().endsWith(".jar")) {
+				BundleInfo info = new BundleInfo(repository, path);
+				ResourceImpl resource = info.build();
+				if (urlTemplate != null) {
+					doTemplate(path, resource);
+				}
+                else {
+                    resource.setURL(path.toURL());
+                }
+
+				resources.add(resource);
+			}
+			else {
+			    // this is some other resource, we might want to include it.
+			    if (all) {
+			        resources.add(new ResourceImpl(repository, path.toURL()));
+			    }
+			}
+		}
+	}
+
+	static void doTemplate(File path, ResourceImpl resource)
+			throws MalformedURLException {
+		String dir = path.getAbsoluteFile().getParentFile().getAbsoluteFile()
+				.toURL().toString();
+		if (dir.endsWith("/")) {
+            dir = dir.substring(0, dir.length() - 1);
+        }
+
+		if (dir.startsWith(root)) {
+            dir = dir.substring(root.length());
+        }
+
+		String url = urlTemplate.replaceAll("%v", "" + resource.getVersion());
+		url = url.replaceAll("%s", resource.getSymbolicName());
+		url = url.replaceAll("%f", path.getName());
+		url = url.replaceAll("%p", dir);
+		resource.setURL(new URL(url));
+	}
+
+	/**
+	 * Create the repository index
+	 *
+	 * @param resources Set of resources
+	 * @param collected The output zip file
+	 * @throws java.io.IOException
+	 */
+	static Tag doIndex(Collection resources) throws IOException {
+		Tag repository = new Tag("repository");
+		repository.addAttribute("lastmodified", new Date());
+		repository.addAttribute("name", name);
+
+		for (Iterator i = resources.iterator(); i.hasNext();) {
+			ResourceImpl resource = (ResourceImpl) i.next();
+			repository.addContent(resource.toXML());
+		}
+		return repository;
+	}
+
+	/**
+	 * Add the resource to the ZIP file, calculating the CRC etc.
+	 *
+	 * @param zip The output ZIP file
+	 * @param name The name of the resource
+	 * @param actual The contents stream
+	 * @throws java.io.IOException
+	 */
+	static void addToZip(ZipOutputStream zip, String name, InputStream actual)
+			throws IOException {
+		byte buffer[];
+		buffer = readAll(actual, 0);
+		actual.close();
+		CRC32 checksum = new CRC32();
+		checksum.update(buffer);
+		ZipEntry ze = new ZipEntry(name);
+		ze.setSize(buffer.length);
+		ze.setCrc(checksum.getValue());
+		zip.putNextEntry(ze);
+		zip.write(buffer, 0, buffer.length);
+		zip.closeEntry();
+	}
+
+	/**
+	 * Read a complete stream till EOF. This method will parse the input stream
+	 * until a -1 is discovered.
+	 *
+	 * The method is recursive. It keeps on calling a higher level routine until
+	 * EOF. Only then is the result buffer calculated.
+	 */
+	static byte[] readAll(InputStream in, int offset) throws IOException {
+		byte temp[] = new byte[4096];
+		byte result[];
+		int size = in.read(temp, 0, temp.length);
+		if (size <= 0) {
+            return new byte[offset];
+        }
+		//
+		// We have a positive result, copy it
+		// to the right offset.
+		//
+		result = readAll(in, offset + size);
+		System.arraycopy(temp, 0, result, offset, size);
+		return result;
+	}
+
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/packageinfo?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/packageinfo (added)
+++ ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/bindex/packageinfo Tue Apr  2 13:26:43 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/BundleInfo.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/BundleInfo.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/BundleInfo.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/BundleInfo.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,506 @@
+/*
+ * $Id: BundleInfo.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $
+ * 
+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.
+ * 
+ * Licensed 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.osgi.impl.bundle.obr.resource;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import java.util.zip.*;
+
+import org.osgi.service.obr.Resource;
+
+/**
+ * Convert a bundle to a generic resource description and store its local
+ * dependencies (like for example a license file in the JAR) in a zip file.
+ * 
+ * @version $Revision: 44 $
+ */
+public class BundleInfo {
+	Manifest	manifest;
+	File		bundleJar;
+	ZipFile		jar;
+	String		license;
+	Properties	localization;
+	RepositoryImpl	repository;
+
+	/**
+	 * Parse a zipFile from the file system. We only need the manifest and the
+	 * localization. So a zip file is used to minimze memory consumption.
+	 * 
+	 * @param bundleJar Path name
+	 * @throws Exception Any errors that occur
+	 */
+	public BundleInfo(RepositoryImpl repository, File bundleJar) throws Exception {
+		this.bundleJar = bundleJar;
+		this.repository = repository;
+		
+		if (!this.bundleJar.exists())
+			throw new FileNotFoundException(bundleJar.toString());
+
+		jar = new ZipFile(bundleJar);
+		ZipEntry entry = jar.getEntry("META-INF/MANIFEST.MF");
+		if (entry == null)
+			throw new FileNotFoundException("No Manifest in "
+					+ bundleJar.toString());
+		manifest = new Manifest(jar.getInputStream(entry));
+	}
+
+	public BundleInfo(Manifest manifest) throws Exception {
+		this.manifest = manifest;
+	}
+
+	/**
+	 * Convert the bundle to a Resource. All URIs are going to be abslute, but
+	 * could be local.
+	 * 
+	 * @return the resource
+	 * @throws Exception
+	 */
+	public ResourceImpl build() throws Exception {
+		ResourceImpl resource;
+		// Setup the manifest
+		// and create a resource
+		resource = new ResourceImpl(repository, manifest.getSymbolicName(), manifest
+				.getVersion());
+
+		try {
+
+			// Calculate the location URL of the JAR
+			URL location = new URL("jar:" + bundleJar.toURL().toString() + "!/");
+			resource.setURL(bundleJar.toURL());
+			resource.setFile(bundleJar);
+
+			doReferences(resource, location);
+			doSize(resource);
+			doCategories(resource);
+			doImportExportServices(resource);
+			doDeclarativeServices(resource);
+			doFragment(resource);
+			doRequires(resource);
+			doBundle(resource);
+			doExports(resource);
+			doImports(resource);
+			doExecutionEnvironment(resource);
+
+			return resource;
+		}
+		finally {
+			try {
+				jar.close();
+			}
+			catch (Exception e) {
+				// ignore
+			}
+		}
+	}
+
+	/**
+	 * Check the size and add it.
+	 * 
+	 * @param resource
+	 */
+	void doSize(ResourceImpl resource) {
+		long size = bundleJar.length();
+		if (size > 0)
+			resource.setSize(size);
+	}
+
+	/**
+	 * Find the categories, break them up and add them.
+	 * 
+	 * @param resource
+	 */
+	void doCategories(ResourceImpl resource) {
+		for (int i = 0; i < manifest.getCategories().length; i++) {
+			String category = manifest.getCategories()[i];
+			resource.addCategory(category);
+		}
+	}
+
+	void doReferences(ResourceImpl resource, URL location) {
+		// Presentation name
+		String name = translated("Bundle-Name");
+		if (name != null)
+			resource.setPresentationName(name);
+
+		// Handle license. -l allows a global license
+		// set when no license is included.
+
+		String license = translated("Bundle-License");
+		if (license != null)
+			resource.setLicense(toURL(location, license));
+		else if (this.license != null)
+			resource.setLicense(toURL(location, this.license));
+
+		String description = translated("Bundle-Description");
+		if (description != null)
+			resource.setDescription(description);
+
+		String copyright = translated("Bundle-Copyright");
+		if (copyright != null)
+			resource.setCopyright(copyright);
+
+		String documentation = translated("Bundle-DocURL");
+		if (documentation != null)
+			resource.setDocumentation(toURL(location, documentation));
+
+		String source = manifest.getValue("Bundle-Source");
+		if (source != null)
+			resource.setSource(toURL(location, source));
+	}
+
+	URL toURL(URL location, String source) {
+		try {
+			return new URL(location, source);
+		}
+		catch (Exception e) {
+			System.err.println("Error in converting url: " + location + " : "
+					+ source);
+			return null;
+		}
+	}
+
+	void doDeclarativeServices(ResourceImpl resource) throws Exception {
+		String serviceComponent = manifest.getValue("service-component");
+		if (serviceComponent == null)
+			return;
+
+		StringTokenizer st = new StringTokenizer(serviceComponent, " ,\t");
+		String parts[] = new String[st.countTokens()];
+		for (int i = 0; i < parts.length; i++)
+			parts[i] = st.nextToken();
+
+		for (int i = 0; i < parts.length; i++) {
+			ZipEntry entry = jar.getEntry(parts[i]);
+			if (entry == null) {
+				System.err.println("Bad Service-Component header: "
+						+ serviceComponent + ", no such file " + parts[i]);
+			}
+			InputStream in = jar.getInputStream(entry);
+			// TODO parse declarative services files.
+			in.close();
+		}
+	}
+
+	void doImportExportServices(ResourceImpl resource) throws IOException {
+		String importServices = manifest.getValue("import-service");
+		if (importServices != null) {
+			List entries = manifest.getEntries(importServices);
+			for (Iterator i = entries.iterator(); i.hasNext();) {
+				ManifestEntry entry = (ManifestEntry) i.next();
+				RequirementImpl ri = new RequirementImpl("service");
+				ri.setFilter(createServiceFilter(entry));
+				ri.setComment("Import Service " + entry.getName());
+
+				// TODO the following is arbitrary
+				ri.setOptional(false);
+				ri.setMultiple(true);
+				resource.addRequirement(ri);
+			}
+		}
+
+		String exportServices = manifest.getValue("export-service");
+		if (exportServices != null) {
+			List entries = manifest.getEntries(exportServices);
+			for (Iterator i = entries.iterator(); i.hasNext();) {
+				ManifestEntry entry = (ManifestEntry) i.next();
+				CapabilityImpl cap = createServiceCapability(entry);
+				resource.addCapability(cap);
+			}
+		}
+	}
+
+	String translated(String key) {
+		return translate(manifest.getValue(key));
+	}
+
+	void doFragment(ResourceImpl resource) {
+		// Check if we are a fragment
+		ManifestEntry entry = manifest.getHost();
+		if (entry == null) {
+			return;
+		}
+		else {
+			// We are a fragment, create a requirement
+			// to our host.
+			RequirementImpl r = new RequirementImpl("bundle");
+			StringBuffer sb = new StringBuffer();
+			sb.append("(&(symbolicname=");
+			sb.append(entry.getName());
+			sb.append(")(version>=");
+			sb.append(entry.getVersion());
+			sb.append("))");
+			r.setFilter(sb.toString());
+			r.setComment("Required Host " + entry.getName() );
+			r.setExtend(true);
+			r.setOptional(false);
+			r.setMultiple(false);
+			resource.addRequirement(r);
+
+			// And insert a capability that we are available
+			// as a fragment. ### Do we need that with extend?
+			CapabilityImpl capability = new CapabilityImpl("fragment");
+			capability.addProperty("host", entry.getName());
+			capability.addProperty("version", entry.getVersion());
+			resource.addCapability(capability);
+		}
+	}
+
+	void doRequires(ResourceImpl resource) {
+		List entries = manifest.getRequire();
+		if (entries == null)
+			return;
+
+		for (Iterator i = entries.iterator(); i.hasNext();) {
+			ManifestEntry entry = (ManifestEntry) i.next();
+			RequirementImpl r = new RequirementImpl("bundle");
+
+			StringBuffer sb = new StringBuffer();
+			sb.append("(&(symbolicname=");
+			sb.append(entry.getName());
+			sb.append(")(version>=");
+			sb.append(entry.getVersion());
+			sb.append("))");
+			r.setFilter(sb.toString());
+			r.setComment("Require Bundle " + entry.getName() + "; "
+					+ entry.getVersion());
+			if (entry.directives == null
+					|| "true".equalsIgnoreCase((String) entry.directives
+							.get("resolution")))
+				r.setOptional(false);
+			else
+				r.setOptional(true);
+			resource.addRequirement(r);
+		}
+	}
+
+	void doExecutionEnvironment(ResourceImpl resource) {
+		String[] parts = manifest.getRequiredExecutionEnvironments();
+		if (parts == null)
+			return;
+
+		StringBuffer sb = new StringBuffer();
+		sb.append("(|");
+		for (int i = 0; i < parts.length; i++) {
+			String part = parts[i];
+			sb.append("(ee=");
+			sb.append(part);
+			sb.append(")");
+		}
+		sb.append(")");
+
+		RequirementImpl req = new RequirementImpl("ee");
+		req.setFilter(sb.toString());
+		req.setComment("Execution Environment " + sb.toString());
+		resource.addRequirement(req);
+	}
+
+	void doImports(ResourceImpl resource) {
+		List requirements = new ArrayList();
+		List packages = manifest.getImports();
+		if (packages == null)
+			return;
+
+		for (Iterator i = packages.iterator(); i.hasNext();) {
+			ManifestEntry pack = (ManifestEntry) i.next();
+			RequirementImpl requirement = new RequirementImpl("package");
+
+			createImportFilter(requirement, "package", pack);
+			requirement.setComment("Import package " + pack);
+			requirements.add(requirement);
+		}
+		for (Iterator i = requirements.iterator(); i.hasNext();)
+			resource.addRequirement((RequirementImpl) i.next());
+	}
+
+	String createServiceFilter(ManifestEntry pack) {
+		StringBuffer filter = new StringBuffer();
+		filter.append("(service=");
+		filter.append(pack.getName());
+		filter.append(")");
+		return filter.toString();
+	}
+
+	void createImportFilter(RequirementImpl req, String name, ManifestEntry pack) {
+		StringBuffer filter = new StringBuffer();
+		filter.append("(&(");
+		filter.append(name);
+		filter.append("=");
+		filter.append(pack.getName());
+		filter.append(")");
+		VersionRange version = pack.getVersion();
+		if (version != null) {
+			if ( version.isRange() ) {
+				filter.append("(version");
+				filter.append(">");
+				if (version.includeLow())
+					filter.append("=");
+				filter.append(version.low);
+				filter.append(")");
+
+				filter.append("(version");
+				filter.append("<");
+				if (version.includeHigh())
+					filter.append("=");
+				filter.append(version.high);
+				filter.append(")");
+			}
+			else {
+				filter.append("(version>=");
+				filter.append(pack.getVersion());
+				filter.append(")");
+			}
+		}
+		Map attributes = pack.getAttributes();
+		Set attrs = doImportPackageAttributes(req, filter, attributes);
+		if (attrs.size() > 0) {
+			String del = "";
+			filter.append("(mandatory:<*");
+			for (Iterator i = attrs.iterator(); i.hasNext();) {
+				filter.append(del);
+				filter.append(i.next());
+				del = ", ";
+			}
+			filter.append(")");
+		}
+		filter.append(")");
+		req.setFilter(filter.toString());
+	}
+
+	Set doImportPackageAttributes(RequirementImpl req, StringBuffer filter,
+			Map attributes) {
+		HashSet set = new HashSet();
+
+		if (attributes != null)
+			for (Iterator i = attributes.keySet().iterator(); i.hasNext();) {
+				String attribute = (String) i.next();
+				String value = (String) attributes.get(attribute);
+				if (attribute.equalsIgnoreCase("specification-version")
+						|| attribute.equalsIgnoreCase("version"))
+					continue;
+				else if (attribute.equalsIgnoreCase("resolution:")) {
+					req.setOptional(value.equalsIgnoreCase("optional"));
+				}
+				if (attribute.endsWith(":")) {
+					// Ignore
+				}
+				else {
+					filter.append("(");
+					filter.append(attribute);
+					filter.append("=");
+					filter.append(attributes.get(attribute));
+					filter.append(")");
+					set.add(attribute);
+				}
+			}
+		return set;
+	}
+
+	void doBundle(ResourceImpl resource) {
+		CapabilityImpl capability = new CapabilityImpl("bundle");
+		capability.addProperty("symbolicname", manifest.getSymbolicName());
+		if (manifest.getValue("Bundle-Name") != null)
+			capability.addProperty(
+					Resource.PRESENTATION_NAME,
+					translated("Bundle-Name"));
+		capability.addProperty("version", manifest.getVersion());
+		capability
+				.addProperty("manifestversion", manifest.getManifestVersion());
+
+		/**
+		 * Is this needed TODO
+		 */
+		ManifestEntry host = manifest.getHost();
+		if (host != null) {
+			capability.addProperty("host", host.getName());
+			if (host.getVersion() != null)
+				capability.addProperty("version", host.getVersion());
+		}
+		resource.addCapability(capability);
+	}
+
+	void doExports(ResourceImpl resource) {
+		List capabilities = new ArrayList();
+		List packages = manifest.getExports();
+		if (packages != null) {
+			for (Iterator i = packages.iterator(); i.hasNext();) {
+				ManifestEntry pack = (ManifestEntry) i.next();
+				CapabilityImpl capability = createCapability("package", pack);
+				capabilities.add(capability);
+			}
+        }
+		for (Iterator i = capabilities.iterator(); i.hasNext();)
+			resource.addCapability((CapabilityImpl) i.next());
+	}
+
+	CapabilityImpl createServiceCapability(ManifestEntry pack) {
+		CapabilityImpl capability = new CapabilityImpl("service");
+		capability.addProperty("service", pack.getName());
+		return capability;
+	}
+
+	CapabilityImpl createCapability(String name, ManifestEntry pack) {
+		CapabilityImpl capability = new CapabilityImpl(name);
+		capability.addProperty(name, pack.getName());
+		capability.addProperty("version", pack.getVersion());
+		Map attributes = pack.getAttributes();
+		if (attributes != null)
+			for (Iterator at = attributes.keySet().iterator(); at.hasNext();) {
+				String key = (String) at.next();
+				if (key.equalsIgnoreCase("specification-version")
+						|| key.equalsIgnoreCase("version"))
+					continue;
+				else {
+					Object value = attributes.get(key);
+					capability.addProperty(key, value);
+				}
+			}
+		return capability;
+	}
+
+	String translate(String s) {
+		if (s == null)
+			return null;
+
+		if (!s.startsWith("%")) {
+			return s;
+		}
+
+		if (localization == null)
+			try {
+				localization = new Properties();
+				String path = manifest
+						.getValue("Bundle-Localization", "bundle");
+				path += ".properties";
+				InputStream in = jar.getInputStream(new ZipEntry(path));
+				if (in != null) {
+					localization.load(in);
+					in.close();
+				}
+			}
+			catch (IOException e) {
+				e.printStackTrace();
+			}
+		s = s.substring(1);
+		return localization.getProperty(s, s);
+	}
+
+	File getZipFile() {
+		return bundleJar;
+	}
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java?rev=1463529&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java (added)
+++ ace/trunk/org.apache.ace.obr/src/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java Tue Apr  2 13:26:43 2013
@@ -0,0 +1,109 @@
+/*
+ * $Id: CapabilityImpl.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $
+ * 
+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.
+ * 
+ * Licensed 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.osgi.impl.bundle.obr.resource;
+
+import java.util.*;
+
+import org.osgi.service.obr.Capability;
+import org.xmlpull.v1.XmlPullParser;
+
+
+
+public class CapabilityImpl implements Capability {
+	String				name;
+	Map	properties	= new TreeMap();
+
+	public CapabilityImpl(String name) {
+		this.name = name;
+	}
+
+	public CapabilityImpl(XmlPullParser parser) throws Exception {
+		parser.require(XmlPullParser.START_TAG, null, "capability");
+		name = parser.getAttributeValue(null,"name");
+		while ( parser.nextTag() == XmlPullParser.START_TAG ) {
+			if ( parser.getName().equals("p")) {
+				String name = parser.getAttributeValue(null,"n");
+				String value = parser.getAttributeValue(null,"v");
+				String type = parser.getAttributeValue(null,"t");
+				Object v = value;
+
+				if ( "nummeric".equals(type))
+					v = new Long(value);
+				else if ( "version".equals(type))
+					v = new VersionRange(value);
+				addProperty(name,v);
+			}
+			parser.next();
+			parser.require(XmlPullParser.END_TAG, null, "p" );
+		}
+		parser.require(XmlPullParser.END_TAG, null, "capability" );
+	}
+
+
+	public void addProperty(String key, Object value) {
+		List values = (List) properties.get(key);
+		if (values == null) {
+			values = new ArrayList();
+			properties.put(key, values);
+		}
+		values.add(value);
+	}
+
+	public Tag toXML() {
+		return toXML(this);
+	}
+	
+	public static Tag toXML(Capability capability) {
+		Tag tag = new Tag("capability");
+		tag.addAttribute("name", capability.getName());
+		Map properties = capability.getProperties();
+		for ( Iterator k= properties.keySet().iterator(); k.hasNext(); ) {
+			String key = (String) k.next();
+			List values = (List) properties.get(key);
+			for ( Iterator v = values.iterator(); v.hasNext(); ) {
+				Object value = v.next();
+				Tag p = new Tag("p");
+				tag.addContent(p);
+				p.addAttribute("n", key);
+				if ( value != null )
+					p.addAttribute("v", value.toString());
+				else
+					System.out.println("Missing value " + key);
+				String type = null;
+				if (value instanceof Number )
+					type = "number";
+				else if (value.getClass() == VersionRange.class)
+					type = "version";
+				if (type != null)
+					p.addAttribute("t", type);
+			}
+		}
+		return tag;
+	}
+
+
+	public String getName() {
+		return name;
+	}
+
+
+	public Map getProperties() {
+		return properties;
+	}
+
+}