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 2009/06/27 17:53:26 UTC

svn commit: r788992 [14/25] - in /incubator/ace/trunk: gateway/ gateway/src/ gateway/src/net/ gateway/src/net/luminis/ gateway/src/net/luminis/liq/ gateway/src/net/luminis/liq/bootstrap/ gateway/src/net/luminis/liq/bootstrap/multigateway/ gateway/src/n...

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/StreamGeneratorImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/StreamGeneratorImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/StreamGeneratorImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/StreamGeneratorImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,240 @@
+package net.luminis.liq.deployment.streamgenerator.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.SoftReference;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import net.luminis.liq.deployment.provider.ArtifactData;
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+import net.luminis.liq.deployment.streamgenerator.StreamGenerator;
+
+/**
+ * Stream generator for deployment packages. Communicates with a data provider to get the meta data for the streams. Part of the
+ * meta
+ */
+public class StreamGeneratorImpl implements StreamGenerator {
+    private volatile DeploymentProvider m_provider;
+
+    /**
+     * Returns an input stream with the requested deployment package.
+     *
+     * @param id the ID of the package
+     * @param version the version of the package
+     * @return an input stream
+     * @throws IOException when the stream could not be generated
+     */
+    public InputStream getDeploymentPackage(String id, String version) throws IOException {
+        //return execute(new Worker(id, version));
+        List<ArtifactData> data = m_provider.getBundleData(id, version);
+        Manifest manifest = new Manifest();
+        Attributes main = manifest.getMainAttributes();
+
+        main.putValue("Manifest-Version", "1.0");
+        main.putValue("DeploymentPackage-SymbolicName", id);
+        main.putValue("DeploymentPackage-Version", version);
+
+        // Note: getEntries() returns a map. This means that the order of the entries
+        // in the manifest is _not_ defined; this should be fine, as far as the
+        // deployment admin spec goes.
+        for (ArtifactData bd : data) {
+            manifest.getEntries().put(bd.getFilename(), bd.getManifestAttributes(false));
+        }
+
+        return DeploymentPackageStream.createStreamForThread(manifest, data.iterator(), false);
+    }
+
+    /**
+     * Returns an input stream with the requested deployment fix package.
+     *
+     * @param id the ID of the package.
+     * @param fromVersion the version of the target.
+     * @param toVersion the version the target should be in after applying the package.
+     * @return an input stream.
+     * @throws IOException when the stream could not be generated.
+     */
+    public InputStream getDeploymentPackage(String id, String fromVersion, String toVersion) throws IOException {
+        //return execute(new WorkerFixPackage(id, fromVersion, toVersion));
+        List<ArtifactData> data = m_provider.getBundleData(id, fromVersion, toVersion);
+        Manifest manifest = new Manifest();
+        Attributes main = manifest.getMainAttributes();
+
+        main.putValue("Manifest-Version", "1.0");
+        main.putValue("DeploymentPackage-SymbolicName", id);
+        main.putValue("DeploymentPackage-Version", toVersion);
+        main.putValue("DeploymentPackage-FixPack", "[" + fromVersion + "," + toVersion + ")");
+
+        for (ArtifactData bd : data) {
+            manifest.getEntries().put(bd.getFilename(), bd.getManifestAttributes(true));
+        }
+
+        return DeploymentPackageStream.createStreamForThread(manifest, data.iterator(), true);
+    }
+
+    private static final class DeploymentPackageStream extends InputStream {
+        private byte[] m_readBuffer;
+
+        private byte[] m_buffer;
+
+        private final OutputBuffer m_outputBuffer = new OutputBuffer(this);
+
+        private JarOutputStream m_output;
+
+        private Iterator<ArtifactData> m_iter;
+
+        private InputStream m_current = null;
+
+        private int m_pos = 0;
+
+        private int m_max = 0;
+
+        private boolean m_fixPack;
+
+        private DeploymentPackageStream() {
+            this(64 * 1024);
+        }
+
+        private DeploymentPackageStream(int bufferSize) {
+            m_buffer = new byte[bufferSize];
+            m_readBuffer = new byte[bufferSize];
+        }
+
+        private static final ThreadLocal<SoftReference<DeploymentPackageStream>> m_cache = new ThreadLocal<SoftReference<DeploymentPackageStream>>();
+
+        static DeploymentPackageStream createStreamForThread(Manifest man, Iterator<ArtifactData> iter, boolean fixpack) throws IOException {
+            SoftReference<DeploymentPackageStream> ref = m_cache.get();
+            DeploymentPackageStream dps = null;
+            if (ref != null) {
+                dps = ref.get();
+            }
+
+            if (dps == null) {
+                dps = new DeploymentPackageStream();
+                m_cache.set(new SoftReference<DeploymentPackageStream>(dps));
+            }
+
+            if (dps.isInUse()) {
+                dps = new DeploymentPackageStream();
+            }
+
+            dps.init(man, iter, fixpack);
+
+            return dps;
+        }
+
+        private boolean isInUse() {
+            return m_output == null;
+        }
+
+        private void init(Manifest man, Iterator<ArtifactData> iter, boolean fixPack) throws IOException {
+            m_max = 0;
+            m_pos = 0;
+            m_output = new JarOutputStream(m_outputBuffer, man);
+            m_output.flush();
+            m_iter = iter;
+            m_fixPack = fixPack;
+            next();
+        }
+
+        private void next() throws IOException {
+            ArtifactData current = (m_iter.hasNext()) ? m_iter.next() : null;
+
+            if (current == null) {
+                m_output.close();
+            }
+            else if (!m_fixPack || current.hasChanged()) {
+                m_current = current.getUrl().openStream();
+                m_output.putNextEntry(new ZipEntry(current.getFilename()));
+            }
+            else {
+                next();
+            }
+        }
+
+        @Override
+        public int read() throws IOException {
+            while (m_pos == m_max) {
+                if (m_current == null) {
+                    if (m_output != null) {
+                        m_output.close();
+                    }
+                    m_output = null;
+                    m_iter = null;
+                    return -1;
+                }
+                m_pos = 0;
+                m_max = 0;
+                int len = m_current.read(m_readBuffer);
+                if (len != -1) {
+                    m_output.write(m_readBuffer, 0, len);
+                    m_output.flush();
+                }
+                else {
+                    try {
+                        m_current.close();
+                    }
+                    catch (Exception ex) {
+                        // Not much we can do
+                    }
+                    m_current = null;
+                    m_output.closeEntry();
+                    m_output.flush();
+                    next();
+                }
+            }
+
+            return m_buffer[m_pos++] & 0xFF;
+        }
+
+        void write(int b) {
+            if (m_max == m_buffer.length) {
+                byte[] tmp = new byte[m_buffer.length + 8192];
+                System.arraycopy(m_buffer, 0, tmp, 0, m_buffer.length);
+                m_buffer = tmp;
+            }
+            m_buffer[m_max++] = (byte) b;
+        }
+
+        @Override
+        public void close() {
+            if (m_output != null) {
+                try {
+                    m_output.close();
+                    m_output = null;
+                }
+                catch (Exception ex) {
+                    // Not much we can do
+                }
+            }
+            if (m_current != null) {
+                try {
+                    m_current.close();
+                    m_current = null;
+                }
+                catch (Exception ex) {
+                    // Not much we can do
+                }
+            }
+            m_iter = null;
+        }
+
+        private static final class OutputBuffer extends OutputStream {
+            private final DeploymentPackageStream m_stream;
+
+            public OutputBuffer(DeploymentPackageStream stream) {
+                m_stream = stream;
+            }
+
+            @Override
+            public void write(int b) {
+                m_stream.write(b);
+            }
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/http/listener/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/http/listener/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/http/listener/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/http/listener/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,185 @@
+package net.luminis.liq.http.listener;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServlet;
+
+import net.luminis.liq.http.listener.constants.HttpConstants;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.log.LogService;
+
+/**
+ * Service responsible for registrating HttpServlets at HttpServices.<p>
+ *
+ * When a HttpServlet is being added or removed, the callback methods in this class are being
+ * called via the DependencyManager. A Servlet is being added to all HttpServices currently
+ * available or removed from all available HttpServices.<p>
+ *
+ * In case a HttpService is being added or removed, other callback methods are being called
+ * via the DependencyManager. When a HttpService is added, all previously registered Servlets
+ * are being registered to this new HttpService. In case of removal, all Servlet endpoints are
+ * being removed from the HttpService that is going to be removed.<p>
+ */
+public class Activator extends DependencyActivatorBase {
+
+    private volatile LogService m_log; // injected
+    private final Set<ServiceReference> m_httpServices = new HashSet<ServiceReference>();
+    private final Map<ServiceReference, String> m_servlets = new HashMap<ServiceReference, String>();
+    private BundleContext m_context;
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        m_context = context;
+        manager.add(createService()
+            .setImplementation(this)
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false))
+            .add(createServiceDependency()
+                .setService(HttpService.class)
+                .setAutoConfig(false)
+                .setCallbacks("addHttpService", "removeHttpService"))
+            .add(createServiceDependency()
+                .setService(HttpServlet.class)
+                .setAutoConfig(false)
+                .setCallbacks("addHttpServlet", "changeHttpServlet", "removeHttpServlet")));
+    }
+
+    /**
+     * Callback method used in case a HttpServlet is being added. This Servlet is being added
+     * to all available HttpServices under the endpoint configured via the Configurator.
+     *
+     * @param ref  reference to the Servlet
+     */
+    public synchronized void addHttpServlet(ServiceReference ref) {
+        // register servlet to all HttpServices
+        String endpoint = (String)ref.getProperty(HttpConstants.ENDPOINT);
+        m_servlets.put(ref, endpoint);
+        Servlet servlet = (Servlet)m_context.getService(ref);
+        for (ServiceReference reference : m_httpServices) {
+            HttpService httpService = (HttpService) m_context.getService(reference);
+            try {
+                if ((httpService != null) && (endpoint != null) && (servlet != null)) {
+                    httpService.registerServlet(endpoint, servlet, null, null);
+                }
+                else {
+                    m_log.log(LogService.LOG_WARNING, "Unable to register servlet with endpoint '" + endpoint + "'");
+                }
+            }
+            catch (Exception e) {
+                m_log.log(LogService.LOG_WARNING, "Already registered under existing endpoint", e);
+            }
+        }
+    }
+
+    public synchronized void changeHttpServlet(ServiceReference ref) {
+        removeServlet(ref, m_servlets.get(ref));
+        addHttpServlet(ref);
+    }
+
+    /**
+     * Callback method used in case a HttpServlet is being removed. This Servlet is being removed
+     * from all available HttpServices using the endpoint configured via the Configurator.
+     *
+     * @param ref  reference to the Servlet
+     */
+    public synchronized void removeHttpServlet(ServiceReference ref) {
+        // remove servlet from all HttpServices
+        String endpoint = (String)ref.getProperty(HttpConstants.ENDPOINT);
+        removeServlet(ref, endpoint);
+    }
+
+    private void removeServlet(ServiceReference ref, String endpoint) {
+        m_servlets.remove(ref);
+        for (ServiceReference reference : m_httpServices) {
+            HttpService httpService = (HttpService) m_context.getService(reference);
+            if ((httpService != null) && (endpoint != null)) {
+                try {
+                    httpService.unregister(endpoint);
+                }
+                catch (Exception e) {
+                    m_log.log(LogService.LOG_WARNING, "Servlet cannot be unregistered, maybe not registered under this endpoint", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback method used in case a HttpService is being added. To this Service all previously
+     * registered Servlet are added under the endpoints of the Servlets (which are configured
+     * via the Configurator).
+     *
+     * @param ref  reference to the Service
+     */
+    public synchronized void addHttpService(ServiceReference ref, HttpService httpService) {
+        m_httpServices.add(ref);
+        // register all servlets to this new HttpService
+        for (ServiceReference reference : m_servlets.keySet()) {
+            Servlet servlet = (Servlet)m_context.getService(reference);
+            String endpoint = (String)reference.getProperty(HttpConstants.ENDPOINT);
+            if ((servlet != null) && (endpoint != null)) {
+                try {
+                    httpService.registerServlet(endpoint, servlet, null, null);
+                }
+                catch (Exception e) {
+                    m_log.log(LogService.LOG_WARNING, "Already registered under existing endpoint", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback method used in case a HttpService is being removed. From this Service all previously
+     * registered Servlet are removed using the endpoints of the Servlets (which are configured
+     * via the Configurator).
+     *
+     * @param ref  reference to the Service
+     */
+    public synchronized void removeHttpService(ServiceReference ref, HttpService httpService) {
+        m_httpServices.remove(ref);
+        // remove references from the unregistered HttpService
+        unregisterEndpoints(httpService);
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager arg1) throws Exception {
+        for (ServiceReference httpRef : m_httpServices) {
+            HttpService httpService = (HttpService)m_context.getService(httpRef);
+            if (httpService != null) {
+                unregisterEndpoints(httpService);
+            }
+        }
+        m_httpServices.clear();
+        m_servlets.clear();
+        m_context = null;
+    }
+
+    /**
+     * Unregisters all Servlets (via their endpoints) from the HttpService being passed to
+     * this method.
+     *
+     * @param httpService  the HttpService that is being unregistered
+     */
+    private void unregisterEndpoints(HttpService httpService) {
+        for (ServiceReference reference : m_servlets.keySet()) {
+            String endpoint = (String)reference.getProperty(HttpConstants.ENDPOINT);
+            if (endpoint != null) {
+                try {
+                    httpService.unregister(endpoint);
+                }
+                catch (Exception e) {
+                    m_log.log(LogService.LOG_WARNING, "Servlet cannot be unregistered, maybe not registered under this endpoint", e);
+                }
+            }
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/http/listener/constants/HttpConstants.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/http/listener/constants/HttpConstants.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/http/listener/constants/HttpConstants.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/http/listener/constants/HttpConstants.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,8 @@
+package net.luminis.liq.http.listener.constants;
+
+public interface HttpConstants {
+    /**
+     * Endpoint constant to be used by several Servlet bundles.
+     */
+    public static final String ENDPOINT = "net.luminis.liq.server.servlet.endpoint";
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/LocationService.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/LocationService.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/LocationService.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/LocationService.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,9 @@
+package net.luminis.liq.location;
+
+import java.net.URL;
+
+public interface LocationService {
+	public URL getLocation();
+	public String getServerType();
+	public int getServerLoad();
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,47 @@
+package net.luminis.liq.location.upnp;
+
+import net.luminis.liq.location.LocationService;
+import net.luminis.liq.location.upnp.util.HostUtil;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.upnp.UPnPDevice;
+
+public class Activator extends DependencyActivatorBase {
+
+
+	@Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+
+	    //we need these to construct the actual presentation url for the service
+		int port = Integer.valueOf(context.getProperty("org.osgi.service.http.port"));
+		String host = HostUtil.getHost();
+
+		ProvisioningDevice psDevice = new ProvisioningDevice(host, port);
+
+
+
+		//this service is configured with the correct settings
+		manager.add(createService()
+		    .setImplementation(new LocationServiceImpl(host, port))
+            .setInterface(LocationService.class.getName(), null)
+            .add(createConfigurationDependency().setPid(LocationServiceImpl.PID))
+		    );
+
+		//this service depends on the highest ranked location service
+		manager.add(createService()
+				.setImplementation(psDevice)
+				.setInterface(UPnPDevice.class.getName(), psDevice.getDescriptions(null))
+				.setComposition("getComposition")
+				.add(createServiceDependency().setService(HttpService.class).setRequired(true))
+                .add(createServiceDependency().setService(LocationService.class).setRequired(true))
+		);
+
+	};
+
+	@Override
+	public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+	}
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/LocationServiceImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/LocationServiceImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/LocationServiceImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/LocationServiceImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,101 @@
+package net.luminis.liq.location.upnp;
+
+import java.net.URL;
+import java.util.Dictionary;
+
+import net.luminis.liq.location.LocationService;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+/**
+ *
+ * the actual implementation for the Location Service.
+ *
+ *
+ * @author dennisg
+ *
+ */
+public class LocationServiceImpl implements LocationService, ManagedService {
+
+
+
+
+
+    public  final static String PID = "net.luminis.liq.location.upnp.LocationService";
+    private final static String ENDPOINT_URL = "endpoint.url";
+    private final static String ENDPOINT_TYPE = "endpoint.type";
+
+
+	private final String m_host;
+
+	private final int m_port;
+
+	private URL m_location;
+
+	private String m_serverType;
+
+
+	public LocationServiceImpl(String host, int port) {
+	    m_host = host;
+	    m_port = port;
+	}
+
+    private String get(Dictionary dict, String key) throws ConfigurationException {
+        Object val = dict.get(key);
+
+        if (val == null) {
+            throw new ConfigurationException(key, "no such key defined");
+        }
+
+        if (val instanceof String) {
+            return (String)val;
+        }
+        throw new ConfigurationException(key, "invalid format (not a String)");
+
+    }
+
+
+    public synchronized void updated(Dictionary dict) throws ConfigurationException {
+
+        if (dict == null) {
+            return;
+        }
+
+        String serverType = get(dict, ENDPOINT_TYPE);
+        String url = get(dict, ENDPOINT_URL);
+
+        URL location = null;
+
+        try {
+            //we expect something like:
+            //http://<host>:<port>/xyz
+
+            url = url.replaceFirst("<host>", m_host);
+            url = url.replaceFirst("<port>", "" + m_port);
+            location = new URL(url);
+
+        }
+        catch (Exception e) {
+            throw new ConfigurationException(null, e.getMessage());
+        }
+
+        //all's well: apply
+        m_serverType = serverType;
+        m_location = location;
+
+    }
+
+	public URL getLocation() {
+		return m_location;
+	}
+
+	public String getServerType() {
+		return m_serverType;
+	}
+
+	public int getServerLoad() {
+		return (int)(40 + Math.random()*10);
+	}
+
+}
\ No newline at end of file

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/ProvisioningDevice.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/ProvisioningDevice.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/ProvisioningDevice.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/ProvisioningDevice.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,144 @@
+package net.luminis.liq.location.upnp;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.util.Dictionary;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.service.http.HttpService;
+import org.osgi.service.upnp.UPnPDevice;
+import org.osgi.service.upnp.UPnPIcon;
+import org.osgi.service.upnp.UPnPService;
+
+public class ProvisioningDevice extends HttpServlet implements UPnPDevice {
+
+	final private String DEVICE_ID = "uuid:" + UUID.randomUUID();
+
+	final static String BASE_URL = "/upnp/provisioningdevice";
+
+	private Properties m_properties;
+
+	//managed
+	private HttpService m_http;
+
+    private final String m_host;
+    private final int m_port;
+
+	private final UPnPLocationServiceWrapper m_wrapper;
+
+	public ProvisioningDevice(String host, int port) throws Exception {
+	    m_host = host;
+	    m_port = port;
+		m_wrapper = new UPnPLocationServiceWrapper();
+
+		setupDeviceProperties();
+	}
+
+	public Object[] getComposition() {
+	    return new Object[]{this, m_wrapper};
+	}
+
+	private void start() throws Exception {
+		m_http.registerServlet(BASE_URL, this, null, null);
+	}
+
+
+	private void stop() {
+		m_http.unregister(BASE_URL);
+	}
+
+	private void setupDeviceProperties() throws MalformedURLException {
+
+		m_properties = new Properties();
+		m_properties.put(UPnPDevice.UPNP_EXPORT,"");
+
+
+		//this is odd, we have to have this
+		// property here, otherwise the device will not be exported
+		//although the docs say otherwise
+		m_properties.put(
+		        org.osgi.service.device.Constants.DEVICE_CATEGORY,
+	        	new String[]{UPnPDevice.DEVICE_CATEGORY}
+	        );
+
+		m_properties.put(UPnPDevice.FRIENDLY_NAME,"UPnP Provisioning Device");
+		m_properties.put(UPnPDevice.MANUFACTURER,"luminis iQ Products");
+		m_properties.put(UPnPDevice.MANUFACTURER_URL,"http://www.luminiq.nl");
+		m_properties.put(UPnPDevice.MODEL_DESCRIPTION,"A Provisioning Device that is automagically locatable by gateways");
+		m_properties.put(UPnPDevice.MODEL_NAME,"DennisG");
+		m_properties.put(UPnPDevice.MODEL_NUMBER,"1.0");
+		m_properties.put(UPnPDevice.MODEL_URL,"https://opensource.luminis.net/subversion/oss/projects/upnp-models/");
+
+        m_properties.put(UPnPDevice.PRESENTATION_URL, "http://" + m_host + ":" + m_port + BASE_URL);
+		m_properties.put(UPnPDevice.SERIAL_NUMBER,"123456789");
+		m_properties.put(UPnPDevice.TYPE, UPnPConstants.PROVISIONING_DEVICE_TYPE);
+		m_properties.put(UPnPDevice.UDN, DEVICE_ID);
+		m_properties.put(UPnPDevice.UPC,"123456789");
+
+
+		m_properties.put(UPnPService.ID, m_wrapper.getId());
+		m_properties.put(UPnPService.TYPE, m_wrapper.getType());
+
+	}
+
+
+	public Dictionary getDescriptions(String name) {
+		return m_properties;
+	}
+
+	public UPnPIcon[] getIcons(String name) {
+	    //sorry, no icons yet
+		return new UPnPIcon[0];
+	}
+
+	public UPnPService getService(String id) {
+		if (m_wrapper.getId().equals(id)) {
+			return m_wrapper;
+		}
+		return null;
+	}
+
+	public UPnPService[] getServices() {
+		return new UPnPService[]{m_wrapper};
+	}
+
+
+
+    @Override
+    public void doGet(HttpServletRequest request, HttpServletResponse response)
+	    throws IOException, ServletException {
+
+	    response.setContentType("text/html");
+	    PrintWriter out = response.getWriter();
+	    out.println("<html>");
+	    out.println("<head><title>Luminis Provisioning Device</title></head>");
+	    out.println("<body>");
+	    out.println("  <center>");
+	    out.println("  <h1><font face='Arial' color='#808080'>Luminis Provisioning Device</font></h1>");
+
+		out.println("  <p><strong>Location:&nbsp;<i>"  + m_wrapper.getLocation()   + "</i></strong</p>");
+		out.println("  <p><strong>Server Type:&nbsp;[" + m_wrapper.getServerType() + "]</strong</p>");
+		out.println("  <p><strong>Server Load:&nbsp;[" + m_wrapper.getServerLoad() + "%]</strong</p>");
+
+	    out.println("  <p><a href=" + BASE_URL + "/>Refresh</a></p>");
+	    out.println("  </center>");
+	    out.println("  </body>");
+	    out.println("</html>");
+	    out.flush();
+	}
+
+
+	@Override
+    public void doPost(HttpServletRequest request, HttpServletResponse response)
+	    throws IOException, ServletException {
+	    doGet(request, response);
+	}
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPConstants.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPConstants.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPConstants.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPConstants.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,8 @@
+package net.luminis.liq.location.upnp;
+
+public interface UPnPConstants {
+
+	public final static String PROVISIONING_DEVICE_TYPE = "urn:schemas-upnp-org:device:ProvisioningDevice:1";
+	public final static String LOCATION_SERVICE_TYPE = "urn:schemas-upnp-org:service:LocationService:1";
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPLocationServiceWrapper.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPLocationServiceWrapper.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPLocationServiceWrapper.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/UPnPLocationServiceWrapper.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,93 @@
+package net.luminis.liq.location.upnp;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.luminis.liq.location.LocationService;
+import net.luminis.liq.location.upnp.actions.GetLocationAction;
+import net.luminis.liq.location.upnp.actions.GetServerLoadAction;
+import net.luminis.liq.location.upnp.actions.GetServerTypeAction;
+
+import org.osgi.service.upnp.UPnPAction;
+import org.osgi.service.upnp.UPnPService;
+import org.osgi.service.upnp.UPnPStateVariable;
+
+public class UPnPLocationServiceWrapper implements UPnPService, LocationService {
+
+	final static private String SERVICE_ID = "urn:upnp-org:serviceId:LocationService:1";
+
+	//managed
+	private volatile LocationService m_ls;
+
+	volatile private Map<String, UPnPAction> m_actions;
+
+
+	public UPnPLocationServiceWrapper() {
+
+		m_actions = new HashMap<String, UPnPAction>();
+
+		UPnPAction location = new GetLocationAction(this);
+		m_actions.put(location.getName(), location);
+
+		UPnPAction type = new GetServerTypeAction(this);
+		m_actions.put(type.getName(), type);
+
+		UPnPAction load = new GetServerLoadAction(this);
+		m_actions.put(load.getName(), load);
+	}
+
+	public URL getLocation() {
+		return m_ls.getLocation();
+	}
+
+	public String getServerType() {
+		return m_ls.getServerType();
+	}
+
+	public int getServerLoad() {
+		return (int)(40 + Math.random()*10);
+	}
+
+
+	public UPnPAction getAction(String actionName) {
+		return m_actions.get(actionName);
+	}
+
+	public UPnPAction[] getActions() {
+		return m_actions.values().toArray(new UPnPAction[0]);
+	}
+
+	public String getId() {
+		return SERVICE_ID;
+	}
+
+	public UPnPStateVariable getStateVariable(String id) {
+		UPnPAction action = m_actions.get(id);
+		if (action != null) {
+			return action.getStateVariable(null);
+		}
+		return null;
+	}
+
+	public UPnPStateVariable[] getStateVariables() {
+		int i=0;
+		UPnPStateVariable[] states = new UPnPStateVariable[m_actions.size()];
+		for (UPnPAction action : m_actions.values()) {
+			states[i++] = action.getStateVariable(null);
+		}
+		return states;
+	}
+
+	public String getType() {
+		return UPnPConstants.LOCATION_SERVICE_TYPE;
+	}
+
+
+	public String getVersion() {
+		return "1";
+	}
+
+
+
+}
\ No newline at end of file

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetLocationAction.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetLocationAction.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetLocationAction.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetLocationAction.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,79 @@
+package net.luminis.liq.location.upnp.actions;
+
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import net.luminis.liq.location.LocationService;
+
+import org.osgi.service.upnp.UPnPAction;
+import org.osgi.service.upnp.UPnPStateVariable;
+
+public class GetLocationAction implements UPnPAction {
+
+	final private String NAME = "GetLocation";
+	final private String RET_TARGET_VALUE = "RetLocationValue";
+	final private String[] OUT_ARG_NAMES = new String[]{RET_TARGET_VALUE};
+	private UPnPStateVariable state;
+
+	private final LocationService m_locationService;
+
+
+	public GetLocationAction(LocationService ls) {
+		m_locationService = ls;
+		state = new StateVarImpl();
+	}
+
+	public String getName() {
+		return NAME;
+	}
+
+	public String getReturnArgumentName() {
+		return RET_TARGET_VALUE;
+	}
+
+	public String[] getInputArgumentNames() {
+		return null;
+	}
+
+	public String[] getOutputArgumentNames() {
+		return OUT_ARG_NAMES;
+	}
+
+	public UPnPStateVariable getStateVariable(String argumentName) {
+		return state;
+	}
+
+	public Dictionary invoke(Dictionary args) throws Exception {
+		URL location = m_locationService.getLocation();
+
+		Hashtable result = new Hashtable();
+		result.put(RET_TARGET_VALUE, location.toExternalForm());
+		return result;
+	}
+
+
+	private class StateVarImpl extends StateVar {
+
+
+		public String getName() {
+			return "Location";
+		}
+
+		public Object getCurrentValue() {
+			URL location =  m_locationService.getLocation();
+			if (location != null) {
+				return location.toString();
+			}
+			return null;
+		}
+
+		public Class getJavaDataType() {
+			return String.class;
+		}
+
+		public String getUPnPDataType() {
+			return TYPE_STRING;
+		}
+	}
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerLoadAction.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerLoadAction.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerLoadAction.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerLoadAction.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,91 @@
+package net.luminis.liq.location.upnp.actions;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import net.luminis.liq.location.LocationService;
+
+import org.osgi.service.upnp.UPnPAction;
+import org.osgi.service.upnp.UPnPStateVariable;
+
+
+public class GetServerLoadAction implements UPnPAction {
+
+	final private String NAME = "GetServerLoad";
+	final private String RET_TARGET_VALUE = "RetServerLoadValue";
+	final private String[] OUT_ARG_NAMES = new String[]{RET_TARGET_VALUE};
+	private UPnPStateVariable state;
+
+	private final LocationService m_locationService;
+
+
+	public GetServerLoadAction(LocationService ls) {
+		m_locationService = ls;
+		state = new StateVarImpl();
+	}
+
+	public String getName() {
+		return NAME;
+	}
+
+	public String getReturnArgumentName() {
+		return RET_TARGET_VALUE;
+	}
+
+	public String[] getInputArgumentNames() {
+
+		return null;
+	}
+
+	public String[] getOutputArgumentNames() {
+		return OUT_ARG_NAMES;
+	}
+
+	public UPnPStateVariable getStateVariable(String argumentName) {
+		return state;
+	}
+
+	public Dictionary invoke(Dictionary args) throws Exception {
+		int load = m_locationService.getServerLoad();
+
+		Hashtable result = new Hashtable();
+		result.put(RET_TARGET_VALUE, load);
+		return result;
+	}
+
+	private class StateVarImpl extends StateVar {
+
+
+		public String getName() {
+			return "ServerLoad";
+		}
+
+		public Object getCurrentValue() {
+			return m_locationService.getServerLoad();
+		}
+
+		public Class getJavaDataType() {
+			return Integer.TYPE;
+		}
+
+		public String getUPnPDataType() {
+			return TYPE_INT;
+		}
+
+        @Override
+        public Number getMinimum() {
+		    return 0;
+		}
+
+        @Override
+        public Number getMaximum() {
+            return 100;
+        }
+
+        @Override
+        public Number getStep() {
+            return 1;
+        }
+	}
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerTypeAction.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerTypeAction.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerTypeAction.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/GetServerTypeAction.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,78 @@
+package net.luminis.liq.location.upnp.actions;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import net.luminis.liq.location.LocationService;
+
+import org.osgi.service.upnp.UPnPAction;
+import org.osgi.service.upnp.UPnPStateVariable;
+
+
+public class GetServerTypeAction implements UPnPAction {
+
+	final private String NAME = "GetServerType";
+	final private String RET_TARGET_VALUE = "RetServerTypeValue";
+	final private String[] OUT_ARG_NAMES = new String[]{RET_TARGET_VALUE};
+	private UPnPStateVariable state;
+
+	private final LocationService m_locationService;
+
+
+	public GetServerTypeAction(LocationService ls) {
+		m_locationService = ls;
+		state = new StateVarImpl();
+
+	}
+
+	public String getName() {
+		return NAME;
+	}
+
+	public String getReturnArgumentName() {
+		return RET_TARGET_VALUE;
+	}
+
+	public String[] getInputArgumentNames() {
+
+		return null;
+	}
+
+	public String[] getOutputArgumentNames() {
+		return OUT_ARG_NAMES;
+	}
+
+	public UPnPStateVariable getStateVariable(String argumentName) {
+		return state;
+	}
+
+	public Dictionary invoke(Dictionary args) throws Exception {
+		String type = m_locationService.getServerType();
+
+		Hashtable result = new Hashtable();
+		result.put(RET_TARGET_VALUE, type);
+		return result;
+	}
+
+
+	private class StateVarImpl extends StateVar {
+
+
+		public String getName() {
+			return "ServerType";
+		}
+
+		public Object getCurrentValue() {
+			return m_locationService.getServerType();
+		}
+
+		public Class getJavaDataType() {
+			return String.class;
+		}
+
+		public String getUPnPDataType() {
+			return TYPE_STRING;
+		}
+	}
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/StateVar.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/StateVar.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/StateVar.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/actions/StateVar.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,30 @@
+package net.luminis.liq.location.upnp.actions;
+
+import org.osgi.service.upnp.UPnPLocalStateVariable;
+
+public abstract class StateVar implements UPnPLocalStateVariable {
+
+	public String[] getAllowedValues() {
+		return null;
+	}
+
+	public Object getDefaultValue() {
+		return null;
+	}
+
+	public Number getMinimum() {
+		return null;
+	}
+
+	public Number getMaximum() {
+		return null;
+	}
+
+	public Number getStep() {
+		return null;
+	}
+
+	public boolean sendsEvents() {
+		return false;
+	}
+}
\ No newline at end of file

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/HostUtil.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/HostUtil.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/HostUtil.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/HostUtil.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,28 @@
+package net.luminis.liq.location.upnp.util;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class HostUtil {
+
+	private HostUtil(){}
+	
+	
+	public static final String getHost() {
+		String host;
+		
+        InetAddress inet;
+		try {
+			inet = InetAddress.getLocalHost();
+	        host = inet.getHostAddress();
+		} catch (UnknownHostException e) {
+			System.out.println("Warning: enable to create host name");
+			host = "localhost";
+		}
+		
+		return host;
+	}
+	
+	
+	
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/Inspector.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/Inspector.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/Inspector.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/location/upnp/util/Inspector.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,105 @@
+package net.luminis.liq.location.upnp.util;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Properties;
+
+import org.osgi.service.upnp.UPnPAction;
+import org.osgi.service.upnp.UPnPService;
+import org.osgi.service.upnp.UPnPStateVariable;
+
+
+public class Inspector {
+
+	
+	
+	private static UPnPService createService(Class iface, Object target) throws Exception {
+		Method[] methods = iface.getMethods();
+		
+		List<UPnPAction> list = new ArrayList<UPnPAction>();
+		for (Method method : methods) {
+			list.add(createAction(method, target));
+		}
+		return null;
+	}
+	
+	
+	
+	private static UPnPAction createAction(Method m, Object target) {
+		return new UPnPActionImpl(m, target);
+	}
+	
+	
+	
+	
+	private static class UPnPActionImpl implements UPnPAction {
+		
+		private final Method m_method;
+		private final Object m_target;
+		
+		public UPnPActionImpl(Method m, Object t) {
+			m_method = m;
+			m_target = t;
+		}
+		
+		
+		public String getName() {
+			return m_method.getName();
+		}
+		
+		public String[] getInputArgumentNames() {
+			Class[] inputArgTypes = m_method.getParameterTypes();
+			String[] inputArgs = new String[inputArgTypes.length];
+			
+			if (inputArgs.length == 1) {
+				inputArgs[0] = inputArgTypes[0].getSimpleName();
+			}
+			else {
+				int i = 0;
+				for (Class inputType : inputArgTypes) {
+					inputArgs[i] = inputArgTypes[i].getSimpleName() + i;
+					i++;
+				}
+			}
+			return inputArgs;
+		}
+		
+		public String[] getOutputArgumentNames() {
+			return new String[]{getReturnArgumentName()};
+		}
+		
+		public String getReturnArgumentName() {
+			Class returnType = m_method.getReturnType();
+			return returnType.getSimpleName();
+		}
+		
+		public UPnPStateVariable getStateVariable(String arg0) {
+			// TODO Auto-generated method stub
+			return null;
+		}
+		
+		public Dictionary invoke(Dictionary dict) throws Exception {
+			String[] argNames = getInputArgumentNames();
+			Object[] args = new Object[argNames.length];
+			
+			int i=0;
+			for (String name : argNames) {
+				args[i++] = dict.get(name);
+			}
+			
+			Object retVal = m_method.invoke(m_target, args);
+			if (retVal==null) {
+				return null;
+			}
+			
+			Properties p = new Properties();
+			p.put(getReturnArgumentName(), retVal);
+			return p;
+		}
+		
+		
+		
+	}
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/MetadataGenerator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/MetadataGenerator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/MetadataGenerator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/MetadataGenerator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,18 @@
+package net.luminis.liq.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 IOException If I/O problems occur when generating the new meta data index file.
+     */
+    public void generateMetadata(File directory) throws IOException;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,26 @@
+package net.luminis.liq.obr.metadata.bindex;
+
+import net.luminis.liq.obr.metadata.MetadataGenerator;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.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(createService()
+            .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
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/BIndexMetadataGenerator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/BIndexMetadataGenerator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/BIndexMetadataGenerator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/metadata/bindex/BIndexMetadataGenerator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,57 @@
+package net.luminis.liq.obr.metadata.bindex;
+
+import java.io.File;
+import java.io.IOException;
+
+import net.luminis.liq.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 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()});
+                // TODO: try to move the index file to it's final location, this can fail if the target
+                // file was not released by a third party before we were called (not all platforms support reading and writing
+                // to a file at the same time), for now we will try 10 times and throw an IOException if the move has not
+                // succeeded by then.
+                boolean renameOK = false;
+                int attempts = 0;
+                while(!renameOK && (attempts < 10)) {
+                    index.delete();
+                    renameOK = tempIndex.renameTo(index);
+                    if (!renameOK) {
+                        attempts++;
+                        Thread.sleep(1000);
+                    }
+                }
+                if (!renameOK) {
+                    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 (" + tempIndex.getAbsolutePath() + ") to it's final location (" + index.getAbsolutePath() + ")");
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_ERROR, "Unable to create temporary file for new repository index.", e);
+                throw e;
+            }
+            catch (InterruptedException e) {
+                m_log.log(LogService.LOG_ERROR, "Waiting for next attempt to move temporary repository index failed.", e);
+            }
+            catch (Exception e) {
+                m_log.log(LogService.LOG_ERROR, "Failed to generate new repository index.", e);
+                throw new IOException("Failed to generate new repository index. + (" + e.getMessage() + ")");
+            }
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,35 @@
+package net.luminis.liq.obr.servlet;
+
+import javax.servlet.http.HttpServlet;
+
+import net.luminis.liq.obr.storage.BundleStore;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+    public static final String PID = "net.luminis.liq.obr.servlet";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createService()
+            .setInterface(HttpServlet.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
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/BundleServlet.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/BundleServlet.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/BundleServlet.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/servlet/BundleServlet.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,185 @@
+package net.luminis.liq.obr.servlet;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.luminis.liq.obr.storage.BundleStore;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+
+public class BundleServlet extends HttpServlet implements ManagedService {
+    public static final String TEXT_MIMETYPE = "text/plain";
+
+    private static final long serialVersionUID = 1L;
+    private static final int COPY_BUFFER_SIZE = 1024;
+
+    private volatile LogService m_log; /* will be injected by dependencymanager */
+    private volatile BundleStore m_store; /* will be injected by dependencymanager */
+
+    /**
+     * 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, HttpServletResponse.SC_BAD_REQUEST);
+        }
+        else {
+            String id = path.substring(1);
+            try {
+                if (m_store.put(id, request.getInputStream())) {
+                    sendResponse(response, HttpServletResponse.SC_OK);
+                }
+                else {
+                    sendResponse(response, HttpServletResponse.SC_CONFLICT);
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_WARNING, "Exception handling request: " + request.getRequestURL(), e);
+                sendResponse(response, HttpServletResponse.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, HttpServletResponse.SC_BAD_REQUEST);
+        }
+        else {
+            String id = path.substring(1);
+            try {
+                if (m_store.remove(id)) {
+                    sendResponse(response, HttpServletResponse.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, HttpServletResponse.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, HttpServletResponse.SC_BAD_REQUEST);
+        }
+        else {
+            String id = path.substring(1);
+            response.setContentType(TEXT_MIMETYPE);
+
+            ServletOutputStream output = null;
+            try {
+                output = response.getOutputStream();
+                InputStream fileStream = null;
+                try {
+                    fileStream = m_store.get(id);
+
+                    if (fileStream != null) {
+                        // send the bundle as stream to the caller
+                        byte[] buffer = new byte[COPY_BUFFER_SIZE];
+                        for (int bytes = fileStream.read(buffer); bytes != -1; bytes = fileStream.read(buffer)) {
+                            output.write(buffer, 0, bytes);
+                        }
+                    }
+                    else {
+                        sendResponse(response, HttpServletResponse.SC_NOT_FOUND);
+                    }
+                }
+                finally {
+                    if (fileStream != null) {
+                        try {
+                            fileStream.close();
+                        }
+                        catch (IOException ioe) {
+                            m_log.log(LogService.LOG_WARNING, "Exception closing the stream" + request.getRequestURL(), ioe);
+                        }
+                    }
+                }
+            }
+            catch (IOException ex) {
+                m_log.log(LogService.LOG_WARNING, "Exception in request: " + request.getRequestURL(), ex);
+                sendResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+            finally {
+                try {
+                    if (output != null) {
+                        output.close();
+                    }
+                }
+                catch (Exception ex) {
+                    m_log.log(LogService.LOG_WARNING, "Exception trying to close stream after request: " + 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);
+        }
+    }
+
+    @Override
+    public String getServletInfo() {
+        return "LiQ OBR Servlet";
+    }
+
+    @SuppressWarnings("unchecked")
+    public void updated(Dictionary settings) throws ConfigurationException {
+        // Nothing needs to be done - handled by DependencyManager
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/BundleStore.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/BundleStore.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/BundleStore.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/BundleStore.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,37 @@
+package net.luminis.liq.obr.storage;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.osgi.service.cm.ManagedService;
+
+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 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 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 IOException If there was a problem removing the data of the resource from the store.
+     */
+    public boolean remove(String filename) throws IOException;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,33 @@
+package net.luminis.liq.obr.storage.file;
+
+import net.luminis.liq.obr.metadata.MetadataGenerator;
+import net.luminis.liq.obr.storage.BundleStore;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+    public static final String PID = "net.luminis.liq.obr.storage.file";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createService()
+            .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
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/BundleFileStore.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/BundleFileStore.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/BundleFileStore.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/BundleFileStore.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,149 @@
+package net.luminis.liq.obr.storage.file;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.luminis.liq.obr.metadata.MetadataGenerator;
+import net.luminis.liq.obr.storage.BundleStore;
+import net.luminis.liq.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 volatile MetadataGenerator m_metadata; /* will be injected by dependencymanager */
+    private volatile LogService m_log; /* will be injected by dependencymanager */
+
+    private final Map<String, Long> m_foundFiles = new ConcurrentHashMap<String, Long>();
+    private volatile File m_dir;
+
+    @SuppressWarnings("unused")
+    private void start() {
+        try {
+            generateMetadata();
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Could not generate initial meta data for bundle repository");
+        }
+    }
+
+    public InputStream get(String fileName) throws IOException {
+        if (fileName.equals("repository.xml") && directoryChanged()) {
+            generateMetadata(); // might be called too often
+        }
+        return new FileInputStream(new File(m_dir, fileName));
+    }
+
+    public synchronized boolean put(String fileName, InputStream data) throws IOException {
+        File file = new File(m_dir, fileName);
+        if (!file.exists()) {
+            FileOutputStream output = null;
+            File tempFile = null;
+            boolean success = false;
+            try {
+                tempFile = File.createTempFile("obr", ".tmp");
+                output = new FileOutputStream(tempFile);
+                byte[] buffer = new byte[BUFFER_SIZE];
+                for (int count = data.read(buffer); count != -1; count = data.read(buffer)) {
+                    output.write(buffer, 0, count);
+                }
+                success = true;
+            }
+            finally {
+                if (output != null) {
+                    output.flush();
+                    output.close();
+                }
+            }
+            if (success) {
+                tempFile.renameTo(file);
+            }
+            return success;
+        }
+        return false;
+    }
+
+    public synchronized boolean remove(String fileName) throws IOException {
+        File file = new File(m_dir, fileName);
+        if (file.exists()) {
+            if (file.delete()) {
+                return true;
+            }
+            else {
+                throw new IOException("Unable to delete file (" + file.getAbsolutePath() + ")");
+            }
+        }
+        return false;
+    }
+
+    @SuppressWarnings("boxing")
+    private boolean directoryChanged() {
+        File[] files = m_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;
+    }
+
+    @SuppressWarnings("boxing")
+    public synchronized void generateMetadata() throws IOException {
+        File[] files = m_dir.listFiles();
+        m_metadata.generateMetadata(m_dir);
+        for (File current : files) {
+            m_foundFiles.put(current.getAbsolutePath(), current.lastModified() ^ current.length());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public synchronized 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 dir = new File(path);
+            if (!dir.equals(m_dir)) {
+                if (!dir.exists()) {
+                    dir.mkdirs();
+                }
+                else if (!dir.isDirectory()) {
+                    throw new ConfigurationException(OBRFileStoreConstants.FILE_LOCATION_KEY, "Is not a directory: " + dir);
+                }
+                m_dir = dir;
+                m_foundFiles.clear();
+            }
+        }
+        else {
+            // clean up after getting a null as dictionary, as the service is going to be pulled afterwards
+            m_foundFiles.clear();
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/constants/OBRFileStoreConstants.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/constants/OBRFileStoreConstants.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/constants/OBRFileStoreConstants.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/obr/storage/file/constants/OBRFileStoreConstants.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,5 @@
+package net.luminis.liq.obr.storage.file.constants;
+
+public interface OBRFileStoreConstants {
+    public static final String FILE_LOCATION_KEY = "fileLocation";
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/BackupRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/BackupRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/BackupRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/BackupRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,47 @@
+package net.luminis.liq.repository.ext;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Provides an interface for backing up objects. <code>write</code> and <code>read</code>
+ * allow writing and reading of the current version of the object. <code>backup</code>
+ * backs up the object, and <code>restore</code> restores it from a previously backed up
+ * version, if any. There is no way to directly use the backup.
+ */
+public interface BackupRepository {
+
+    /**
+     * Writes the input stream to the current object.
+     * @param data The data to be written. Remember to close this stream, if necessary.
+     * @throws IOException Will be thrown when (a) the input stream gets closed
+     * unexpectedly, or (b) there is an error writing the data.
+     */
+    public void write(InputStream data) throws IOException;
+
+    /**
+     * Reads the input stream from the current object. If there is no current version,
+     * an empty stream will be returned.
+     * @return An input stream, from which can be read. Remember to close it.
+     * @throws IOException Will be thrown when there is a problem storing the data.
+     */
+    public InputStream read() throws IOException;
+
+    /**
+     * Restores a previously backuped version of the object.
+     * @return True when there was a previously backup version which has
+     * now been restored, false otherwise.
+     * @throws IOException Thrown when the restore process goes bad.
+     */
+    public boolean restore() throws IOException;
+
+    /**
+     * Backs up the current version of the object, overwriting a previous
+     * backup, if any.
+     * @return True when there was a current version to be backed up, false
+     * otherwise.
+     * @throws IOException Thrown when the restore process goes bad.
+     */
+    public boolean backup() throws IOException;
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/CachedRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/CachedRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/CachedRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/repository/ext/CachedRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,71 @@
+package net.luminis.liq.repository.ext;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import net.luminis.liq.repository.Repository;
+
+
+/**
+ * Provides a cached repository representation, allowing the storing of local changes, without
+ * committing them to the actual repository immediately.
+ */
+public interface CachedRepository extends Repository {
+
+    /**
+     * Checks our the most current version from the actual repository.
+     * @param fail Indicates that this method should throw an IOException when no data
+     * is available. Setting it to <code>false</code> will make it return an
+     * empty stream in that case.
+     * @return An input stream representing the checked out object.
+     * @throws IOException Is thrown when the actual repository's commit does.
+     */
+    public InputStream checkout(boolean fail) throws IOException;
+
+    /**
+     * Commits the most current version to the actual repository.
+     * @return true on success, false on failure (e.g. bad version number)
+     * @throws IOException Is thrown when the actual repository's commit does.
+     */
+    public boolean commit() throws IOException;
+
+    /**
+     * Gets the most recent version of the object. If no current version is available,
+     * and empty stream will be returned.
+     * @param fail Indicates that this method should throw an IOException when no data
+     * is available. Setting it to <code>false</code> will make it return an
+     * empty stream in that case.
+     * @return An input stream representing the most recently written object.
+     * @throws IOException Thrown when there is a problem retrieving the data.
+     */
+    public InputStream getLocal(boolean fail) throws IOException;
+
+    /**
+     * Writes the most recent version of the object.
+     * @throws IOException Thrown when there is a problem storing the data.
+     */
+    public void writeLocal(InputStream data) throws IOException;
+
+    /**
+     * Undoes all changes made using <code>writeLocal()</code> since the
+     * last <code>commit</code> or <code>checkout</code>.
+     * @throws IOException
+     */
+    public boolean revert() throws IOException;
+
+    /**
+     * Gets the most recent version of this repository, that is, the most recent version
+     * number that is either committed (successfully) or checked out.
+     * @return The most recent version of the underlying repository.
+     */
+    public long getMostRecentVersion();
+
+    /**
+     * Checks whether the version we have locally is current with respect to the version
+     * on the server.
+     * @return whether the version we have locally is current with respect to the version
+     * on the server.
+     * @throws IOException Thrown when an error occurs communicating with the server.
+     */
+    public boolean isCurrent() throws IOException;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,31 @@
+package net.luminis.liq.repository.impl;
+
+
+import java.util.Properties;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.service.prefs.PreferencesService;
+
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, "net.luminis.liq.server.repository.factory");
+        manager.add(createService()
+            .setInterface(ManagedServiceFactory.class.getName(), props)
+            .setImplementation(new RepositoryFactory(manager))
+            .add(createServiceDependency().setService(PreferencesService.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do here
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/CachedRepositoryImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/CachedRepositoryImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/CachedRepositoryImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/repository/impl/CachedRepositoryImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,149 @@
+package net.luminis.liq.repository.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import net.luminis.liq.repository.RangeIterator;
+import net.luminis.liq.repository.Repository;
+import net.luminis.liq.repository.SortedRangeSet;
+import net.luminis.liq.repository.ext.BackupRepository;
+import net.luminis.liq.repository.ext.CachedRepository;
+
+import org.osgi.service.useradmin.User;
+
+/**
+ * Provides a CachedRepository, which uses either a <code>Repository</code> and a <code>BackupRepository</code>
+ * as remote and local storage, or a URL location and two files, from which it will create a <code>Repository</code>
+ *  and a <code>FileBasedBackupRepository</code>. Note that this class is not thread-safe, and should be synchronized
+ *  by the caller.
+ */
+public class CachedRepositoryImpl implements CachedRepository {
+    public static final long UNCOMMITTED_VERSION = -1;
+
+    private final User m_user;
+    private volatile long m_mostRecentVersion;
+
+    private final BackupRepository m_local;
+    private final Repository m_remote;
+
+    /**
+     * Creates a cached repository which uses <code>remote</code>, <code>customer</code> and
+     * <code>name</code> to create a <code>RemoteRepository</code>, and uses the <code>Files</code>s
+     * passed in as a for local storage and backup.
+     * @param user A user object, which is allowed to access <code>remote</code>.
+     * @param remote The location of the remote repository.
+     * @param customer The customer name to be used with the remote repository.
+     * @param name The name to be used with the remote repository.
+     * @param local A local file to be used for storage of changes to the repository.
+     * @param backup A local file to be used as a local backup of what was on the server.
+     * @param mostRecentVersion The version from which <code>backup</code> was checked out or committed.
+     * If no version has been committed yet, use <code>UNCOMMITTED_VERSION</code>.
+     */
+    public CachedRepositoryImpl(User user, URL remote, String customer, String name, File local, File backup, long mostRecentVersion) {
+        this(user,
+            new RemoteRepository(remote, customer, name),
+            new FilebasedBackupRepository(local, backup),
+            mostRecentVersion);
+    }
+
+    /**
+     * Creates a cached repository using.
+     * @param user A user object, which is allowed to access <code>remote</code>.
+     * @param remote A repository which holds committed versions.
+     * @param backup A backup repository for local changes.
+     * @param mostRecentVersion The version from which <code>backup</code> was checked out or committed.
+     * If no version has been committed yet, use <code>UNCOMMITTED_VERSION</code>.
+     */
+    public CachedRepositoryImpl(User user, Repository remote, BackupRepository backup, long mostRecentVersion) {
+        m_user = user;
+        m_remote = remote;
+        m_local = backup;
+        m_mostRecentVersion = mostRecentVersion;
+    }
+
+
+    public InputStream checkout(boolean fail) throws IOException, IllegalArgumentException {
+        m_mostRecentVersion = highestRemoteVersion();
+        if (m_mostRecentVersion == 0) {
+            // If there is no remote version, then simply return an empty stream.
+            if (fail) {
+                throw new IOException("No version has yet been checked in to the repository.");
+            }
+            else {
+                return new ByteArrayInputStream(new byte[0]);
+            }
+        }
+        return checkout(m_mostRecentVersion);
+    }
+
+    public InputStream checkout(long version) throws IOException, IllegalArgumentException {
+        m_local.write(m_remote.checkout(version));
+        m_local.backup();
+
+        m_mostRecentVersion = version;
+
+        return m_local.read();
+    }
+
+    public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException {
+        m_local.write(data);
+
+        return commit(fromVersion);
+    }
+
+    public boolean commit() throws IOException {
+        if (m_mostRecentVersion < 0) {
+            throw new IllegalStateException("A commit should be preceded by a checkout.");
+        }
+        return commit(m_mostRecentVersion++);
+    }
+
+    public boolean commit(long fromVersion) throws IOException, IllegalArgumentException {
+        boolean success = m_remote.commit(m_local.read(), fromVersion);
+        if (success) {
+            m_local.backup();
+        }
+
+        return success;
+    }
+
+    public SortedRangeSet getRange() throws IOException {
+        return m_remote.getRange();
+    }
+
+    public InputStream getLocal(boolean fail) throws IllegalArgumentException, IOException {
+        if ((m_mostRecentVersion <= 0) && fail) {
+            throw new IOException("No local version available of " + m_local + ", remote " + m_remote);
+        }
+        return m_local.read();
+    }
+
+    public boolean revert() throws IOException {
+         return m_local.restore();
+    }
+
+    public void writeLocal(InputStream data) throws IllegalArgumentException, IOException {
+        m_local.write(data);
+    }
+
+    public long getMostRecentVersion() {
+        return m_mostRecentVersion;
+    }
+
+    public boolean isCurrent() throws IOException {
+        return highestRemoteVersion() == m_mostRecentVersion;
+    }
+
+    private long highestRemoteVersion() throws IOException {
+        long result = 0;
+        RangeIterator ri = getRange().iterator();
+        while (ri.hasNext()) {
+            result = ri.next();
+        }
+        return result;
+    }
+
+}