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 [13/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/client/repositoryuseradmin/impl/RoleImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repositoryuseradmin/impl/RoleImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repositoryuseradmin/impl/RoleImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repositoryuseradmin/impl/RoleImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,179 @@
+package net.luminis.liq.client.repositoryuseradmin.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/**
+ * RoleImpl works as an implements of Role, User and Group for external purposes, and
+ * as a value object for use by the {@link RepositoryUserAdminImpl}.
+ */
+public class RoleImpl implements Role, User, Group {
+    private final String m_name;
+    private final int m_type;
+    private final StringOnlyDictionary m_properties = new StringOnlyDictionary();
+    private final StringOnlyDictionary m_credentials = new StringOnlyDictionary();
+    private final Set<Role> m_members = new HashSet<Role>();
+
+    public RoleImpl(String name, int type) {
+        if (name == null) {
+            throw new IllegalArgumentException("Name can not be null");
+        }
+        m_name = name;
+        m_type = type;
+    }
+
+    public String getName() {
+        return m_name;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Dictionary getProperties() {
+        return m_properties;
+    }
+
+    public int getType() {
+        return m_type;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Dictionary getCredentials() {
+        return m_credentials;
+    }
+
+    public boolean hasCredential(String key, Object value) {
+        if (value == null) {
+            return false;
+        }
+
+        // Credentials can be both Strings or byte[] s.
+        Object credential = m_credentials.get(key);
+        if (credential instanceof String) {
+            return ((String) credential).equals(value);
+        }
+        else if (credential instanceof byte[]) {
+            return Arrays.equals((byte[]) value, (byte[]) credential);
+        }
+
+        return false;
+    }
+
+    public boolean addMember(Role role) {
+        return m_members.add(role);
+    }
+
+    public boolean addRequiredMember(Role role) {
+        throw new UnsupportedOperationException("addRequiredMember is not supported by RepositoryUserAdmin.");
+    }
+
+    public Role[] getMembers() {
+        List<Role> result = new ArrayList<Role>();
+        for (Role role : m_members) {
+            result.add(role);
+        }
+        return result.toArray(new Role[result.size()]);
+    }
+
+    public Role[] getRequiredMembers() {
+        throw new UnsupportedOperationException("getRequiredMembers is not supported by RepositoryUserAdmin.");
+    }
+
+    public boolean removeMember(Role role) {
+        return m_members.remove(role);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof RoleImpl)) {
+            return false;
+        }
+        return m_name.equals(((RoleImpl) other).m_name) && (m_type == ((RoleImpl) other).m_type);
+    }
+
+    /**
+     * A specialization of the dictionary that only supports String keys,
+     * and String or byte[] values.
+     */
+    @SuppressWarnings("unchecked")
+    private static final class StringOnlyDictionary extends Dictionary {
+        private final Dictionary m_dict = new Hashtable<String, String>();
+
+        @Override
+        public Enumeration elements() {
+            return m_dict.elements();
+        }
+
+        @Override
+        public Object get(Object key) {
+            return m_dict.get(key);
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return m_dict.isEmpty();
+        }
+
+        @Override
+        public Enumeration keys() {
+            return m_dict.keys();
+        }
+
+        @Override
+        public Object put(Object key, Object value) {
+            if (!(key instanceof String)) {
+                throw new IllegalArgumentException("key should be of type String, not " + key.getClass().getName());
+            }
+            if (!(value instanceof String) && !(value instanceof byte[])) {
+                throw new IllegalArgumentException("value should be of type String or byte[], not " + value.getClass().getName());
+            }
+            return m_dict.put(key, value);
+        }
+
+        @Override
+        public Object remove(Object key) {
+            if (!(key instanceof String)) {
+                throw new IllegalArgumentException("key should be of type String, not " + key.getClass().getName());
+            }
+            return m_dict.remove(key);
+        }
+
+        @Override
+        public int size() {
+            return m_dict.size();
+        }
+    }
+
+    /**
+     * Determines the names of the groups that this role is a member of.
+     */
+    String[] getMemberships(RepositoryUserAdminImpl parent) {
+        // TODO For performance reasons, we could cache this list in the future.
+        List<String> result = new ArrayList<String>();
+        try {
+            for (Role role : parent.getRoles(null)) {
+                if (role instanceof Group) {
+                    for (Role member : ((Group) role).getMembers()) {
+                        if (equals(member)) {
+                            result.add(role.getName());
+                        }
+                    }
+                }
+            }
+        }
+        catch (InvalidSyntaxException e) {
+            // will not happen, since we pass in a null filter
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/configurator/serveruseradmin/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/configurator/serveruseradmin/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/configurator/serveruseradmin/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/configurator/serveruseradmin/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,74 @@
+package net.luminis.liq.configurator.serveruseradmin;
+
+import java.util.Dictionary;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * This bundle configures a single server user, which is to be used until we
+ * have a full-fledged user administration system.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    private final static String TEST_USER = "serverUser";
+    private final static String TEST_PASSWORD = "serverPassword";
+
+    private volatile UserAdmin m_userAdmin; /* Injected by dependency manager */
+    private volatile LogService m_log;      /* Injected by dependency manager */
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createService()
+            .setImplementation(this)
+            .add(createServiceDependency().setService(UserAdmin.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // do nothing
+    }
+
+    public synchronized void start() {
+        // create users
+        createUser(TEST_USER, TEST_PASSWORD);
+    }
+
+    @SuppressWarnings("unchecked")
+    private User createUser(String username, String password) {
+        User user = (User) m_userAdmin.createRole(username, Role.USER);
+        if (user != null) {
+            Dictionary properties = user.getProperties();
+            if (properties != null) {
+                properties.put("username", username);
+            }
+            else {
+                m_log.log(LogService.LOG_ERROR, "Could not get properties for " + username);
+            }
+
+            Dictionary credentials = user.getCredentials();
+            if (credentials != null) {
+                credentials.put("password", password);
+            }
+            else {
+                m_log.log(LogService.LOG_ERROR, "Could not get credentials for " + username);
+            }
+        }
+        else {
+            try {
+                user = (User) m_userAdmin.getRole(username);
+                m_log.log(LogService.LOG_WARNING, "User " + username + " already existed.");
+            }
+            catch (ClassCastException e) {
+                m_log.log(LogService.LOG_WARNING, "Role " + username + " already existed (it's no user).");
+            }
+        }
+        return user;
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,36 @@
+package net.luminis.liq.configurator.useradmin.task;
+
+import java.util.Properties;
+
+import net.luminis.liq.resourceprocessor.useradmin.UserAdminConfigurator;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+/**
+ * Activator for the UserAdmin updater task.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put("taskName", UpdateUserAdminTask.PID);
+        props.put("description", "Synchronizes the UserAdmin with the server.");
+        manager.add(createService()
+            .setInterface(Runnable.class.getName(), props)
+            .setImplementation(UpdateUserAdminTask.class)
+            .add(createServiceDependency().setService(UserAdminConfigurator.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false))
+            .add(createConfigurationDependency().setPid(UpdateUserAdminTask.PID))
+            );
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do, the runnable will be pulled automatically.
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/UpdateUserAdminTask.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/UpdateUserAdminTask.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/UpdateUserAdminTask.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/configurator/useradmin/task/UpdateUserAdminTask.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,159 @@
+package net.luminis.liq.configurator.useradmin.task;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Properties;
+
+import net.luminis.liq.repository.ext.CachedRepository;
+import net.luminis.liq.repository.impl.CachedRepositoryImpl;
+import net.luminis.liq.resourceprocessor.useradmin.UserAdminConfigurator;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+
+/**
+ * UpdateUserAdminTask processes the contents of a repository containing
+ * an XML description of the users that should be present in this system's
+ * user admin.<br>
+ * <br>
+ * From the first run on, this task will keep a local copy of the user repository,
+ * so login can happen when the server is offline.
+ */
+public class UpdateUserAdminTask implements Runnable, ManagedService {
+    /**
+     * The UpdateUserAdminTask is also used as its taskName for the scheduler.
+     */
+    public static final String PID = UpdateUserAdminTask.class.getName();
+
+    public static final String KEY_REPOSITORY_LOCATION = "repositoryLocation";
+    public static final String KEY_REPOSITORY_CUSTOMER = "repositoryCustomer";
+    public static final String KEY_REPOSITORY_NAME = "repositoryName";
+
+    private static final String FILE_ROOT = "userrepositories";
+    private static final String VERSION = "version";
+
+    private volatile UserAdminConfigurator m_configurator; /* Will be injected by dependency manager */
+    private volatile LogService m_log; /* Will be injected by dependency manager */
+    private volatile BundleContext m_context; /* Will be injected by dependency manager */
+
+    private CachedRepository m_repo;
+    private File m_properties;
+
+    public void run() {
+        try {
+            if (!m_repo.isCurrent()) {
+                m_configurator.setUsers(m_repo.checkout(true));
+                m_log.log(LogService.LOG_DEBUG, "UpdateUserAdminTask updates to a new version: " + m_repo.getMostRecentVersion());
+                saveVersion(m_properties, m_repo.getMostRecentVersion());
+            }
+        }
+        catch (IOException e) {
+            // If anything went wrong, this means the remote repository is not available;
+            // this also means the UserAdmin is left undisturbed.
+        }
+    }
+
+    public void start() {
+        try {
+            // Try to read the server data
+            m_configurator.setUsers(m_repo.checkout(true));
+        }
+        catch (IOException e) {
+            try {
+                m_log.log(LogService.LOG_DEBUG, "UpdateUserAdminTask failed to load remote data; falling back to local data.");
+                // If reading remote fails, try to set whatever we have locally
+                m_configurator.setUsers(m_repo.getLocal(true));
+            }
+            catch (IOException e2) {
+                // No problem, now we just have an empty user admin...
+                m_log.log(LogService.LOG_DEBUG, "UpdateUserAdminTask failed to load local data.");
+            }
+        }
+    }
+
+    /**
+     * Loads the most recent version from the given properties file.
+     */
+    private long loadVersion(File propertiesfile) {
+        long result = CachedRepositoryImpl.UNCOMMITTED_VERSION;
+        try {
+            Properties props = new Properties();
+            props.loadFromXML(new FileInputStream(propertiesfile));
+            result = Long.parseLong((String) props.get(VERSION));
+        }
+        catch (IOException ioe) {
+            // We have no data; no problem.
+        }
+        return result;
+    }
+
+    /**
+     * Saves the most recent version to the given properties file.
+     */
+    private void saveVersion(File properties, Long version) {
+        Properties props = new Properties();
+        props.put(VERSION, version.toString());
+        try {
+            FileOutputStream fileOutputStream = new FileOutputStream(properties);
+            props.storeToXML(fileOutputStream, null);
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "UpdateUserAdminTask failed to save local version number.");
+        }
+    }
+
+    public void updated(Dictionary dict) throws ConfigurationException {
+        if (dict != null) {
+            String locationString = (String) dict.get(KEY_REPOSITORY_LOCATION);
+            if (locationString == null) {
+                throw new ConfigurationException(KEY_REPOSITORY_LOCATION, "Property missing.");
+            }
+            URL location;
+            try {
+                location = new URL(locationString);
+            }
+            catch (MalformedURLException e) {
+                throw new ConfigurationException(KEY_REPOSITORY_LOCATION, "Location " + locationString + " is not a valid URL.");
+            }
+            String customer = (String) dict.get(KEY_REPOSITORY_CUSTOMER);
+            if (customer == null) {
+                throw new ConfigurationException(KEY_REPOSITORY_CUSTOMER, "Property missing.");
+            }
+            String name = (String) dict.get(KEY_REPOSITORY_NAME);
+            if (name == null) {
+                throw new ConfigurationException(KEY_REPOSITORY_NAME, "Property missing.");
+            }
+
+            String fileRoot = FILE_ROOT + File.separator + location.getAuthority().replace(':', '-') + location.getPath().replace('/', '\\') + File.separator + customer + File.separator + name + File.separator;
+            File local = getFile(fileRoot + "local");
+            File backup = getFile(fileRoot + "backup");
+            m_properties = getFile(fileRoot + "properties");
+
+            m_repo = new CachedRepositoryImpl(null, location, customer, name, local, backup, loadVersion(m_properties));
+        }
+    }
+
+    private File getFile(String name) {
+        File result = m_context.getDataFile(name);
+        if (!result.exists()) {
+            result.getParentFile().mkdirs();
+            try {
+                if (!result.createNewFile()) {
+                    m_log.log(LogService.LOG_ERROR, "Error creating new file " + name);
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_ERROR, "Error creating new file " + name, e);
+            }
+        }
+        return result;
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/ArtifactData.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/ArtifactData.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/ArtifactData.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/ArtifactData.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,63 @@
+package net.luminis.liq.deployment.provider;
+
+import java.net.URL;
+import java.util.jar.Attributes;
+
+/**
+ * The ArtifactData as returned by the <code>DeploymentProvider</code> class in this package.
+ * It contains several pieces of data which describe the artifact and the place where it can be found.
+ */
+public interface ArtifactData {
+
+    /**
+     * Indicate if the bundle has changed. This is used when comparing artifacts in 2 versions. (see DeploymentProvider)
+     * If you requested one version it always returns true.
+     *
+     * @return if this artifact has changed.
+     */
+    public boolean hasChanged();
+
+    /**
+     * @return <code>true</code> if this artifact is a bundle; <code>false</code> otherwise.
+     */
+    public boolean isBundle();
+
+    /**
+     * @return <code>true</code> if this artifact is a customizer that contains a resource processor; <code>false</code> otherwise.
+     */
+    public boolean isCustomizer();
+
+    /**
+     * @return the filename of the artifact
+     */
+    public String getFilename();
+
+    /**
+     *  @return the symbolic name, if this artifact is a bundle.
+     */
+    public String getSymbolicName();
+
+    /**
+     *  @return the version, if this artifact is a bundle. If it is an artifact, this function
+     *  will always return "0.0.0".
+     */
+    public String getVersion();
+
+    /**
+     * @return the url to the artifact data.
+     */
+    public URL getUrl();
+
+    /**
+     * @return the processor Pid to be used for this resource, if any.
+     */
+    public String getProcessorPid();
+
+    /**
+     * @return a set of attributes that describes this artifact in a manifest.
+     * @param fixPackage Indicating whether this set of headers is intended to be part
+     * of a fixpackage.
+     */
+    public Attributes getManifestAttributes(boolean fixPackage);
+
+}
\ No newline at end of file

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/DeploymentProvider.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/DeploymentProvider.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/DeploymentProvider.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/DeploymentProvider.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,52 @@
+package net.luminis.liq.deployment.provider;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * An interface that provides the meta information for the bundles
+ * in a certain version number.
+ */
+public interface DeploymentProvider {
+
+    /**
+     * Get the collection of bundleData for a specific version. This data can be used to generate a deployment package.
+     * The ArtifactData.hasChanged method will return true for all bundles in this collection
+     *
+     * @return a collection of bundledata. If there are no bundles in this version, return an empty list
+     * @throws IllegalArgumentException if the gateway or version do not exist
+     * @throws IOException If an IOException occurs.
+     */
+    public List<ArtifactData> getBundleData(String gatewayId, String version) throws IllegalArgumentException, IOException;
+
+    /**
+     * This data can be used to generate a fix package. It gives the differences between the versionFrom and versionTo.
+     *
+     * Changes between versions are indicated by ArtifactData.hasChanged:
+     * <ol>
+     * <li> If a bundle was present in versionFrom and not in VersionTo, it will not be in the collection</li>
+     * <li> If a bundle existed in versionFrom and exists unchanged in VersionTo, hasChanged will return false</li>
+     * <li> If a bundle existed in versionFrom and exists changed (i.e. other version) in versionTo, hasChanged will return true</li>
+     * <li> If a bundle did not exist in versionFrom and exists in VersionTo, hasChanged will return true</li>
+     * </ol>
+     *
+     * @return a list of bundles.
+     * @throws IllegalArgumentException if the gateway, the versionFrom or versionTo do no exist
+     * @throws IOException If an IOException occurs.
+     */
+
+    public List<ArtifactData> getBundleData(String gatewayId, String versionFrom, String versionTo) throws IllegalArgumentException, IOException;
+
+    /**
+     * Returns a list of versions for a specific gateway. The list is sorted in
+     * ascending order, so the latest version is the last one in the list.
+     *
+     * @param gatewayId  The id of the gateway for which all available deployment package
+     *                   versions are being retrieved.
+     * @return All available deployment package versions for a specific gateway. If none available,
+     *         return an empty List.
+     *         If the gateway doesn't exist, an IllegalArgumentException is thrown
+     * @throws IOException If an IOException occurs.
+     */
+    public List<String> getVersions(String gatewayId) throws IllegalArgumentException, IOException;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,30 @@
+package net.luminis.liq.deployment.provider.filebased;
+
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+
+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.deployment.provider.filebased";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createService()
+            .setInterface(DeploymentProvider.class.getName(), null)
+            .setImplementation(FileBasedProvider.class)
+            .add(createConfigurationDependency()
+                .setPid(PID)
+             )
+             .add(createServiceDependency()
+                 .setService(LogService.class)
+                 .setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext arg0, DependencyManager arg1) throws Exception {
+        // TODO Auto-generated method stub
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/FileBasedProvider.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/FileBasedProvider.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/FileBasedProvider.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/filebased/FileBasedProvider.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,289 @@
+package net.luminis.liq.deployment.provider.filebased;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import net.luminis.liq.deployment.provider.ArtifactData;
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+import net.luminis.liq.deployment.provider.impl.ArtifactDataImpl;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+
+/**
+ * This class reads data from the filesystem. It contains deployment data in the following format: <storage dir>/<gateway-name>/<bundle-version>/<jars>
+ * example : storage-directory/ storage-directory/gateway-a storage-directory/gateway-a/1.0.0
+ * storage-directory/gateway-a/1.0.0/bundle1.jar storage-directory/gateway-a/1.0.0/bundle2.jar storage-directory/gateway-a/1.1.0
+ * storage-directory/gateway-a/1.1.0/bundle2.jar storage-directory/gateway-a/1.1.0/bundle3.jar The versions are in the
+ * org.osgi.framework.Version format.
+ */
+public class FileBasedProvider implements DeploymentProvider, ManagedService {
+
+    private static final String DIRECTORY_NAME = "BaseDirectoryName";
+
+    private final int OSGI_R4_MANIFEST_VERSION = 2;
+
+    private volatile File m_baseDirectory;
+
+    private volatile LogService m_log;
+
+    private final Semaphore m_disk = new Semaphore(1, true);
+
+    /**
+     * Get the bundle data from the bundles in the <data dir>/<gateway>/<version> directory It reads the manifest from all the
+     * .jar files in that directory. If the manifest cannot be found, This method can only parse OSGi R4 bundles
+     */
+    public List<ArtifactData> getBundleData(String gatewayId, String version) throws IllegalArgumentException {
+
+        List<String> versions = getVersions(gatewayId);
+        if (!versions.contains(version)) {
+            throw new IllegalArgumentException("Unknown version " + version + " requested");
+        }
+        File gatewayDirectory = new File(m_baseDirectory, gatewayId);
+        File versionDirectory = new File(gatewayDirectory, version);
+        List<ArtifactData> bundleData = new ArrayList<ArtifactData>();
+
+        JarInputStream jarInputStream = null;
+
+        File[] jarFiles = versionDirectory.listFiles();
+        for (int i = 0; i < jarFiles.length; i++) {
+            Manifest bundleManifest = null;
+            File jarFile = jarFiles[i];
+            try {
+                jarInputStream = new JarInputStream(new FileInputStream(jarFile));
+                bundleManifest = jarInputStream.getManifest();
+            }
+            catch (IOException ioe) {
+                m_log.log(LogService.LOG_WARNING, "Error making inputstream", ioe);
+                continue;
+            }
+            finally {
+                if (jarInputStream != null) {
+                    try {
+                        jarInputStream.close();
+                    }
+                    catch (Exception ioe) {
+                        m_log.log(LogService.LOG_ERROR, "Error closing the file input stream", ioe);
+                    }
+                }
+            }
+            Attributes mainAttributes = bundleManifest.getMainAttributes();
+            String manifestVersion = mainAttributes.getValue(Constants.BUNDLE_MANIFESTVERSION);
+            String symbolicName = mainAttributes.getValue(Constants.BUNDLE_SYMBOLICNAME);
+            String bundleVersion = mainAttributes.getValue(Constants.BUNDLE_VERSION);
+
+            if ((manifestVersion != null) && (symbolicName != null) && (bundleVersion != null)) {
+                // ok, now at least we have the required attributes
+                // now check if they have the expected values
+                if (OSGI_R4_MANIFEST_VERSION != new Integer(manifestVersion).intValue()) {
+                    m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + jarFile.getAbsolutePath() + " has the wrong manifest version.");
+                }
+                else {
+                    // it is the right manifest version
+                    if (symbolicName.trim().equals("")) {
+                        m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + jarFile.getAbsolutePath() + " the symbolic name is empty.");
+                    }
+                    else {
+                        // it also has the right symbolic name
+                        try {
+                            new Version(bundleVersion);
+                            // Do a file.toURI().toURL() to preserve special path characters
+                            // see http://www.javalobby.org/java/forums/t19698.html
+                            URL bundleUrl = new URL(null, jarFile.toURI().toURL().toString(), new URLStreamHandler() {
+
+                                @Override
+                                protected URLConnection openConnection(final URL u) throws IOException {
+                                    return new URLConnection(u) {
+
+                                        @Override
+                                        public void connect() throws IOException {
+                                            // TODO Auto-generated method stub
+
+                                        }
+
+                                        @Override
+                                        public InputStream getInputStream() throws IOException {
+                                            final InputStream parent;
+                                            try {
+                                                parent = new URL(u.toURI().toURL().toString()).openStream();
+                                            }
+                                            catch (URISyntaxException ex) {
+                                                throw new IOException(ex.getMessage());
+                                            }
+                                            return new InputStream() {
+                                                @Override
+                                                public int read() throws IOException {
+                                                    return parent.read();
+                                                }
+
+                                                @Override
+                                                public int read(byte[] buffer) throws IOException {
+                                                    return read(buffer, 0, buffer.length);
+                                                }
+
+                                                @Override
+                                                public int read(byte[] buffer, int off, int length) throws IOException {
+                                                    m_disk.acquireUninterruptibly();
+                                                    try {
+                                                        return parent.read(buffer, off, length);
+                                                    }
+                                                    finally {
+                                                        m_disk.release();
+                                                    }
+                                                }
+
+												@Override
+												public void close() throws IOException {
+													parent.close();
+												}
+                                            };
+                                        }
+                                    };
+                                }
+
+                            });
+                            bundleData.add(new ArtifactDataImpl(jarFile.getName(), symbolicName, bundleVersion, bundleUrl, true));
+                        }
+                        catch (IllegalArgumentException iae) {
+                            m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + jarFile.getAbsolutePath() + " has an illegal version", iae);
+                        }
+                        catch (MalformedURLException mue) {
+                            m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + jarFile.getAbsolutePath() + " unable to convert path to URL", mue);
+                        }
+                    }
+                }
+            }
+            else {
+                m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + jarFile.getAbsolutePath() + " is missing required attributes");
+            }
+        }
+
+        return bundleData;
+    }
+
+    public List<ArtifactData> getBundleData(String gatewayId, String versionFrom, String versionTo) throws IllegalArgumentException {
+        List<ArtifactData> dataVersionFrom = getBundleData(gatewayId, versionFrom);
+        List<ArtifactData> dataVersionTo = getBundleData(gatewayId, versionTo);
+
+        Iterator<ArtifactData> it = dataVersionTo.iterator();
+        while (it.hasNext()) {
+            ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next();
+            // see if there was previously a version of this bundle.
+            ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
+            bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
+        }
+        return dataVersionTo;
+    }
+
+    /**
+     * Check for the existence of bundledata in the collection for a bundle with the given symbolic name
+     *
+     * @param symbolicName
+     * @return
+     */
+    private ArtifactData getBundleData(String symbolicName, Collection<ArtifactData> data) {
+        Iterator<ArtifactData> it = data.iterator();
+        while (it.hasNext()) {
+            ArtifactData bundle = it.next();
+            if (bundle.getSymbolicName().equals(symbolicName)) {
+                return bundle;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Look in the baseDirectory for the specified gateway. If it exists, get the list of directories in there and check if they
+     * conform to the <code>org.osgi.framework.Version</code> format. If it does, it will be in the list of versions that are
+     * returned. If there are no valid versions, return an empty list. If the gateway cannot be found, an
+     * IllegalArgumentException is thrown. The list will be sorted on version.
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getVersions(String gatewayId) throws IllegalArgumentException {
+        List<Version> versionList = new ArrayList<Version>();
+        File gatewayDirectory = new File(m_baseDirectory.getAbsolutePath(), gatewayId);
+        if (gatewayDirectory.isDirectory()) {
+            // ok, it is a directory. Now treat all the subdirectories as seperate versions
+            File[] files = gatewayDirectory.listFiles();
+            for (int i = 0; i < files.length; i++) {
+                File possibleVersionDirectory = files[i];
+                if (possibleVersionDirectory.isDirectory()) {
+                    // ok, it is a directory. Now see if it is a version
+                    try {
+                        Version version = Version.parseVersion(possibleVersionDirectory.getName());
+                        // no exception, but is could still be an empty version
+                        if (!version.equals(Version.emptyVersion)) {
+                            versionList.add(version);
+                        }
+                    }
+                    catch (IllegalArgumentException iae) {
+                        // do nothing. This version will be ignored.
+                        m_log.log(LogService.LOG_WARNING, "Directory " + possibleVersionDirectory.toString() + " does not contain a valid version number", iae);
+                    }
+                }
+            }
+            if (files.length == 0) {
+                m_log.log(LogService.LOG_DEBUG, "No versions found for gateway " + gatewayId);
+            }
+        }
+        else {
+            throw new IllegalArgumentException("Gateway directory " + gatewayDirectory.toString() + " is requested but not found.");
+        }
+        // now sort the list of versions and convert all values to strings.
+        Collections.sort(versionList);
+        List<String> stringVersionList = new ArrayList<String>();
+        Iterator<Version> it = versionList.iterator();
+        while (it.hasNext()) {
+            String version = (it.next()).toString();
+            stringVersionList.add(version);
+        }
+        return stringVersionList;
+    }
+
+    /**
+     * Update the configuration for this bundle. It checks if the basedirectory exists and is a directory.
+     */
+    @SuppressWarnings("unchecked")
+    public void updated(Dictionary settings) throws ConfigurationException {
+        if (settings != null) {
+            String baseDirectoryName = getNotNull(settings, DIRECTORY_NAME, "The base directory cannot be null");
+            File baseDirectory = new File(baseDirectoryName);
+            if (!baseDirectory.exists() || !baseDirectory.isDirectory()) {
+                throw new ConfigurationException(DIRECTORY_NAME, "The directory called '" + baseDirectoryName + "' " + (baseDirectory.exists() ? "is no directory." : "doesn't exist."));
+            }
+            m_baseDirectory = baseDirectory;
+        }
+    }
+
+    /**
+     * Convenience method for getting settings from a configuration dictionary.
+     */
+    @SuppressWarnings("unchecked")
+    private String getNotNull(Dictionary settings, String id, String errorMessage) throws ConfigurationException {
+        String result = (String) settings.get(id);
+        if (result == null) {
+            throw new ConfigurationException(id, errorMessage);
+        }
+        return result;
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/impl/ArtifactDataImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/impl/ArtifactDataImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/impl/ArtifactDataImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/impl/ArtifactDataImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,189 @@
+package net.luminis.liq.deployment.provider.impl;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.jar.Attributes;
+
+import net.luminis.liq.client.repository.object.DeploymentArtifact;
+import net.luminis.liq.deployment.provider.ArtifactData;
+
+import org.osgi.framework.Constants;
+
+/**
+ * Implementation of <code>ArtifactData</code>. It overrides equals to make comparisons between versions easier.
+ */
+public class ArtifactDataImpl implements ArtifactData {
+    public final static String HEADER_NAME = "Name";
+    public static final String CUSTOMIZER = "DeploymentPackage-Customizer";
+    public static final String PROCESSORPID = "Resource-Processor";
+
+    private final String m_filename;
+    private final String m_symbolicName;
+    private final String m_version;
+    private final Map<String, String> m_directives;
+
+    private final URL m_url;
+    private volatile boolean m_hasChanged;
+
+    /**
+     * Constructs an ArtifactDataImpl object.
+     * @param url The URL to the bundle. It will also be used to create the filename.
+     * The file-part of the url (after the last /) should It must only contain characters [A-Za-z0-9._-].
+     * @param directives A map of extra directives.
+     * @param symbolicName The symbolicname of the bundle.
+     * @param version The version of the bundle. If this is <code>null</code> or empty, it will be
+     * normalized to "0.0.0".
+     * @param hasChanged Indication of whether this bundle has changed relative to the previous deployment.
+     */
+    public ArtifactDataImpl(URL url, Map<String, String> directives, String symbolicName, String version, boolean hasChanged) {
+        this(url, symbolicName, version, null, directives, hasChanged);
+    }
+
+    /**
+     * Constructs an ArtifactDataImpl object.
+     * @param url The URL to the bundle. It will also be used to create the filename.
+     * The file-part of the url (after the last /) should It must only contain characters [A-Za-z0-9._-].
+     * @param directives A map of extra directives.
+     * @param hasChanged Indication of whether this bundle has changed relative to the previous deployment.
+     */
+    public ArtifactDataImpl(URL url, Map<String, String> directives, boolean hasChanged) {
+        this(url, null, null, null, directives, hasChanged);
+    }
+
+    /**
+     * Constructs an ArtifactDataImpl object.
+     * @param filename The filename of the bundle. If passed, it must only contain characters [A-Za-z0-9._-]; can be null.
+     * @param symbolicName The symbolicname of the bundle.
+     * @param version The version of the bundle. If this is <code>null</code> or empty, it will be
+     * normalized to "0.0.0".
+     * @param url The URL to the bundle. If filename is null, this will be used to create the filename; hence, the file-part of
+     * the url (after the last /) should adhere to the same rules as filename.
+     * @param hasChanged Indication of whether this bundle has changed relative to the previous deployment.
+     */
+    public ArtifactDataImpl(String filename, String symbolicName, String version, URL url, boolean hasChanged) {
+        this(url, symbolicName, version, filename, null, hasChanged);
+    }
+
+    private ArtifactDataImpl(URL url, String symbolicName, String version, String filename, Map<String, String> directives, boolean hasChanged) {
+        m_url = url;
+
+        if (filename != null) {
+            m_filename = filename;
+        }
+        else {
+            String urlString = m_url.toString();
+            m_filename = (urlString == null) ? null : urlString.substring(urlString.lastIndexOf('/') + 1);
+        }
+
+        for (byte b : m_filename.getBytes()) {
+            if (!(((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z')) || ((b >= '0') && (b <= '9')) || (b == '.') || (b == '-') || (b == '_'))) {
+                throw new IllegalArgumentException("Filename " + m_filename + " " + (filename == null ? "constructed from the url" : "") + " contains an illegal character '" + new String(new byte[] {b}) + "'");
+            }
+        }
+
+        m_symbolicName = symbolicName;
+        if ((version == null) || (version.trim().length() == 0)) {
+            m_version = "0.0.0";
+        }
+        else {
+            m_version = version;
+        }
+        m_directives = directives;
+        m_hasChanged = hasChanged;
+    }
+
+    public String getFilename() {
+        return m_filename;
+    }
+
+    public String getSymbolicName() {
+        return m_symbolicName;
+    }
+
+    public String getVersion() {
+        return m_version;
+    }
+
+    public String getProcessorPid() {
+        if (m_directives != null) {
+            return m_directives.get(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID);
+        }
+        return null;
+    }
+
+    public URL getUrl() {
+        return m_url;
+    }
+
+    public boolean hasChanged() {
+        return m_hasChanged;
+    }
+
+    /**
+     * @param hasChanged Indicate the bundle has changed
+     */
+    public void setChanged(boolean hasChanged) {
+        m_hasChanged = hasChanged;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof ArtifactDataImpl)) {
+            return false;
+        }
+        ArtifactDataImpl jarFile2 = (ArtifactDataImpl) other;
+
+        if (getSymbolicName() != null) {
+            // this is a bundle
+            return getSymbolicName().equals(jarFile2.getSymbolicName()) &&
+            getVersion().equals(jarFile2.getVersion());
+        }
+        else {
+            // this is another artifact.
+            return m_url.equals(jarFile2.getUrl());
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 11;
+        if (getSymbolicName() != null) {
+            result = result ^ getSymbolicName().hashCode();
+        }
+        result = result ^ getVersion().hashCode();
+        result = result ^ getUrl().hashCode();
+        return result;
+    }
+
+    public Attributes getManifestAttributes(boolean fixPackage) {
+        Attributes a = new Attributes();
+
+        if (!isBundle()) {
+            // this is a regular artifact
+            a.putValue(PROCESSORPID, getProcessorPid());
+        }
+        else {
+            a.putValue(Constants.BUNDLE_SYMBOLICNAME, getSymbolicName());
+            a.putValue(Constants.BUNDLE_VERSION, getVersion());
+            // this is a bundle
+            if (isCustomizer()) {
+                a.putValue(CUSTOMIZER, "true");
+            }
+        }
+
+        if (!hasChanged() && fixPackage) {
+            a.putValue("DeploymentPackage-Missing", "true");
+        }
+
+        return a;
+    }
+
+    public boolean isCustomizer() {
+        return (m_directives != null) && "true".equals(m_directives.get(DeploymentArtifact.DIRECTIVE_ISCUSTOMIZER));
+    }
+
+    public boolean isBundle() {
+        return getSymbolicName() != null;
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,30 @@
+package net.luminis.liq.deployment.provider.repositorybased;
+
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+
+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.deployment.provider.repositorybased";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createService()
+            .setInterface(DeploymentProvider.class.getName(), null)
+            .setImplementation(RepositoryBasedProvider.class)
+            .add(createConfigurationDependency()
+                .setPid(PID)
+             )
+             .add(createServiceDependency()
+                 .setService(LogService.class)
+                 .setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext arg0, DependencyManager arg1) throws Exception {
+        // TODO Auto-generated method stub
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/RepositoryBasedProvider.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/RepositoryBasedProvider.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/RepositoryBasedProvider.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/provider/repositorybased/RepositoryBasedProvider.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,635 @@
+package net.luminis.liq.deployment.provider.repositorybased;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathVariableResolver;
+
+import net.luminis.liq.client.repository.helper.bundle.BundleHelper;
+import net.luminis.liq.client.repository.object.DeploymentArtifact;
+import net.luminis.liq.deployment.provider.ArtifactData;
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+import net.luminis.liq.deployment.provider.impl.ArtifactDataImpl;
+import net.luminis.liq.repository.RangeIterator;
+import net.luminis.liq.repository.Repository;
+import net.luminis.liq.repository.ext.BackupRepository;
+import net.luminis.liq.repository.ext.CachedRepository;
+import net.luminis.liq.repository.impl.CachedRepositoryImpl;
+import net.luminis.liq.repository.impl.FilebasedBackupRepository;
+import net.luminis.liq.repository.impl.RemoteRepository;
+
+import org.osgi.framework.Version;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+/**
+ * The RepositoryBasedProvider provides version information and bundle data by the DeploymentProvider interface. It uses a
+ * Repository to get its information from, which it parses using a SAX parser.
+ */
+public class RepositoryBasedProvider implements DeploymentProvider, ManagedService {
+    private static final String URL = "url";
+    private static final String NAME = "name";
+    private static final String CUSTOMER = "customer";
+    private volatile LogService m_log;
+
+    /** This variable is volatile since it can be changed by the Updated() method. */
+    private volatile CachedRepository m_cachedRepository;
+
+    /**
+     * This variable is volatile since it can be changed by the updated() method. Furthermore, it will be used to inject a
+     * custom repository in the integration test.
+     */
+    private volatile Repository m_directRepository;
+
+    public List<ArtifactData> getBundleData(String gatewayId, String version) throws IllegalArgumentException, IOException {
+        return getBundleData(gatewayId, null, version);
+    }
+
+    public List<ArtifactData> getBundleData(String gatewayId, String versionFrom, String versionTo) throws IllegalArgumentException, IOException {
+        try {
+            if (versionFrom != null) {
+                Version.parseVersion(versionFrom);
+            }
+            Version.parseVersion(versionTo);
+        }
+        catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException(nfe);
+        }
+
+        InputStream input = null;
+        List<ArtifactData> dataVersionTo = null;
+        List<ArtifactData> dataVersionFrom = null;
+
+        List<URLDirectivePair>[] pairs = null;
+        try {
+            input = getRepositoryStream();
+            if (versionFrom == null) {
+                pairs = getURLDirectivePairs(input, gatewayId, new String[] { versionTo });
+            }
+            else {
+                pairs = getURLDirectivePairs(input, gatewayId, new String[] { versionFrom, versionTo });
+            }
+        }
+        catch (IOException ioe) {
+            m_log.log(LogService.LOG_WARNING, "Problem parsing source version.", ioe);
+            throw ioe;
+        }
+        finally {
+            if (input != null) {
+                try {
+                    input.close();
+                }
+                catch (IOException e) {
+                    m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
+                }
+            }
+        }
+
+        if ((pairs != null) && (pairs.length > 1)) {
+            dataVersionFrom = getBundleDataByDocument(pairs[0]);
+            dataVersionTo = getBundleDataByDocument(pairs[1]);
+            Iterator<ArtifactData> it = dataVersionTo.iterator();
+            while (it.hasNext()) {
+                ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next();
+                // see if there was previously a version of this bundle, and update the 'changed' property accordingly.
+                if (bundleDataVersionTo.isBundle()) {
+                    ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
+                    bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
+                }
+                else {
+                    ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getUrl(), dataVersionFrom);
+                    bundleDataVersionTo.setChanged(bundleDataVersionFrom == null);
+                }
+            }
+        }
+        else {
+            dataVersionTo = getBundleDataByDocument(pairs[0]);
+        }
+
+        return dataVersionTo != null ? dataVersionTo : new ArrayList<ArtifactData>();
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<String> getVersions(String gatewayId) throws IllegalArgumentException, IOException {
+        List<String> stringVersionList = new ArrayList<String>();
+        InputStream input = null;
+
+        try {
+            input = getRepositoryStream();
+            List<Version> versionList = getAvailableVersions(input, gatewayId);
+            if (versionList.isEmpty()) {
+                m_log.log(LogService.LOG_DEBUG, "No versions found for gateway " + gatewayId);
+            }
+            else {
+                // now sort the list of versions and convert all values to strings.
+                Collections.sort(versionList);
+                Iterator<Version> it = versionList.iterator();
+                while (it.hasNext()) {
+                    String version = (it.next()).toString();
+                    stringVersionList.add(version);
+                }
+            }
+        }
+        catch (IllegalArgumentException iae) {
+            // just move on.
+        }
+        catch (IOException ioe) {
+            m_log.log(LogService.LOG_DEBUG, "Problem parsing DeploymentRepository", ioe);
+            throw ioe;
+        }
+        finally {
+            if (input != null) {
+                try {
+                    input.close();
+                }
+                catch (IOException e) {
+                    m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
+                }
+            }
+        }
+
+        return stringVersionList;
+    }
+
+    /**
+     * Helper method to get the bundledata given an inputstream to a repository xml file
+     *
+     * @param input An input stream to the XML data to be parsed.
+     * @return A list of ArtifactData object representing this version.
+     */
+    private List<ArtifactData> getBundleDataByDocument(List<URLDirectivePair> urlDirectivePairs) throws IllegalArgumentException {
+        List<ArtifactData> result = new ArrayList<ArtifactData>();
+
+        // get the bundledata for each URL
+        for (URLDirectivePair pair : urlDirectivePairs) {
+            Map<String, String> directives = pair.getDirective();
+
+            if (directives.get(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID) == null) {
+                // this is a bundle.
+                String symbolicName = directives.remove(BundleHelper.KEY_SYMBOLICNAME);
+                String bundleVersion = directives.remove(BundleHelper.KEY_VERSION);
+                if (symbolicName != null) {
+                    // it is the right symbolic name
+                    if (symbolicName.trim().equals("")) {
+                        m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + pair.toString() + " the symbolic name is empty.");
+                    }
+                    else {
+                        result.add(new ArtifactDataImpl(pair.getUrl(), directives, symbolicName, bundleVersion, true));
+                    }
+                }
+            }
+            else {
+                // it is an artifact.
+                result.add(new ArtifactDataImpl(pair.getUrl(), directives, true));
+            }
+
+        }
+        return result;
+    }
+
+    /**
+     * Helper method check for the existence of artifact data in the collection for a bundle with the given url.
+     *
+     * @param url The url to be found.
+     * @return The <code>ArtifactData</code> object that has this <code>url</code>, or <code>null</code> if none can be
+     *         found.
+     */
+    private ArtifactData getBundleData(URL url, Collection<ArtifactData> data) {
+        ArtifactData bundle = null;
+        Iterator<ArtifactData> it = data.iterator();
+        while (it.hasNext()) {
+            bundle = it.next();
+            if (bundle.getUrl().equals(url)) {
+                return bundle;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper method check for the existence of artifact data in the collection for a bundle with the given symbolic name.
+     *
+     * @param symbolicName The symbolic name to be found.
+     * @return The <code>ArtifactData</code> object that has this <code>symbolicName</code>, or <code>null</code> if none
+     *         can be found.
+     */
+    private ArtifactData getBundleData(String symbolicName, Collection<ArtifactData> data) {
+        ArtifactData bundle = null;
+        Iterator<ArtifactData> it = data.iterator();
+        while (it.hasNext()) {
+            bundle = it.next();
+            if ((bundle.getSymbolicName() != null) && bundle.getSymbolicName().equals(symbolicName)) {
+                return bundle;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the available deployment versions for a gateway
+     *
+     * @param input A dom document representation of the repository
+     * @param gatewayId The gatwayId
+     * @return A list of available versions
+     */
+    private List<Version> getAvailableVersions(InputStream input, String gatewayId) throws IllegalArgumentException {
+        //result list
+        List<Version> versionList = new ArrayList<Version>();
+        XPathContext context = XPathContext.getInstance();
+
+        try {
+            NodeList versions = context.getVersions(gatewayId, input);
+            if (versions != null) {
+                for (int j = 0; j < versions.getLength(); j++) {
+                    Node n = versions.item(j);
+                    String versionValue = n.getTextContent();
+                    try {
+                        Version version = Version.parseVersion(versionValue);
+                        // no exception, but is could still be an empty version
+                        if (!version.equals(Version.emptyVersion)) {
+                            versionList.add(version);
+                        }
+                    }
+                    catch (NumberFormatException nfe) {
+                        // Ignore this version
+                        m_log.log(LogService.LOG_WARNING, "Deploymentversion ignored: ", nfe);
+                    }
+                }
+            }
+
+            return versionList;
+        }
+        catch (XPathExpressionException xee) {
+            throw new IllegalArgumentException(xee);
+        }
+        finally {
+            context.destroy();
+        }
+    }
+
+    /*
+     * This class is used to cache compiled xpath expression for the queries we need on a per thread basis. In order
+     * to do this a thread local is used to cache an instance of this class per calling thread. The reference to this
+     * instance is wrapped in a soft reference to make it possible to GC the instance in case memory is low.
+     * <p>
+     * Example Usage:
+     * <pre>
+     * XPathContext context = XPathContext.getInstance();
+     *
+     * try
+     * {
+     *     // to get all artifacts of a number of versions:
+     *     context.init(gatewayId, versions, input);
+     *
+     *     for (int i = 0; i < versions.length; i++)
+     *     {
+     *         Node version = context.getVersion(i);
+     *         // Do work with version
+     *     }
+     *     // to get all versions of a number of a gateway:
+     *     NodeList versions = context.getVersions(gatewayId, input);
+     *     // Do wortk with versions
+     * }
+     * finally
+     * {
+     *     context.destory();
+     * }
+     * </pre>
+     */
+    private static final class XPathContext implements XPathVariableResolver {
+        private static final ThreadLocal<SoftReference<XPathContext>> m_cache = new ThreadLocal<SoftReference<XPathContext>>();
+
+        private final XPath m_xPath = XPathFactory.newInstance().newXPath();
+
+        private final XPathExpression m_attributesExpression;
+
+        private final XPathExpression m_versionsExpression;
+
+        private final Map<Integer, XPathExpression> m_expressions = new HashMap<Integer, XPathExpression>();
+
+        private String m_gatewayId;
+
+        private String[] m_versions;
+
+        private Node m_node;
+
+        private String m_version;
+
+        private XPathContext() {
+            m_xPath.setXPathVariableResolver(this);
+            try {
+                m_attributesExpression = m_xPath.compile("//deploymentversions/deploymentversion/attributes/child::gatewayID[text()=$id]/../child::version[text()=$version]/../../artifacts");
+                m_versionsExpression = m_xPath.compile("//deploymentversions/deploymentversion/attributes/child::gatewayID[text()=$id]/parent::attributes/version/text()");
+            }
+            catch (XPathExpressionException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * @return A thread local instance from the cache.
+         */
+        public static XPathContext getInstance() {
+            SoftReference<XPathContext> ref = m_cache.get();
+            XPathContext instance = null;
+            if (ref != null) {
+                instance = ref.get();
+            }
+
+            if (instance == null) {
+                ref = null;
+                instance = new XPathContext();
+            }
+
+            if (ref == null) {
+                m_cache.set(new SoftReference<XPathContext>(instance));
+            }
+
+            return instance;
+        }
+
+        /**
+         * @param gatewayId the id of the gateway
+         * @param input the stream to read repository from
+         * @return  the versions in the repo for the given gatewayId or null if none
+         * @throws XPathExpressionException in case something goes wrong
+         */
+        public NodeList getVersions(String gatewayId, InputStream input) throws XPathExpressionException {
+            m_gatewayId = gatewayId;
+            return (NodeList) m_versionsExpression.evaluate(new InputSource(input), XPathConstants.NODESET);
+        }
+
+        /**
+         * @param gatewayId the id of the gateway
+         * @param versions the versions to return
+         * @param input the stream to read repository from
+         * @return true if versions can be found, otherwise false
+         * @throws XPathExpressionException if something goes wrong
+         */
+        @SuppressWarnings("boxing")
+        public boolean init(String gatewayId, String[] versions, InputStream input) throws XPathExpressionException {
+            XPathExpression expression = m_expressions.get(versions.length);
+            if (expression == null) {
+                StringBuilder versionStatement = new StringBuilder("//deploymentversions/deploymentversion/attributes/child::gatewayID[text()=$id]/following::version[text()=$0");
+                for (int i = 1; i < versions.length; i++) {
+                    versionStatement.append(" or ").append(".=$").append(i);
+                }
+                versionStatement.append("]/../../..");
+                expression = m_xPath.compile(versionStatement.toString());
+                m_expressions.put(versions.length, expression);
+            }
+            m_gatewayId = gatewayId;
+            m_versions = versions;
+
+            m_node = (Node) expression.evaluate(new InputSource(input), XPathConstants.NODE);
+            return (m_node != null);
+        }
+
+        /**
+         *  @param i the index into the versions form init
+         *  @return the version at index i
+         * @throws XPathExpressionException if something goes wrong
+         */
+        public Node getVersion(int i) throws XPathExpressionException {
+            m_version = m_versions[i];
+            return (Node) m_attributesExpression.evaluate(m_node, XPathConstants.NODE);
+        }
+
+        /**
+         * reset this thread local instance
+         */
+        public void destroy() {
+            m_node = null;
+            m_version = null;
+            m_gatewayId = null;
+            m_versions = null;
+        }
+
+        /**
+         * @param name id|version|<version-index>
+         * @return id->gatewayId | version->version | version-index -> versions[version-index]
+         */
+        public Object resolveVariable(QName name) {
+            String localPart = name.getLocalPart();
+            if ("id".equals(localPart)) {
+                return m_gatewayId;
+            }
+            else if ("version".equals(localPart)) {
+                return m_version;
+            }
+            return m_versions[Integer.parseInt(localPart)];
+        }
+    }
+
+    /**
+     * Helper method to retrieve urls and directives for a gateway-version combination.
+     *
+     * @param input An input stream from which an XML representation of a deployment repository can be read.
+     * @param gatewayId The gatewayId to be used
+     * @param versions An array of versions.
+     * @return An array of lists of URLDirectivePairs. For each version in <code>versions</code>, a separate list will be
+     *         created; the index of a version in the <code>versions</code> array is equal to the index of its result in the
+     *         result array.
+     * @throws IllegalArgumentException if the gatewayId or versions cannot be found in the input stream, or if
+     *         <code>input</code> does not contain an XML stream.
+     */
+    @SuppressWarnings("unchecked")
+    private List<URLDirectivePair>[] getURLDirectivePairs(InputStream input, String gatewayId, String[] versions) throws IllegalArgumentException {
+
+        XPathContext context = XPathContext.getInstance();
+        List<URLDirectivePair>[] result = new List[versions.length]; //unfortunately, we cannot use a typed list array.
+
+        try {
+            if (!context.init(gatewayId, versions, input)) {
+                m_log.log(LogService.LOG_WARNING, "Versions not found for Gateway: " + gatewayId);
+                throw new IllegalArgumentException("Versions not found.");
+            }
+            for (int i = 0; i < versions.length; i++) {
+                result[i] = new ArrayList<URLDirectivePair>();
+
+                // find all artifacts for the version we're currently working on.
+                Node artifactNode = null;
+                try {
+                    artifactNode = context.getVersion(i);
+                }
+                catch (XPathExpressionException e) {
+                    m_log.log(LogService.LOG_WARNING, "Version " + versions[i] + " not found for Gateway: " + gatewayId);
+                    continue;
+                }
+                NodeList artifacts = artifactNode.getChildNodes();
+                // Read the artifacts
+                for (int artifactNumber = 0; artifactNumber < artifacts.getLength(); artifactNumber++) {
+                    Node artifact = artifacts.item(artifactNumber);
+
+                    NodeList artifactElements = artifact.getChildNodes();
+
+                    String url = null;
+                    Map<String, String> directives = new HashMap<String, String>();
+
+                    for (int elementNumber = 0; elementNumber < artifactElements.getLength(); elementNumber++) {
+                        // find the attributes of this artifact we are interested in.
+                        Node element = artifactElements.item(elementNumber);
+
+                        if (element.getNodeName().equals("url")) {
+                            url = element.getTextContent();
+                        }
+                        else if (element.getNodeName().equals("directives")) {
+                            // we found the directives? put all of them into our map.
+                            NodeList directivesElements = element.getChildNodes();
+                            for (int nDirective = 0; nDirective < directivesElements.getLength(); nDirective++) {
+                                Node directivesElement = directivesElements.item(nDirective);
+                                if (!"#text".equals(directivesElement.getNodeName())) {
+                                    directives.put(directivesElement.getNodeName(), directivesElement.getTextContent());
+                                }
+                            }
+                        }
+                    }
+
+                    if (url != null) {
+                        try {
+                            result[i].add(new URLDirectivePair(new URL(url), directives));
+                        }
+                        catch (MalformedURLException mue) {
+                            m_log.log(LogService.LOG_WARNING, "The BundleUrl is malformed: ", mue);
+                        }
+                    }
+                }
+            }
+
+            return result;
+        }
+        catch (XPathExpressionException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+        finally {
+            context.destroy();
+        }
+    }
+
+    /**
+     * Helper to get an input stream to the currently used deployment repository.
+     *
+     * @return An input stream to the repository document. Will return an empty stream if none can be found.
+     * @throws IOException if there is a problem communicating with the local or remote repository.
+     */
+    private InputStream getRepositoryStream() throws IOException {
+        // cache the repositories, since we do not want them to change while we're in this method.
+        CachedRepository cachedRepository = m_cachedRepository;
+        Repository repository = m_directRepository;
+        InputStream result;
+
+        if (cachedRepository != null) {
+            // we can use the cached repository
+            if (cachedRepository.isCurrent()) {
+                result = cachedRepository.getLocal(true);
+            }
+            else {
+                result = cachedRepository.checkout(true);
+            }
+        }
+        else {
+            RangeIterator ri = repository.getRange().iterator();
+            long resultVersion = 0;
+            while (ri.hasNext()) {
+                resultVersion = ri.next();
+            }
+            if (resultVersion != 0) {
+                result = repository.checkout(resultVersion);
+            }
+            else {
+                throw new IllegalArgumentException("There is no deployment information available.");
+            }
+        }
+
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void updated(Dictionary settings) throws ConfigurationException {
+        if (settings != null) {
+            String url = getNotNull(settings, URL, "DeploymentRepository URL not configured.");
+            String name = getNotNull(settings, NAME, "RepositoryName not configured.");
+            String customer = getNotNull(settings, CUSTOMER, "RepositoryCustomer not configured.");
+
+            //create the remote repository and set it.
+            try {
+                BackupRepository backup = null;
+                try {
+                    backup = new FilebasedBackupRepository(File.createTempFile("currentrepository", null), File.createTempFile("backuprepository", null));
+                }
+                catch (Exception e) {
+                    m_log.log(LogService.LOG_WARNING, "Unable to create temporary files for FilebasedBackupRepository");
+                }
+
+                // We always create the remote repository. If we can create a backup repository, we will wrap a CachedRepository
+                // around it.
+                m_directRepository = new RemoteRepository(new URL(url), customer, name);
+                if (backup == null) {
+                    m_cachedRepository = null;
+                }
+                else {
+                    m_cachedRepository = new CachedRepositoryImpl(null, m_directRepository, backup, CachedRepositoryImpl.UNCOMMITTED_VERSION);
+                }
+            }
+            catch (MalformedURLException mue) {
+                throw new ConfigurationException(URL, mue.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Convenience method for getting settings from a configuration dictionary.
+     */
+    @SuppressWarnings("unchecked")
+    private String getNotNull(Dictionary settings, String id, String errorMessage) throws ConfigurationException {
+        String result = (String) settings.get(id);
+        if (result == null) {
+            throw new ConfigurationException(id, errorMessage);
+        }
+        return result;
+    }
+
+    /**
+     * Helper class to store a pair of URL and directive, in which the directive may be empty.
+     */
+    private class URLDirectivePair {
+        final private URL m_url;
+
+        final private Map<String, String> m_directives;
+
+        URLDirectivePair(URL url, Map<String, String> directives) {
+            m_url = url;
+            m_directives = directives;
+        }
+
+        public URL getUrl() {
+            return m_url;
+        }
+
+        public Map<String, String> getDirective() {
+            return m_directives;
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,30 @@
+package net.luminis.liq.deployment.servlet;
+import javax.servlet.http.HttpServlet;
+
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+import net.luminis.liq.deployment.streamgenerator.StreamGenerator;
+
+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.deployment.servlet";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createService()
+            .setInterface(HttpServlet.class.getName(), null)
+            .setImplementation(DeploymentServlet.class)
+            .add(createServiceDependency().setService(StreamGenerator.class).setRequired(true))
+            .add(createServiceDependency().setService(DeploymentProvider.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false))
+            .add(createConfigurationDependency().setPropagate(true).setPid(PID)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // do nothing
+    }
+}
\ No newline at end of file

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/DeploymentServlet.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/DeploymentServlet.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/DeploymentServlet.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/servlet/DeploymentServlet.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,152 @@
+package net.luminis.liq.deployment.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+import net.luminis.liq.deployment.streamgenerator.StreamGenerator;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+
+/**
+ * The DeploymentServlet class provides in a list of versions available for a gateway and a stream
+ * of data containing the DeploymentPackage (or fix package) for a specific gateway and version.
+ */
+public class DeploymentServlet extends HttpServlet implements ManagedService {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final String CURRENT = "current";
+    public static final String VERSIONS = "versions";
+    public static final String DP_MIMETYPE = "application/vnd.osgi.dp";
+    public static final String TEXT_MIMETYPE = "text/plain";
+
+    private volatile LogService m_log;                  /* injected by dependency manager */
+    private volatile StreamGenerator m_streamGenerator; /* injected by dependency manager */
+    private volatile DeploymentProvider m_provider;     /* injected by dependency manager */
+
+    /**
+     * Responds to GET requests sent to this endpoint, the response depends on the requested path:
+     * <li>http://host/endpoint/gatewayid/versions/ returns a list of versions available for the specified gateway
+     * <li>http://host/endpoint/gatewayid/versions/x.y.z returns a deployment package stream for the specified gateway and version
+     *
+     * The status code of the response can be one of the following:
+     * <li><code>HttpServletResponse.SC_BAD_REQUEST</code> - If no gateway is specified or the request is malformed in a different way.
+     * <li><code>HttpServletResponse.SC_NOT_FOUND</code> - If the specified gateway or version does not exist.
+     * <li><code>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</code> - If there was a problem processing the request.
+     * <li><code>HttpServletResponse.SC_OK</code> - If all went fine
+     */
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+        String path = request.getPathInfo();
+        if (path == null) {
+            sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Request URI is invalid");
+            return;
+        }
+
+        String[] pathElements = path.split("/");
+        int numberOfElements = pathElements.length;
+
+        if ((numberOfElements < 3) || (numberOfElements > 4) || !VERSIONS.equals(pathElements[2])) {
+            sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Request URI is invalid");
+            return;
+        }
+
+        String gatewayID = pathElements[1];
+        List<String> versions;
+        try {
+            versions = m_provider.getVersions(gatewayID);
+        }
+        catch (IllegalArgumentException iae) {
+            String description = "Unknown gateway (" + gatewayID + ")";
+            m_log.log(LogService.LOG_WARNING, description, iae);
+            sendError(response, HttpServletResponse.SC_NOT_FOUND, description);
+            return;
+        }
+        catch (IOException ioe) {
+            String description = "Error getting available versions.";
+            m_log.log(LogService.LOG_WARNING, description, ioe);
+            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, description);
+            return;
+        }
+
+        ServletOutputStream output = null;
+        try {
+            output = response.getOutputStream();
+            if (numberOfElements == 3) {
+                response.setContentType(TEXT_MIMETYPE);
+                for (String version : versions) {
+                    output.print(version);
+                    output.print("\n");
+                }
+            }
+            else {
+                String version = pathElements[3];
+                if (!versions.contains(version)) {
+                    sendError(response, HttpServletResponse.SC_NOT_FOUND, "Unknown version (" + version + ")");
+                    return;
+                }
+                String current = request.getParameter(CURRENT);
+
+                InputStream inputStream;
+                if (current != null) {
+                    inputStream = m_streamGenerator.getDeploymentPackage(gatewayID, current, version);
+                }
+                else {
+                    inputStream = m_streamGenerator.getDeploymentPackage(gatewayID, version);
+                }
+
+                response.setContentType(DP_MIMETYPE);
+                byte[] buffer = new byte[1024 * 32];
+                for (int bytesRead = inputStream.read(buffer); bytesRead != -1; bytesRead = inputStream.read(buffer)) {
+                    output.write(buffer, 0, bytesRead);
+                }
+            }
+        }
+        catch (IOException ex) {
+            String description = "Problem reading request or response data";
+            m_log.log(LogService.LOG_WARNING, description, ex);
+            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, description);
+        }
+        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 and description
+    private void sendError(HttpServletResponse response, int statusCode, String description) {
+        m_log.log(LogService.LOG_WARNING, "Deployment request failed: " + description);
+        try {
+            response.sendError(statusCode, description);
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_WARNING, "Unable to send error response with status code '" + statusCode + "'", e);
+        }
+    }
+
+    @Override
+    public String getServletInfo() {
+        return "LiQ Deployment Servlet Endpoint";
+    }
+
+    @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/deployment/streamgenerator/StreamGenerator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/StreamGenerator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/StreamGenerator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/StreamGenerator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,28 @@
+package net.luminis.liq.deployment.streamgenerator;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface StreamGenerator {
+
+    /**
+     * 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;
+
+    /**
+     * 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;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/deployment/streamgenerator/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,27 @@
+package net.luminis.liq.deployment.streamgenerator.impl;
+
+import net.luminis.liq.deployment.provider.DeploymentProvider;
+import net.luminis.liq.deployment.streamgenerator.StreamGenerator;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createService()
+            .setInterface(StreamGenerator.class.getName(), null)
+            .setImplementation(StreamGeneratorImpl.class)
+            .add(createServiceDependency()
+                .setService(DeploymentProvider.class)
+                .setRequired(true)
+                )
+            );
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+    }
+}