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 [9/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/ne...

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/BundleHelper.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/BundleHelper.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/BundleHelper.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/BundleHelper.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,37 @@
+package net.luminis.liq.client.repository.helper.bundle;
+
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+
+import org.osgi.framework.Constants;
+
+/**
+ * Definitions for a BundleHelper, which are used to treat an artifact as a bundle.
+ */
+public interface BundleHelper extends ArtifactHelper {
+    public static final String KEY_SYMBOLICNAME = Constants.BUNDLE_SYMBOLICNAME;
+    public static final String KEY_NAME = Constants.BUNDLE_NAME;
+    public static final String KEY_VERSION = Constants.BUNDLE_VERSION;
+    public static final String KEY_VENDOR = Constants.BUNDLE_VENDOR;
+    public static final String KEY_RESOURCE_PROCESSOR_PID = "Deployment-ProvidesResourceProcessor";
+
+    public static final String MIMETYPE = "application/vnd.osgi.bundle";
+
+    /**
+     * Used to include an OSGi version range (see section 3.2.5 of the core specification) with an association.
+     * When included in the association's properties, this statement will cause the association to automatically
+     * match the highest available bundle version that matches the statement; an open ended range can be
+     * created by passing "0.0.0".<br>
+     * Not specifying this attribute will lead to the <code>Artifact2GroupAssociation</code<'s default behavior,
+     * using all matches for the filter string.
+     */
+    public static final String KEY_ASSOCIATION_VERSIONSTATEMENT = "associationVersionStatement";
+    
+    public boolean isResourceProcessor(ArtifactObject object);
+    public String getResourceProcessorPIDs(ArtifactObject object);
+    public String getSymbolicName(ArtifactObject object);
+    public String getName(ArtifactObject object);
+    public String getVersion(ArtifactObject object);
+    public String getVendor(ArtifactObject object);
+    
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,41 @@
+package net.luminis.liq.client.repository.helper.bundle.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.helper.ArtifactRecognizer;
+import net.luminis.liq.client.repository.helper.bundle.BundleHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Activator class for the Bundle ArtifactHelper.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        Dictionary<String, String> props = new Hashtable<String, String>();
+        props.put(ArtifactObject.KEY_MIMETYPE, BundleHelper.MIMETYPE);
+        BundleHelperImpl helperImpl = new BundleHelperImpl();
+        manager.add(createService()
+            .setInterface(ArtifactHelper.class.getName(), props)
+            .setImplementation(helperImpl));
+        manager.add(createService()
+            .setInterface(ArtifactRecognizer.class.getName(), null)
+            .setImplementation(helperImpl));
+        manager.add(createService()
+            .setInterface(BundleHelper.class.getName(), null)
+            .setImplementation(helperImpl));
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/BundleHelperImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/BundleHelperImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/BundleHelperImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/bundle/impl/BundleHelperImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,264 @@
+package net.luminis.liq.client.repository.helper.bundle.impl;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import net.luminis.liq.client.repository.RepositoryUtil;
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.helper.ArtifactPreprocessor;
+import net.luminis.liq.client.repository.helper.ArtifactRecognizer;
+import net.luminis.liq.client.repository.helper.bundle.BundleHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.util.VersionRange;
+
+import org.osgi.framework.Version;
+
+/**
+ * BundleHelperImpl provides the Artifact Repository with Helper and Recognizer services.
+ */
+public class BundleHelperImpl implements ArtifactRecognizer, BundleHelper {
+    /** A custom <code>Comparator</code>, used to sort bundles in increasing version */
+    private static final Comparator <ArtifactObject> BUNDLE_COMPARATOR = new Comparator<ArtifactObject>() {
+        public int compare(ArtifactObject left, ArtifactObject right) {
+            Version vLeft = new Version(left.getAttribute(BundleHelper.KEY_VERSION));
+            Version vRight = new Version(right.getAttribute(BundleHelper.KEY_VERSION));
+            return vRight.compareTo(vLeft);
+        }
+    };
+
+    /*
+     * From ArtifactHelper
+     */
+
+    public boolean canUse(ArtifactObject object) {
+        if (object == null) {
+            return false;
+        }
+        return (object.getMimetype().equals(MIMETYPE));
+    }
+
+    public <TYPE extends ArtifactObject> String getAssociationFilter(TYPE obj, Map<String, String> properties) {
+        /*
+         * Creates an endpoint filter for an association. If there is a KEY_ASSOCIATION_VERSIONSTATEMENT, a filter
+         * will be created that matches exactly the given range.
+         */
+        if ((properties != null) && properties.containsKey(KEY_ASSOCIATION_VERSIONSTATEMENT)) {
+            String versions = properties.get(KEY_ASSOCIATION_VERSIONSTATEMENT);
+            VersionRange versionRange = null;
+            try {
+                versionRange = VersionRange.parse(versions);
+            }
+            catch (IllegalArgumentException iae) {
+                throw new IllegalArgumentException("version " + ((versions != null) ? versions + " " : "(null) ") + "cannot be parsed into a valid version range statement.");
+            }
+
+            StringBuilder bundleStatement = new StringBuilder("(&(" + KEY_SYMBOLICNAME + "=" + RepositoryUtil.escapeFilterValue(obj.getAttribute(KEY_SYMBOLICNAME)) + ")");
+
+            bundleStatement.append("(" + KEY_VERSION + ">=" + versionRange.getLow() + ")");
+            if (!versionRange.isLowInclusive()) {
+                bundleStatement.append("(!(" + KEY_VERSION + "=" + versionRange.getLow() + "))");
+            }
+
+            if (versionRange.getHigh() != null) {
+                bundleStatement.append("(" + KEY_VERSION + "<=" + versionRange.getHigh() + ")");
+                if (!versionRange.isHighInclusive()) {
+                    bundleStatement.append("(!(" + KEY_VERSION + "=" + versionRange.getHigh() + "))");
+                }
+            }
+
+            bundleStatement.append(")");
+
+            return bundleStatement.toString();
+        }
+        else
+        {
+            if (obj.getAttribute(KEY_VERSION) != null) {
+                return "(&(" + KEY_SYMBOLICNAME + "=" + RepositoryUtil.escapeFilterValue(obj.getAttribute(KEY_SYMBOLICNAME)) + ")(" + KEY_VERSION + "=" + RepositoryUtil.escapeFilterValue(obj.getAttribute(KEY_VERSION)) + "))";
+            }
+            else {
+                return "(&(" + KEY_SYMBOLICNAME + "=" + RepositoryUtil.escapeFilterValue(obj.getAttribute(KEY_SYMBOLICNAME)) + ")(!(" + KEY_VERSION + "=*)))";
+            }
+        }
+    }
+
+    public <TYPE extends ArtifactObject> int getCardinality(TYPE obj, Map<String, String> properties) {
+        /* Normally, all objects that match the filter given by the previous version should be part of the
+         * association. However, when a version statement has been given, only one should be used. */
+        if ((properties != null) && properties.containsKey(BundleHelper.KEY_ASSOCIATION_VERSIONSTATEMENT)) {
+            return 1;
+        }
+        else {
+            return Integer.MAX_VALUE;
+        }
+    }
+
+    public Comparator<ArtifactObject> getComparator() {
+        return BUNDLE_COMPARATOR;
+    }
+
+    public Map<String, String> checkAttributes(Map<String, String> attributes) {
+        return normalizeVersion(attributes);
+    }
+
+    /**
+     * For the filter to work correctly, we need to make sure the version statement is an
+     * OSGi version.
+     */
+    private static Map<String, String> normalizeVersion(Map<String, String> input) {
+        String version = input.get(KEY_VERSION);
+        if (version != null) {
+            try {
+                Version theVersion = new Version(version);
+                input.put(KEY_VERSION, theVersion.toString());
+            }
+            catch (IllegalArgumentException iae) {
+                throw new IllegalArgumentException("The version statement in the bundle cannot be parsed to a valid version.", iae);
+            }
+        }
+
+        return input;
+    }
+
+    /*
+     * From BundleHelper
+     */
+
+    public String[] getDefiningKeys() {
+        return new String[] {KEY_SYMBOLICNAME, KEY_VERSION};
+    }
+
+    public String[] getMandatoryAttributes() {
+        return new String[] {KEY_SYMBOLICNAME};
+    }
+
+    public String getResourceProcessorPIDs(ArtifactObject object) {
+        ensureBundle(object);
+        return object.getAttribute(KEY_RESOURCE_PROCESSOR_PID);
+    }
+
+    public String getSymbolicName(ArtifactObject object) {
+        ensureBundle(object);
+        return object.getAttribute(KEY_SYMBOLICNAME);
+    }
+
+    public String getName(ArtifactObject object) {
+        ensureBundle(object);
+        return object.getAttribute(KEY_NAME);
+    }
+
+    public String getVersion(ArtifactObject object) {
+        ensureBundle(object);
+        return object.getAttribute(KEY_VERSION);
+    }
+
+    public String getVendor(ArtifactObject object) {
+        ensureBundle(object);
+        return object.getAttribute(KEY_VENDOR);
+    }
+
+    public boolean isResourceProcessor(ArtifactObject object) {
+        ensureBundle(object);
+        return object.getAttribute(KEY_RESOURCE_PROCESSOR_PID) != null;
+    }
+
+    private void ensureBundle(ArtifactObject object) {
+        if ((object == null) || !object.getMimetype().equals(MIMETYPE)) {
+            throw new IllegalArgumentException("This ArtifactObject cannot be handled by a BundleHelper.");
+        }
+    }
+
+    /*
+     * From ArtifactRecognizer
+     */
+
+    public boolean canHandle(String mimetype) {
+        return MIMETYPE.equals(mimetype);
+    }
+
+    public Map<String, String> extractMetaData(URL artifact) throws IllegalArgumentException {
+        /*
+         * Opens the URL as a Jar input stream, gets the manifest, and extracts headers from there.
+         */
+        JarInputStream jis = null;
+        try {
+            jis = new JarInputStream(artifact.openStream());
+
+            Attributes manifestAttributes = jis.getManifest().getMainAttributes();
+            Map<String, String> result = new HashMap<String, String>();
+
+            for (String key : new String[] {KEY_NAME, KEY_SYMBOLICNAME, KEY_VERSION, KEY_VENDOR, KEY_RESOURCE_PROCESSOR_PID}) {
+                String value = manifestAttributes.getValue(key);
+                if (value != null) {
+                    result.put(key, value);
+                }
+            }
+
+            if (result.get(KEY_VERSION) == null) {
+                result.put(KEY_VERSION, "0.0.0");
+            }
+
+            result.put(ArtifactHelper.KEY_MIMETYPE, MIMETYPE);
+            result.put(ArtifactObject.KEY_PROCESSOR_PID, "");
+            String name = manifestAttributes.getValue(KEY_NAME);
+            String version = manifestAttributes.getValue(KEY_VERSION);
+            if (name == null) {
+                name = manifestAttributes.getValue(KEY_SYMBOLICNAME);
+            }
+            result.put(ArtifactObject.KEY_ARTIFACT_NAME, name + (version == null ? "" : "-" + version));
+
+            return result;
+        }
+        catch (Exception e) {
+            throw new IllegalArgumentException("Error extracting metadata from artifact.", e);
+        }
+        finally {
+            try {
+                jis.close();
+            }
+            catch (IOException e) {
+                // Too bad.
+            }
+        }
+    }
+
+    public String recognize(URL artifact) {
+        /*
+         * Tries to find out whether this artifact is a bundle by (a) trying to open it as a
+         * jar, (b) trying to extract the manifest, and (c) checking whether that manifest
+         * contains a Bundle-SymbolicName header.
+         */
+        JarInputStream jis = null;
+        try {
+            jis = new JarInputStream(artifact.openStream());
+
+            Manifest manifest = jis.getManifest();
+
+            Attributes mainAttributes = manifest.getMainAttributes();
+            if (mainAttributes.getValue(KEY_SYMBOLICNAME) != null) {
+                return MIMETYPE;
+            }
+        }
+        catch (Exception e) {
+            return null;
+        }
+        finally {
+            try {
+                jis.close();
+            }
+            catch (Exception e) {
+                // Too bad.
+            }
+        }
+        return null;
+    }
+
+    public ArtifactPreprocessor getPreprocessor() {
+        return null;
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/ConfigurationHelper.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/ConfigurationHelper.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/ConfigurationHelper.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/ConfigurationHelper.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,13 @@
+package net.luminis.liq.client.repository.helper.configuration;
+
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+
+/**
+ * Definitions for ConfigurationHelper,used to treat an artifact as an AutoConf file.
+ */
+public interface ConfigurationHelper extends ArtifactHelper {
+    public static final String KEY_FILENAME = "filename";
+
+    public static final String MIMETYPE = "application/xml:osgi-autoconf";
+    public static final String PROCESSOR = "org.osgi.deployment.rp.autoconf";
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,38 @@
+package net.luminis.liq.client.repository.helper.configuration.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.helper.ArtifactRecognizer;
+import net.luminis.liq.client.repository.helper.configuration.ConfigurationHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Activator class for the Configuration ArtifactHelper.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        Dictionary<String, String> props = new Hashtable<String, String>();
+        props.put(ArtifactObject.KEY_MIMETYPE, ConfigurationHelper.MIMETYPE);
+        ConfigurationHelperImpl helperImpl = new ConfigurationHelperImpl();
+        manager.add(createService()
+            .setInterface(ArtifactHelper.class.getName(), props)
+            .setImplementation(helperImpl));
+        manager.add(createService()
+            .setInterface(ArtifactRecognizer.class.getName(), null)
+            .setImplementation(helperImpl));
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,89 @@
+package net.luminis.liq.client.repository.helper.configuration.impl;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import net.luminis.liq.client.repository.helper.ArtifactPreprocessor;
+import net.luminis.liq.client.repository.helper.ArtifactRecognizer;
+import net.luminis.liq.client.repository.helper.base.VelocityArtifactPreprocessor;
+import net.luminis.liq.client.repository.helper.configuration.ConfigurationHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class ConfigurationHelperImpl implements ArtifactRecognizer, ConfigurationHelper {
+
+    public boolean canHandle(String mimetype) {
+        return MIMETYPE.equals(mimetype);
+    }
+
+    public Map<String, String> extractMetaData(URL artifact) throws IllegalArgumentException {
+        Map<String, String> result = new HashMap<String, String>();
+        result.put(KEY_FILENAME, new File(artifact.getFile()).getName());
+        result.put(ArtifactObject.KEY_PROCESSOR_PID, PROCESSOR);
+        result.put(ArtifactObject.KEY_MIMETYPE, MIMETYPE);
+        result.put(ArtifactObject.KEY_ARTIFACT_NAME, result.get(KEY_FILENAME));
+        return result;
+    }
+
+    public String recognize(URL artifact) {
+        try {
+            InputStream in = artifact.openStream();
+            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+            Node first = doc.getFirstChild();
+            NamedNodeMap attributes = first.getAttributes();
+            Node metatype = attributes.getNamedItem("xmlns:metatype");
+            if (new String("http://www.osgi.org/xmlns/metatype/v1.0.0").equals(metatype.getTextContent())) {
+                return MIMETYPE;
+            }
+        }
+        catch (Exception e) {
+            // Does not matter.
+        }
+
+        return null;
+    }
+
+    public boolean canUse(ArtifactObject object) {
+        return MIMETYPE.equals(object.getMimetype());
+    }
+
+    public Map<String, String> checkAttributes(Map<String, String> attributes) {
+        // All necessary checks will be done by the constructor using getMandatoryAttributes.
+        return attributes;
+    }
+
+    public <TYPE extends ArtifactObject> String getAssociationFilter(TYPE obj, Map<String, String> properties) {
+        return "(" + KEY_FILENAME + "=" + obj.getAttribute(KEY_FILENAME) + ")";
+    }
+
+    public <TYPE extends ArtifactObject> int getCardinality(TYPE obj, Map<String, String> properties) {
+        return Integer.MAX_VALUE;
+    }
+
+    public Comparator<ArtifactObject> getComparator() {
+        return null;
+    }
+
+    public String[] getDefiningKeys() {
+        return new String[] {KEY_FILENAME};
+    }
+
+    public String[] getMandatoryAttributes() {
+        return new String[] {KEY_FILENAME};
+    }
+
+    private final static VelocityArtifactPreprocessor VELOCITY_ARTIFACT_PREPROCESSOR = new VelocityArtifactPreprocessor();
+    public ArtifactPreprocessor getPreprocessor() {
+        return VELOCITY_ARTIFACT_PREPROCESSOR;
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/UserAdminHelper.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/UserAdminHelper.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/UserAdminHelper.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/UserAdminHelper.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,11 @@
+package net.luminis.liq.client.repository.helper.user;
+
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+
+/**
+ * Definitions for the UserAdminHelper artifact helper.
+ */
+public interface UserAdminHelper extends ArtifactHelper {
+    public static final String MIMETYPE = "application/vnd.luminis.useradmin";
+    public static final String PROCESSOR = "net.luminis.liq.resourceprocessor.useradmin";
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,40 @@
+package net.luminis.liq.client.repository.helper.user.impl;
+
+import java.util.Properties;
+
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.helper.ArtifactRecognizer;
+import net.luminis.liq.client.repository.helper.user.UserAdminHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+/**
+ * Activator class for the UserAdmin ArtifactHelper.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put(ArtifactObject.KEY_MIMETYPE, UserAdminHelper.MIMETYPE);
+        UserHelperImpl helperImpl = new UserHelperImpl();
+        manager.add(createService()
+            .setInterface(ArtifactHelper.class.getName(), props)
+            .setImplementation(helperImpl));
+        props = new Properties();
+        props.put(Constants.SERVICE_RANKING, 10);
+        manager.add(createService()
+            .setInterface(ArtifactRecognizer.class.getName(), props)
+            .setImplementation(helperImpl));
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/UserHelperImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/UserHelperImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/UserHelperImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/helper/user/impl/UserHelperImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,91 @@
+package net.luminis.liq.client.repository.helper.user.impl;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import net.luminis.liq.client.repository.helper.ArtifactPreprocessor;
+import net.luminis.liq.client.repository.helper.ArtifactRecognizer;
+import net.luminis.liq.client.repository.helper.base.VelocityArtifactPreprocessor;
+import net.luminis.liq.client.repository.helper.user.UserAdminHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+public class UserHelperImpl implements ArtifactRecognizer, UserAdminHelper {
+
+    public boolean canHandle(String mimetype) {
+        return MIMETYPE.equals(mimetype);
+    }
+
+    public Map<String, String> extractMetaData(URL artifact) throws IllegalArgumentException {
+        Map<String, String> result = new HashMap<String, String>();
+        result.put(ArtifactObject.KEY_PROCESSOR_PID, PROCESSOR);
+        result.put(ArtifactObject.KEY_MIMETYPE, MIMETYPE);
+        result.put(ArtifactObject.KEY_ARTIFACT_NAME, new File(artifact.getFile()).getName());
+        return result;
+    }
+
+    public String recognize(URL artifact) {
+        try {
+            InputStream in = artifact.openStream();
+            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+            Node root = doc.getFirstChild();
+            if (!root.getNodeName().equals("roles")) {
+                return null;
+            }
+            for (Node node = root.getFirstChild(); root != null; root = root.getNextSibling()) {
+                if (!node.getNodeName().equals("group") && !node.getNodeName().equals("user") && !node.getNodeName().equals("#text")) {
+                    return null;
+                }
+            }
+            return MIMETYPE;
+        }
+        catch (Exception e) {
+            // Does not matter.
+        }
+
+        return null;
+    }
+
+    public boolean canUse(ArtifactObject object) {
+        return MIMETYPE.equals(object.getMimetype());
+    }
+
+    public Map<String, String> checkAttributes(Map<String, String> attributes) {
+        // All necessary checks will be done by the constructor using getMandatoryAttributes.
+        return attributes;
+    }
+
+    public <TYPE extends ArtifactObject> String getAssociationFilter(TYPE obj, Map<String, String> properties) {
+        return "(" + ArtifactObject.KEY_ARTIFACT_NAME + "=" + obj.getAttribute(ArtifactObject.KEY_ARTIFACT_NAME) + ")";
+    }
+
+    public <TYPE extends ArtifactObject> int getCardinality(TYPE obj, Map<String, String> properties) {
+        return Integer.MAX_VALUE;
+    }
+
+    public Comparator<ArtifactObject> getComparator() {
+        return null;
+    }
+
+    public String[] getDefiningKeys() {
+        return new String[] {ArtifactObject.KEY_ARTIFACT_NAME};
+    }
+
+    public String[] getMandatoryAttributes() {
+        return new String[] {ArtifactObject.KEY_ARTIFACT_NAME};
+    }
+
+    private final static VelocityArtifactPreprocessor VELOCITY_ARTIFACT_PREPROCESSOR = new VelocityArtifactPreprocessor();
+    public ArtifactPreprocessor getPreprocessor() {
+        return VELOCITY_ARTIFACT_PREPROCESSOR;
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,191 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import net.luminis.liq.client.repository.ObjectRepository;
+import net.luminis.liq.client.repository.RepositoryAdmin;
+import net.luminis.liq.client.repository.RepositoryObject;
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.object.Artifact2GroupAssociation;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.DeploymentVersionObject;
+import net.luminis.liq.client.repository.object.GatewayObject;
+import net.luminis.liq.client.repository.object.Group2LicenseAssociation;
+import net.luminis.liq.client.repository.object.GroupObject;
+import net.luminis.liq.client.repository.object.License2GatewayAssociation;
+import net.luminis.liq.client.repository.object.LicenseObject;
+import net.luminis.liq.client.repository.repository.Artifact2GroupAssociationRepository;
+import net.luminis.liq.client.repository.repository.ArtifactRepository;
+import net.luminis.liq.client.repository.repository.DeploymentVersionRepository;
+import net.luminis.liq.client.repository.repository.GatewayRepository;
+import net.luminis.liq.client.repository.repository.Group2LicenseAssociationRepository;
+import net.luminis.liq.client.repository.repository.GroupRepository;
+import net.luminis.liq.client.repository.repository.License2GatewayAssociationRepository;
+import net.luminis.liq.client.repository.repository.LicenseRepository;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.log.LogService;
+import org.osgi.service.prefs.PreferencesService;
+
+/**
+ * Activator for the RepositoryAdmin bundle. Creates and registers the necessary repositories,
+ * plus the repository admin.
+ */
+public class Activator extends DependencyActivatorBase {
+    private DependencyManager m_manager;
+    List<Service[]> m_services;
+
+    private RepositoryAdminImpl m_repositoryAdminImpl;
+    private ChangeNotifierManager m_changeNotifierManager;
+
+    private ArtifactRepositoryImpl m_artifactRepositoryImpl;
+    private GroupRepositoryImpl m_groupRepositoryImpl;
+    private Artifact2GroupAssociationRepositoryImpl m_artifact2GroupAssociationRepositoryImpl;
+    private LicenseRepositoryImpl m_licenseRepositoryImpl;
+    private Group2LicenseAssociationRepositoryImpl m_group2LicenseAssociationRepositoryImpl;
+    private GatewayRepositoryImpl m_gatewayRepositoryImpl;
+    private License2GatewayAssociationRepositoryImpl m_license2GatewayAssociationRepositoryImpl;
+    private DeploymentVersionRepositoryImpl m_deploymentVersionRepositoryImpl;
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        m_manager = manager;
+
+        m_changeNotifierManager = new ChangeNotifierManager();
+        manager.add(createService()
+            .setImplementation(m_changeNotifierManager)
+            .add(createServiceDependency().setService(EventAdmin.class).setRequired(true)));
+
+        m_repositoryAdminImpl = new RepositoryAdminImpl(this, m_changeNotifierManager.getConfiguredNotifier(RepositoryAdmin.PRIVATE_TOPIC_ROOT, RepositoryAdmin.PUBLIC_TOPIC_ROOT, RepositoryAdmin.TOPIC_ENTITY_ROOT));
+        manager.add(createService()
+            .setInterface(RepositoryAdmin.class.getName(), null)
+            .setImplementation(m_repositoryAdminImpl)
+            .add(createServiceDependency().setService(PreferencesService.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+
+    private <T extends RepositoryObject> Service[] registerRepository(Class<? extends ObjectRepository<T>> iface, ObjectRepositoryImpl<?, T> imp, String[] topics) {
+        Service repositoryService = createService()
+            .setInterface(iface.getName(), null)
+            .setImplementation(imp)
+            .add(createServiceDependency().setService(LogService.class).setRequired(false));
+        Dictionary<String, String[]> topic = new Hashtable<String, String[]>();
+        topic.put(EventConstants.EVENT_TOPIC, topics);
+        Service handlerService = createService()
+            .setInterface(EventHandler.class.getName(), topic)
+            .setImplementation(imp);
+
+        m_manager.add(repositoryService);
+        m_manager.add(handlerService);
+        return new Service[] {repositoryService, handlerService};
+    }
+
+    @SuppressWarnings("unchecked")
+    synchronized Map<Class<? extends ObjectRepository>, ObjectRepositoryImpl> publishRepositories() {
+        // create the repository objects, if this is the first time this method is called.
+        if (m_artifactRepositoryImpl == null) {
+            m_artifactRepositoryImpl = new ArtifactRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, ArtifactObject.TOPIC_ENTITY_ROOT));
+            m_groupRepositoryImpl = new GroupRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, GroupObject.TOPIC_ENTITY_ROOT));
+            m_artifact2GroupAssociationRepositoryImpl = new Artifact2GroupAssociationRepositoryImpl(m_artifactRepositoryImpl, m_groupRepositoryImpl, m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, Artifact2GroupAssociation.TOPIC_ENTITY_ROOT));
+            m_licenseRepositoryImpl = new LicenseRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, LicenseObject.TOPIC_ENTITY_ROOT));
+            m_group2LicenseAssociationRepositoryImpl = new Group2LicenseAssociationRepositoryImpl(m_groupRepositoryImpl, m_licenseRepositoryImpl, m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, Group2LicenseAssociation.TOPIC_ENTITY_ROOT));
+            m_gatewayRepositoryImpl = new GatewayRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, GatewayObject.TOPIC_ENTITY_ROOT));
+            m_license2GatewayAssociationRepositoryImpl = new License2GatewayAssociationRepositoryImpl(m_licenseRepositoryImpl, m_gatewayRepositoryImpl, m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, License2GatewayAssociation.TOPIC_ENTITY_ROOT));
+            m_deploymentVersionRepositoryImpl = new DeploymentVersionRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, DeploymentVersionObject.TOPIC_ENTITY_ROOT));
+        }
+        // first, register the artifact repository manually; it needs some special care.
+        Service artifactRepoService = createService()
+            .setInterface(ArtifactRepository.class.getName(), null)
+            .setImplementation(m_artifactRepositoryImpl)
+            .add(createServiceDependency().setService(LogService.class).setRequired(false))
+            .add(createServiceDependency().setService(ArtifactHelper.class).setRequired(false).setAutoConfig(false).setCallbacks(this, "addArtifactHelper", "removeArtifactHelper"));
+        Dictionary<String, String[]> topic = new Hashtable<String, String[]>();
+        topic.put(EventConstants.EVENT_TOPIC, new String[] {});
+        Service artifactHandlerService = createService()
+            .setInterface(EventHandler.class.getName(), topic)
+            .setImplementation(m_artifactRepositoryImpl);
+        m_manager.add(artifactRepoService);
+        m_manager.add(artifactHandlerService);
+
+        m_services = new ArrayList<Service[]>();
+        m_services.add(new Service[] {artifactRepoService, artifactHandlerService});
+
+        // register all repositories are services. Keep the service objects around, we need them to pull the services later.
+        m_services.add(registerRepository(Artifact2GroupAssociationRepository.class, m_artifact2GroupAssociationRepositoryImpl, new String[] {createPrivateObjectTopic(ArtifactObject.TOPIC_ENTITY_ROOT), createPrivateObjectTopic(GroupObject.TOPIC_ENTITY_ROOT)}));
+        m_services.add(registerRepository(GroupRepository.class, m_groupRepositoryImpl, new String[] {}));
+        m_services.add(registerRepository(Group2LicenseAssociationRepository.class, m_group2LicenseAssociationRepositoryImpl, new String[] {createPrivateObjectTopic(GroupObject.TOPIC_ENTITY_ROOT), createPrivateObjectTopic(LicenseObject.TOPIC_ENTITY_ROOT)}));
+        m_services.add(registerRepository(LicenseRepository.class, m_licenseRepositoryImpl, new String[] {}));
+        m_services.add(registerRepository(License2GatewayAssociationRepository.class, m_license2GatewayAssociationRepositoryImpl, new String[] {createPrivateObjectTopic(LicenseObject.TOPIC_ENTITY_ROOT), createPrivateObjectTopic(GatewayObject.TOPIC_ENTITY_ROOT)}));
+        m_services.add(registerRepository(GatewayRepository.class, m_gatewayRepositoryImpl, new String[] {}));
+        m_services.add(registerRepository(DeploymentVersionRepository.class, m_deploymentVersionRepositoryImpl, new String[] {}));
+
+        // prepare the results.
+        Map<Class<? extends ObjectRepository>, ObjectRepositoryImpl> result = new HashMap<Class<? extends ObjectRepository>, ObjectRepositoryImpl>();
+
+        result.put(ArtifactRepository.class, m_artifactRepositoryImpl);
+        result.put(Artifact2GroupAssociationRepository.class, m_artifact2GroupAssociationRepositoryImpl);
+        result.put(GroupRepository.class, m_groupRepositoryImpl);
+        result.put(Group2LicenseAssociationRepository.class, m_group2LicenseAssociationRepositoryImpl);
+        result.put(LicenseRepository.class, m_licenseRepositoryImpl);
+        result.put(License2GatewayAssociationRepository.class, m_license2GatewayAssociationRepositoryImpl);
+        result.put(GatewayRepository.class, m_gatewayRepositoryImpl);
+        result.put(DeploymentVersionRepository.class, m_deploymentVersionRepositoryImpl);
+
+        return result;
+    }
+
+    /**
+     * Helper method for use in publishRepositories
+     */
+    private static String createPrivateObjectTopic(String entityRoot) {
+        return RepositoryObject.PRIVATE_TOPIC_ROOT + entityRoot + RepositoryObject.TOPIC_ALL_SUFFIX;
+    }
+
+    /**
+     * Pulls all repository services; is used to make sure the repositories go away before the RepositoryAdmin does.
+     */
+    synchronized void pullRepositories() {
+        for (Service[] services : m_services) {
+            for (Service service : services) {
+                m_manager.remove(service);
+            }
+        }
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        if (m_repositoryAdminImpl.loggedIn()) {
+            try {
+                m_repositoryAdminImpl.logout(true);
+            }
+            catch (IOException ioe) {
+                // Not much to do about this. We could log it?
+            }
+        }
+        m_repositoryAdminImpl = null;
+    }
+
+    public void addArtifactHelper(ServiceReference ref, ArtifactHelper helper) {
+        String mimetype = (String) ref.getProperty(ArtifactHelper.KEY_MIMETYPE);
+        m_artifactRepositoryImpl.addHelper(mimetype, helper);
+    }
+
+    public synchronized void removeArtifactHelper(ServiceReference ref, ArtifactHelper helper) {
+        String mimetype = (String) ref.getProperty(ArtifactHelper.KEY_MIMETYPE);
+        m_artifactRepositoryImpl.removeHelper(mimetype, helper);
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,32 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.util.Map;
+
+import net.luminis.liq.client.repository.object.Artifact2GroupAssociation;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.GroupObject;
+
+import org.osgi.framework.InvalidSyntaxException;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the Artifact2GroupAssociation. For 'what it does', see Artifact2GroupAssociation,
+ * for 'how it works', see AssociationImpl.
+ */
+public class Artifact2GroupAssociationImpl extends AssociationImpl<ArtifactObject, GroupObject, Artifact2GroupAssociation> implements Artifact2GroupAssociation {
+    private final static String XML_NODE = "artifact2group";
+
+    public Artifact2GroupAssociationImpl(Map<String, String> attributes, ChangeNotifier notifier, ArtifactRepositoryImpl artifactRepository, GroupRepositoryImpl groupRepository) throws InvalidSyntaxException {
+        super(attributes, notifier, ArtifactObject.class, GroupObject.class, artifactRepository, groupRepository, XML_NODE);
+    }
+
+    public Artifact2GroupAssociationImpl(Map<String, String> attributes, Map<String, String> tags, ChangeNotifier notifier, ArtifactRepositoryImpl artifactRepository, GroupRepositoryImpl groupRepository) throws InvalidSyntaxException {
+        super(attributes, tags, notifier, ArtifactObject.class, GroupObject.class, artifactRepository, groupRepository, XML_NODE);
+    }
+
+    public Artifact2GroupAssociationImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, ArtifactRepositoryImpl artifactRepository, GroupRepositoryImpl groupRepository) throws InvalidSyntaxException {
+        super(reader, notifier, ArtifactObject.class, GroupObject.class, null, null, artifactRepository, groupRepository, XML_NODE);
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationRepositoryImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationRepositoryImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationRepositoryImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/Artifact2GroupAssociationRepositoryImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,59 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.util.Map;
+
+import net.luminis.liq.client.repository.object.Artifact2GroupAssociation;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.GroupObject;
+import net.luminis.liq.client.repository.repository.Artifact2GroupAssociationRepository;
+
+import org.osgi.framework.InvalidSyntaxException;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the Artifact2GroupAssociationRepository. For 'what it does', see Artifact2GroupAssociationRepository,
+ * for 'how it works', see AssociationRepositoryImpl.
+ */
+public class Artifact2GroupAssociationRepositoryImpl extends AssociationRepositoryImpl<ArtifactObject, GroupObject, Artifact2GroupAssociationImpl, Artifact2GroupAssociation> implements Artifact2GroupAssociationRepository {
+    private final static String XML_NODE = "artifacts2groups";
+
+    private final ArtifactRepositoryImpl m_bundleRepository;
+    private final GroupRepositoryImpl m_groupRepository;
+
+    public Artifact2GroupAssociationRepositoryImpl(ArtifactRepositoryImpl bundleRepository, GroupRepositoryImpl groupRepository, ChangeNotifier notifier) {
+        super(notifier, XML_NODE);
+        m_bundleRepository = bundleRepository;
+        m_groupRepository = groupRepository;
+    }
+
+    @Override
+    Artifact2GroupAssociationImpl createNewInhabitant(Map<String, String> attributes) {
+        try {
+            return new Artifact2GroupAssociationImpl(attributes, this, m_bundleRepository, m_groupRepository);
+        }
+        catch (InvalidSyntaxException e) {
+            throw new IllegalArgumentException("Unable to create association: ", e);
+        }
+    }
+
+    @Override
+    Artifact2GroupAssociationImpl createNewInhabitant(Map<String, String> attributes, Map<String, String> tags) {
+        try {
+            return new Artifact2GroupAssociationImpl(attributes, tags, this, m_bundleRepository, m_groupRepository);
+        }
+        catch (InvalidSyntaxException e) {
+            throw new IllegalArgumentException("Unable to create association: ", e);
+        }
+    }
+
+    @Override
+    Artifact2GroupAssociationImpl createNewInhabitant(HierarchicalStreamReader reader) {
+        try {
+            return new Artifact2GroupAssociationImpl(reader, this, m_bundleRepository, m_groupRepository);
+        }
+        catch (InvalidSyntaxException e) {
+            throw new IllegalArgumentException("Unable to create association: ", e);
+        }
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactObjectImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactObjectImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactObjectImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactObjectImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,121 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.object.Artifact2GroupAssociation;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.GroupObject;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the ArtifactObject. For 'what it does', see ArtifactObject,
+ * for 'how it works', see RepositoryObjectImpl.<br>
+ * <br>
+ * Some functionality of this class is delegated to implementers of {@link ArtifactHelper}.
+ */
+public class ArtifactObjectImpl extends RepositoryObjectImpl<ArtifactObject> implements ArtifactObject {
+    private final static String XML_NODE = "artifact";
+
+    /*
+     * As a general rule, RepositoryObjects do not know about their repository. However, since the Helper
+     * to be used is dictated by the repository, this rule is broken for this class.
+     */
+    private ArtifactRepositoryImpl m_repo;
+
+    ArtifactObjectImpl(Map<String, String> attributes, String[] mandatoryAttributes, ChangeNotifier notifier, ArtifactRepositoryImpl repo) {
+        super(checkAttributes(attributes, completeMandatoryAttributes(mandatoryAttributes)), notifier, XML_NODE);
+        m_repo = repo;
+    }
+
+    ArtifactObjectImpl(Map<String, String> attributes, String[] mandatoryAttributes, Map<String, String> tags, ChangeNotifier notifier, ArtifactRepositoryImpl repo) {
+        super(checkAttributes(attributes, completeMandatoryAttributes(mandatoryAttributes)), tags, notifier, XML_NODE);
+        m_repo = repo;
+    }
+
+    private static String[] completeMandatoryAttributes(String[] mandatory) {
+        String[] result = new String[mandatory.length + 2];
+        for (int i = 0; i < mandatory.length; i++) {
+            result[i] = mandatory[i];
+        }
+        result[mandatory.length] = KEY_MIMETYPE;
+        result[mandatory.length + 1 ] = KEY_URL;
+        return result;
+    }
+
+    ArtifactObjectImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, ArtifactRepositoryImpl repo) {
+        super(reader, notifier, XML_NODE);
+        m_repo = repo;
+    }
+
+    public List<GroupObject> getGroups() {
+        return getAssociations(GroupObject.class);
+    }
+
+    public List<Artifact2GroupAssociation> getAssociationsWith(GroupObject group) {
+        return getAssociationsWith(group, GroupObject.class, Artifact2GroupAssociation.class);
+    }
+
+    @Override
+    public String getAssociationFilter(Map<String, String> properties) {
+        return getHelper().getAssociationFilter(this, properties);
+    }
+
+    @Override
+    public int getCardinality(Map<String, String> properties) {
+        return getHelper().getCardinality(this, properties);
+    }
+
+    @Override
+    public Comparator<ArtifactObject> getComparator() {
+        return getHelper().getComparator();
+    }
+
+    @Override
+    String[] getDefiningKeys() {
+        String[] fromHelper = getHelper().getDefiningKeys();
+
+        String[] result = new String[fromHelper.length + 1];
+        for (int i = 0; i < fromHelper.length; i++) {
+            result[i] = fromHelper[i];
+        }
+        result[fromHelper.length] = KEY_URL;
+
+        return result;
+    }
+
+    public String getURL() {
+        return getAttribute(KEY_URL);
+    }
+
+    public String getMimetype() {
+        return getAttribute(KEY_MIMETYPE);
+    }
+
+    public String getProcessorPID() {
+        return getAttribute(KEY_PROCESSOR_PID);
+    }
+
+    public void setProcessorPID(String processorPID) {
+        addAttribute(KEY_PROCESSOR_PID, processorPID);
+    }
+
+    public String getName() {
+        return getAttribute(KEY_ARTIFACT_NAME);
+    }
+
+    public String getDescription() {
+        return getAttribute(KEY_ARTIFACT_DESCRIPTION);
+    }
+
+    private synchronized ArtifactHelper getHelper() {
+        return m_repo.getHelper(getMimetype());
+    }
+
+    public void setDescription(String value) {
+        addAttribute(KEY_ARTIFACT_DESCRIPTION, value);
+    }
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactRepositoryImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactRepositoryImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactRepositoryImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/ArtifactRepositoryImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,550 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.luminis.liq.client.repository.RepositoryObject;
+import net.luminis.liq.client.repository.RepositoryUtil;
+import net.luminis.liq.client.repository.helper.ArtifactHelper;
+import net.luminis.liq.client.repository.helper.ArtifactPreprocessor;
+import net.luminis.liq.client.repository.helper.ArtifactRecognizer;
+import net.luminis.liq.client.repository.helper.PropertyResolver;
+import net.luminis.liq.client.repository.helper.bundle.BundleHelper;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.GatewayObject;
+import net.luminis.liq.client.repository.object.GroupObject;
+import net.luminis.liq.client.repository.object.LicenseObject;
+import net.luminis.liq.client.repository.repository.ArtifactRepository;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the ArtifactRepository. For 'what it does', see ArtifactRepository,
+ * for 'how it works', see ObjectRepositoryImpl.<br>
+ * <br>
+ * This class has some extended functionality when compared to <code>ObjectRepositoryImpl</code>,
+ * <ul>
+ * <li> it keeps track of all <code>ArtifactHelper</code>s, and serves them to its inhabitants.
+ * <li> it handles importing of artifacts.
+ * </ul>
+ */
+public class ArtifactRepositoryImpl extends ObjectRepositoryImpl<ArtifactObjectImpl, ArtifactObject> implements ArtifactRepository {
+    private final static String XML_NODE = "artifacts";
+    private volatile BundleContext m_context; /*Injected by dependency manager*/
+    private volatile LogService m_log; /*Injected by dependency manager*/
+    private Map<String, ArtifactHelper> m_helpers = new HashMap<String, ArtifactHelper>();
+    private URL m_obrBase;
+
+    /**
+     * Custom comparator which sorts service references by service rank, highest rank first.
+     */
+    private static Comparator<ServiceReference> SERVICE_RANK_COMPARATOR = new Comparator<ServiceReference>() {
+        public int compare(ServiceReference o1, ServiceReference o2) {
+            int rank1 = 0;
+            int rank2 = 0;
+            try {
+                Object rankObject1 = o1.getProperty(Constants.SERVICE_RANKING);
+                rank1 = (rankObject1 == null) ? 0 : ((Integer) rankObject1).intValue();
+            }
+            catch (ClassCastException cce) {
+                // No problem.
+            }
+            try {
+                Object rankObject2 = o2.getProperty(Constants.SERVICE_RANKING);
+                rank1 = (rankObject2 == null) ? 0 : ((Integer) rankObject2).intValue();
+            }
+            catch (ClassCastException cce) {
+                // No problem.
+            }
+
+            return rank1 - rank2;
+        }
+    };
+
+
+    public ArtifactRepositoryImpl(ChangeNotifier notifier) {
+        super(notifier, XML_NODE);
+    }
+
+    public List<ArtifactObject> getResourceProcessors() {
+        try {
+            return super.get(createFilter("(" + BundleHelper.KEY_RESOURCE_PROCESSOR_PID + "=*)"));
+        }
+        catch (InvalidSyntaxException e) {
+            m_log.log(LogService.LOG_ERROR, "getResourceProcessors' filter returned an InvalidSyntaxException.", e);
+        }
+        return new ArrayList<ArtifactObject>();
+    }
+
+    @Override
+    public List<ArtifactObject> get(Filter filter) {
+        // Note that this excludes any Bundle artifacts which are resource processors.
+        try {
+            Filter extendedFilter = createFilter("(&" + filter.toString() + "(!(" + BundleHelper.KEY_RESOURCE_PROCESSOR_PID + "=*)))");
+            return super.get(extendedFilter);
+        }
+        catch (InvalidSyntaxException e) {
+            m_log.log(LogService.LOG_ERROR, "Extending " + filter.toString() + " resulted in an InvalidSyntaxException.", e);
+        }
+        return new ArrayList<ArtifactObject>();
+    }
+
+    @Override
+    public List<ArtifactObject> get() {
+        // Note that this excludes any Bundle artifacts which are resource processors.
+        try {
+            return super.get(createFilter("(!(" + RepositoryUtil.escapeFilterValue(BundleHelper.KEY_RESOURCE_PROCESSOR_PID) + "=*))"));
+        }
+        catch (InvalidSyntaxException e) {
+            m_log.log(LogService.LOG_ERROR, "get's filter returned an InvalidSyntaxException.", e);
+        }
+        return new ArrayList<ArtifactObject>();
+    }
+
+    @Override
+    ArtifactObjectImpl createNewInhabitant(Map<String, String> attributes) {
+        ArtifactHelper helper = getHelper(attributes.get(ArtifactObject.KEY_MIMETYPE));
+        return new ArtifactObjectImpl(helper.checkAttributes(attributes), helper.getMandatoryAttributes(), this, this);
+    }
+
+    @Override
+    ArtifactObjectImpl createNewInhabitant(Map<String, String> attributes, Map<String, String> tags) {
+        ArtifactHelper helper = getHelper(attributes.get(ArtifactObject.KEY_MIMETYPE));
+        return new ArtifactObjectImpl(helper.checkAttributes(attributes), helper.getMandatoryAttributes(), tags, this, this);
+    }
+
+    @Override
+    ArtifactObjectImpl createNewInhabitant(HierarchicalStreamReader reader) {
+        return new ArtifactObjectImpl(reader, this, this);
+    }
+
+    /**
+     * Helper method for this repository's inhabitants, which finds the necessary helpers.
+     * @param mimetype The mimetype for which a helper should be found.
+     * @return An artifact helper for the given mimetype.
+     * @throws IllegalArgumentException when the mimetype is invalid, or no helpers are available.
+     */
+    ArtifactHelper getHelper(String mimetype) {
+        synchronized(m_helpers) {
+            if ((mimetype == null) || (mimetype.length() == 0)) {
+                throw new IllegalArgumentException("Without a mimetype, we cannot find a helper.");
+            }
+
+            ArtifactHelper helper = m_helpers.get(mimetype.toLowerCase());
+
+            if (helper == null) {
+                throw new IllegalArgumentException("There are no ArtifactHelpers known for type '" + mimetype + "'.");
+            }
+
+            return helper;
+        }
+    }
+
+    /**
+     * Method intended for adding artifact helpers by the bundle's activator.
+     */
+    void addHelper(String mimetype, ArtifactHelper helper) {
+        synchronized(m_helpers) {
+            if ((mimetype == null) || (mimetype.length() == 0)) {
+                m_log.log(LogService.LOG_WARNING, "An ArtifactHelper has been published without a proper mimetype.");
+            }
+            else {
+                m_helpers.put(mimetype.toLowerCase(), helper);
+            }
+        }
+    }
+
+    /**
+     * Method intended for removing artifact helpers by the bundle's activator.
+     */
+    void removeHelper(String mimetype, ArtifactHelper helper) {
+        synchronized(m_helpers) {
+            if ((mimetype == null) || (mimetype.length() == 0)) {
+                m_log.log(LogService.LOG_WARNING, "An ArtifactHelper is being removed without a proper mimetype.");
+            }
+            else {
+                m_helpers.remove(mimetype.toLowerCase());
+            }
+        }
+    }
+
+    /**
+     * Utility function that takes either a URL or a String representing a mimetype,
+     * and returns the corresponding <code>ArtifactHelper</code>, <code>ArtifactRecognizer</code>
+     * and, if not specified, the mimetype.
+     * @param input Either a <code>URL</code> pointing to a physical artifact, or a <code>String</code>
+     * representing a mime type.
+     * @return A mapping from a class (<code>ArtifactRecognizer</code>, <code>ArtifactHelper</code> or
+     * <code>String</code> to an instance of that class as a result.
+     */
+    protected Map<Class<?>, Object> findRecognizerAndHelper(Object input) throws IllegalArgumentException {
+        // check input.
+        URL url = null;
+        String mimetype = null;
+        if (input instanceof URL) {
+            url = (URL) input;
+        }
+        else if (input instanceof String) {
+            mimetype = (String) input;
+        }
+        else {
+            throw new IllegalArgumentException("findRecognizer received an unrecognized input.");
+        }
+
+        // Get all published ArtifactRecognizers.
+        ServiceReference[] refs = null;
+        try {
+            refs = m_context.getServiceReferences(ArtifactRecognizer.class.getName(), null);
+        }
+        catch (InvalidSyntaxException e) {
+            // We do not pass in a filter, so this should not happen.
+            m_log.log(LogService.LOG_WARNING, "A null filter resulted in an InvalidSyntaxException from getServiceReferences.");
+        }
+
+        if (refs == null) {
+            throw new IllegalArgumentException("There are no artifact recognizers available.");
+        }
+
+        // If available, sort the references by service ranking.
+        Arrays.sort(refs, SERVICE_RANK_COMPARATOR);
+
+        // Check all referenced services to find one that matches our input.
+        ArtifactRecognizer recognizer = null;
+        String foundMimetype = null;
+        for (ServiceReference ref : refs) {
+            ArtifactRecognizer candidate = (ArtifactRecognizer) m_context.getService(ref);
+            if (mimetype != null) {
+                if (candidate.canHandle(mimetype)) {
+                    recognizer = candidate;
+                    break;
+                }
+            }
+            else {
+                String candidateMime = candidate.recognize(url);
+                if (candidateMime != null) {
+                    foundMimetype = candidateMime;
+                    recognizer = candidate;
+                    break;
+                }
+            }
+        }
+
+        if (recognizer == null) {
+            throw new IllegalArgumentException("There is no artifact recognizer that recognizes artifact " + ((url == null) ? "(null)" : url.toString()));
+        }
+
+        // Package the results in the map.
+        Map<Class<?>, Object> result = new HashMap<Class<?>, Object>();
+        result.put(ArtifactRecognizer.class, recognizer);
+        if (mimetype == null) {
+            result.put(ArtifactHelper.class, getHelper(foundMimetype));
+            result.put(String.class, foundMimetype);
+        }
+        else {
+            result.put(ArtifactHelper.class, getHelper(mimetype));
+        }
+
+        return result;
+    }
+
+    public boolean recognizeArtifact(URL artifact) {
+        try {
+            Map<Class<?>, Object> fromArtifact = findRecognizerAndHelper(artifact);
+            String mimetype = (String) fromArtifact.get(String.class);
+            return mimetype != null;
+        }
+        catch (Exception e) {
+            //too bad... Nothing to do now.
+            return false;
+        }
+    }
+
+    public ArtifactObject importArtifact(URL artifact, boolean upload) throws IllegalArgumentException, IOException {
+        try {
+            if ((artifact == null) || (artifact.toString().length() == 0)) {
+                throw new IllegalArgumentException("The URL to import cannot be null or empty.");
+            }
+            checkURL(artifact);
+
+            Map<Class<?>, Object> fromArtifact = findRecognizerAndHelper(artifact);
+            ArtifactRecognizer recognizer = (ArtifactRecognizer) fromArtifact.get(ArtifactRecognizer.class);
+            ArtifactHelper helper = (ArtifactHelper) fromArtifact.get(ArtifactHelper.class);
+            String mimetype = (String) fromArtifact.get(String.class);
+
+            return importArtifact(artifact, recognizer, helper, mimetype, false, upload);
+        }
+        catch (IllegalArgumentException iae) {
+            m_log.log(LogService.LOG_INFO, "Error importing artifact: " + iae.getMessage());
+            throw iae;
+        }
+        catch (IOException ioe) {
+            m_log.log(LogService.LOG_INFO, "Error storing artifact: " + ioe.getMessage());
+            throw ioe;
+        }
+    }
+
+    public ArtifactObject importArtifact(URL artifact, String mimetype, boolean upload) throws IllegalArgumentException, IOException {
+        try {
+            if ((artifact == null) || (artifact.toString().length() == 0)) {
+                throw new IllegalArgumentException("The URL to import cannot be null or empty.");
+            }
+            if ((mimetype == null) || (mimetype.length() == 0)) {
+                throw new IllegalArgumentException("The mimetype of the artifact to import cannot be null or empty.");
+            }
+
+            checkURL(artifact);
+
+            Map<Class<?>, Object> fromMimetype = findRecognizerAndHelper(mimetype);
+            ArtifactRecognizer recognizer = (ArtifactRecognizer) fromMimetype.get(ArtifactRecognizer.class);
+            ArtifactHelper helper = (ArtifactHelper) fromMimetype.get(ArtifactHelper.class);
+
+            return importArtifact(artifact, recognizer, helper, mimetype, true, upload);
+        }
+        catch (IllegalArgumentException iae) {
+            m_log.log(LogService.LOG_INFO, "Error importing artifact: " + iae.getMessage());
+            throw iae;
+        }
+        catch (IOException ioe) {
+            m_log.log(LogService.LOG_INFO, "Error storing artifact: " + ioe.getMessage());
+            throw ioe;
+        }
+    }
+
+    private ArtifactObject importArtifact(URL artifact, ArtifactRecognizer recognizer, ArtifactHelper helper, String mimetype, boolean overwrite, boolean upload) throws IOException {
+        Map<String, String> attributes = recognizer.extractMetaData(artifact);
+        Map<String, String> tags = new HashMap<String, String>();
+
+        helper.checkAttributes(attributes);
+
+        URL location;
+        if (upload) {
+            location = upload(artifact, mimetype);
+        }
+        else {
+            location = artifact;
+        }
+
+        attributes.put(ArtifactObject.KEY_URL, location.toString());
+        attributes.put(ArtifactObject.KEY_ARTIFACT_DESCRIPTION, "");
+        if (overwrite) {
+            attributes.put(ArtifactObject.KEY_MIMETYPE, mimetype);
+        }
+
+        return create(attributes, tags);
+    }
+
+    /**
+     * Helper method which checks a given URL for 'validity', that is, does this URL point
+     * to something that can be read.
+     * @param artifact A URL pointing to an artifact.
+     * @throws IllegalArgumentException when the URL does not point to a valid file.
+     */
+
+    private void checkURL(URL artifact) throws IllegalArgumentException {
+        // First, check whether we can actually reach something from this URL.
+        InputStream is = null;
+        try {
+            is = artifact.openStream();
+        }
+        catch (IOException ioe) {
+            throw new IllegalArgumentException("Artifact " + artifact.toString() + "does not point to a valid file.");
+        }
+        finally {
+            if (is != null) {
+                try {
+                    is.close();
+                }
+                catch (IOException ioe) {
+                    // Too bad, nothing to do.
+                }
+            }
+        }
+
+        // Then, check whether the name is legal.
+        for (byte b : artifact.toString().substring(artifact.toString().lastIndexOf('/') + 1).getBytes()) {
+            if (!(((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z')) || ((b >= '0') && (b <= '9')) || (b == '.') || (b == '-') || (b == '_'))) {
+                throw new IllegalArgumentException("Artifact " + artifact.toString() + "'s name contains an illegal character '" + new String(new byte[] {b}) + "'");
+            }
+        }
+
+    }
+
+    /**
+     * Uploads an artifact to the OBR.
+     * @param artifact URL pointing to the local artifact.
+     * @param mimetype The mimetype of this artifact.
+     * @return The persistent URL of this artifact.
+     * @throws IOException for any problem uploading the artifact.
+     */
+    private URL upload(URL artifact, String mimetype) throws IOException {
+        if (m_obrBase == null) {
+            throw new IOException("There is no storage available for this artifact.");
+        }
+
+        InputStream input = null;
+        OutputStream output = null;
+        URL url = null;
+        try {
+            input = artifact.openStream();
+            url = new URL(m_obrBase, new File(artifact.getFile()).getName());
+            URLConnection connection = url.openConnection();
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+            connection.setUseCaches(false);
+            connection.setRequestProperty("Content-Type", mimetype);
+            output = connection.getOutputStream();
+            byte[] buffer = new byte[4 * 1024];
+            for (int count = input.read(buffer); count != -1; count = input.read(buffer)) {
+                output.write(buffer, 0, count);
+            }
+            output.close();
+            if (connection instanceof HttpURLConnection) {
+                int responseCode = ((HttpURLConnection) connection).getResponseCode();
+                switch (responseCode) {
+                    case HttpURLConnection.HTTP_OK :
+                        break;
+                    case HttpURLConnection.HTTP_CONFLICT:
+                        throw new IOException("Artifact already exists in storage.");
+                    case HttpURLConnection.HTTP_INTERNAL_ERROR:
+                        throw new IOException("The storage server returned an internal server error.");
+                    default:
+                        throw new IOException("The storage server returned code " + responseCode + " writing to " + url.toString());
+                }
+            }
+        }
+        catch (IOException ioe) {
+            throw new IOException("Error importing artifact " + artifact.toString() + ": " + ioe.getMessage());
+        }
+        finally {
+            if (input != null) {
+                try {
+                    input.close();
+                }
+                catch (Exception ex) {
+                    // Not much we can do
+                }
+            }
+            if (output != null) {
+                try {
+                    output.close();
+                }
+                catch (Exception ex) {
+                    // Not much we can do
+                }
+            }
+        }
+
+        return url;
+    }
+
+    public void setObrBase(URL obrBase) {
+        m_obrBase = obrBase;
+    }
+
+    public String preprocessArtifact(ArtifactObject artifact, GatewayObject gateway, String gatewayID, String version) throws IOException {
+        ArtifactPreprocessor preprocessor = getHelper(artifact.getMimetype()).getPreprocessor();
+        if (preprocessor == null) {
+            return artifact.getURL();
+        }
+        else {
+            return preprocessor.preprocess(artifact.getURL(), new GatewayPropertyResolver(gateway), gatewayID, version, m_obrBase);
+        }
+    }
+
+    public boolean needsNewVersion(ArtifactObject artifact, GatewayObject gateway, String gatewayID, String fromVersion) {
+        ArtifactPreprocessor preprocessor = getHelper(artifact.getMimetype()).getPreprocessor();
+        if (preprocessor == null) {
+            return false;
+        }
+        else {
+            return preprocessor.needsNewVersion(artifact.getURL(), new GatewayPropertyResolver(gateway), gatewayID, fromVersion);
+        }
+    }
+
+    private static class GatewayPropertyResolver implements PropertyResolver {
+
+        private final GatewayObject m_go;
+
+        public GatewayPropertyResolver(GatewayObject go) {
+            m_go = go;
+        }
+
+        public String get(String key) {
+            return get(key, m_go);
+        }
+
+        private String get(String key, RepositoryObject ro) {
+            // Is it in this object?
+            String result = findKeyInObject(ro, key);
+            if (result != null) {
+                return result;
+            }
+
+            // Is it in one of the children?
+            List<RepositoryObject> children = getChildren(ro);
+            for (RepositoryObject child : children) {
+                result = findKeyInObject(child, key);
+                if (result != null) {
+                    return result;
+                }
+            }
+
+            // Not found yet? then continue to the next level recursively.
+            for (RepositoryObject child : children) {
+                result = get(key, child);
+                if (result != null) {
+                    return result;
+                }
+            }
+            return result;
+        }
+
+        private List getChildren(RepositoryObject ob) {
+            if (ob instanceof GatewayObject) {
+                return ((GatewayObject) ob).getLicenses();
+            }
+            else if (ob instanceof LicenseObject) {
+                return ((LicenseObject) ob).getGroups();
+            }
+            else if (ob instanceof GroupObject) {
+                return ((GroupObject) ob).getArtifacts();
+            }
+            return new ArrayList();
+        }
+
+        private String findKeyInObject(RepositoryObject ro, String key) {
+            String result;
+            if ((result = ro.getAttribute(key)) != null) {
+                return result;
+            }
+            if ((result = ro.getTag(key)) != null) {
+                return result;
+            }
+            return null;
+        }
+
+    }
+
+    public URL getObrBase() {
+        return m_obrBase;
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/AssociationImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/AssociationImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/AssociationImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/AssociationImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,234 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import net.luminis.liq.client.repository.Associatable;
+import net.luminis.liq.client.repository.Association;
+import net.luminis.liq.client.repository.RepositoryObject;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.event.Event;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * A basic implementation of the Association interface. Implements 'common' behavior for all associations.
+ *
+ * The association allows up to m-to-n associations. Each end of the association gets
+ * a filter to match objects, possibly a cardinality (if no cardinality is specified,
+ * 1 is assumed), and, if there can be more matches to the filter than the given cardinality,
+ * a comparator should be provided in the constructor.
+ *
+ * @param <L> The type of the RepositoryObject on the left side of this association
+ * @param <R> The type of the RepositoryObject on the right side of this association
+ * @param <T> The non-generic Association interface this object should use.
+ */
+public class AssociationImpl<L extends RepositoryObject, R extends RepositoryObject, T extends Association<L, R>> extends RepositoryObjectImpl<T> implements Association<L, R> {
+
+    /* These lists are volatile, since we use copy-on-write semantics for
+     * updating them.
+     */
+    private volatile List<L> m_left = new ArrayList<L>();
+    private volatile List<R> m_right = new ArrayList<R>();
+    private final Object m_lock = new Object();
+
+    private final Filter m_filterLeft;
+    private final Filter m_filterRight;
+
+    private final ObjectRepositoryImpl<?, L> m_leftRepository;
+    private final ObjectRepositoryImpl<?, R> m_rightRepository;
+    private final Class<L> m_leftClass;
+    private final Class<R> m_rightClass;
+
+    /**
+     * Constructor intended for deserialization. For most parameters, see below.
+     * @param reader a stream reader which contains an XML representation of this object's contents.
+     */
+    public AssociationImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, Class<L> leftClass, Class<R> rightClass, Comparator<L> leftComparator, Comparator<R> rightComparator, ObjectRepositoryImpl<?, L> leftRepository, ObjectRepositoryImpl<?, R> rightRepository, String xmlNode) throws InvalidSyntaxException {
+        this(readMap(reader), notifier, leftClass, rightClass, leftRepository, rightRepository, xmlNode);
+    }
+
+    /**
+     * Basic constructor for AssociationImpl.
+     * @param attributes A map of attributes. This should at least contain <code>Association.LEFT_ENDPOINT</code> and <code>Association.RIGHT_ENDPOINT</code>,
+     * and optionally <code>Association.LEFT_CARDINALITY</code> and <code>Association.RIGHT_CARDINALITY</code>.
+     * @param notifier An instance of the event admin
+     * @param leftClass The class on the left side of this association.
+     * @param rightClass The class on the right side of this association.
+     * @param leftRepository The repository which holds object of <code>leftClass</code>.
+     * @param rightRepository The repository which holds object of <code>rightClass</code>.
+     * @param xmlNode The tag by which this object is known in the XML representation.
+     * @throws InvalidSyntaxException Thrown when the attributes contain an invalidly constructed filter string.
+     */
+    public AssociationImpl(Map<String, String> attributes, ChangeNotifier notifier, Class<L> leftClass, Class<R> rightClass, ObjectRepositoryImpl<?, L> leftRepository, ObjectRepositoryImpl<?, R> rightRepository, String xmlNode) throws InvalidSyntaxException {
+        super(attributes, notifier, xmlNode);
+
+        if ((getAttribute(LEFT_CARDINALITY) != null) && (Integer.parseInt(getAttribute(LEFT_CARDINALITY)) < 1)) {
+            throw new IllegalArgumentException("The left cardinality should be 1 or greater.");
+        }
+        if ((getAttribute(RIGHT_CARDINALITY) != null) && (Integer.parseInt(getAttribute(RIGHT_CARDINALITY)) < 1)) {
+            throw new IllegalArgumentException("The right cardinality should be 1 or greater.");
+        }
+
+        m_leftClass = leftClass;
+        m_rightClass = rightClass;
+        m_leftRepository = leftRepository;
+        m_rightRepository = rightRepository;
+
+        m_filterLeft = m_leftRepository.createFilter(getAttribute(Association.LEFT_ENDPOINT));
+        m_filterRight = m_rightRepository.createFilter(getAttribute(Association.RIGHT_ENDPOINT));
+
+        locateLeftEndpoint(false);
+        locateRightEndpoint(false);
+    }
+
+    public AssociationImpl(Map<String, String> attributes, Map<String, String> tags, ChangeNotifier notifier, Class<L> leftClass, Class<R> rightClass, ObjectRepositoryImpl<?, L> leftRepository, ObjectRepositoryImpl<?, R> rightRepository, String xmlNode) throws InvalidSyntaxException {
+        super(attributes, tags, notifier, xmlNode);
+
+        if ((getAttribute(LEFT_CARDINALITY) != null) && (Integer.parseInt(getAttribute(LEFT_CARDINALITY)) < 1)) {
+            throw new IllegalArgumentException("The left cardinality should be 1 or greater.");
+        }
+        if ((getAttribute(RIGHT_CARDINALITY) != null) && (Integer.parseInt(getAttribute(RIGHT_CARDINALITY)) < 1)) {
+            throw new IllegalArgumentException("The right cardinality should be 1 or greater.");
+        }
+
+        m_leftClass = leftClass;
+        m_rightClass = rightClass;
+        m_leftRepository = leftRepository;
+        m_rightRepository = rightRepository;
+
+        m_filterLeft = m_leftRepository.createFilter(getAttribute(Association.LEFT_ENDPOINT));
+        m_filterRight = m_rightRepository.createFilter(getAttribute(Association.RIGHT_ENDPOINT));
+
+        locateLeftEndpoint(false);
+        locateRightEndpoint(false);
+    }
+
+    public List<Associatable> getTo(Associatable from) {
+        if (m_left.contains(from)) {
+            return new ArrayList<Associatable>(m_right);
+        }
+        if (m_right.contains(from)) {
+            return new ArrayList<Associatable>(m_left);
+        }
+        return null;
+    }
+
+    public List<L> getLeft() {
+        return new ArrayList<L>(m_left);
+    }
+
+    public List<R> getRight() {
+        return new ArrayList<R>(m_right);
+    }
+
+    public void remove() {
+        for (L l : m_left) {
+            l.remove(this, m_rightClass);
+        }
+        for (R r : m_right) {
+            r.remove(this, m_leftClass);
+        }
+    }
+
+    /**
+     * Locates the most suited endpoint of one side of this association. If the corresponding filter
+     * matches multiple objects, the <code>comparator</code> will be used to find the most suited one.
+     * The association will register itself with a new endpoint, and remove itself from the old one.
+     * @param <TYPE> (only used for type matching).
+     * @param objectRepositoryImpl The repository where the endpoint should come from.
+     * @param filter A filter string, used to get candidate-endpoints from <code>objectRepositoryImpl</code>.
+     * @param endpoint The current endpoint.
+     * @param comparator A comparator, used when there are multiple potential endpoints.
+     * @param clazz The class of the 'other side' of this association.
+     * @return The most suited endpoint; this could be equal to <code>endpoint</code>.
+     */
+    @SuppressWarnings("unchecked")
+    private <TYPE extends RepositoryObject> List<TYPE> locateEndpoint(ObjectRepositoryImpl<?, TYPE> objectRepositoryImpl, Filter filter, List<TYPE> endpoints, int cardinality, Class<? extends RepositoryObject> clazz, boolean notify) {
+        List<TYPE> candidates = objectRepositoryImpl.get(filter);
+        List<TYPE> newEndpoints = new ArrayList<TYPE>();
+        List<TYPE> oldEndpoints = new ArrayList<TYPE>(endpoints);
+
+        if (candidates.size() > cardinality) {
+            Comparator<TYPE> comparator = candidates.get(0).getComparator();
+            if (comparator != null) {
+                Collections.sort(candidates, comparator);
+            }
+            else {
+                throw new NullPointerException("Filter '" + filter.toString() + "' has resulted in multiple candidates, so the RepositoryObject descendents should have provide a comparator, which they do not.");
+            }
+        }
+
+        for (int i = 0; (i < cardinality) && !candidates.isEmpty(); i++) {
+            TYPE current = candidates.remove(0);
+            newEndpoints.add(current);
+
+            if (!oldEndpoints.remove(current)) {
+                current.add(this, clazz);
+            }
+        }
+
+        for (TYPE e : oldEndpoints) {
+            e.remove(this, clazz);
+        }
+
+        if (!endpoints.equals(newEndpoints)) {
+            Properties props = new Properties();
+            props.put(EVENT_OLD, new ArrayList<TYPE>(endpoints));
+            props.put(EVENT_NEW, new ArrayList<TYPE>(newEndpoints));
+            if (notify) {
+                notifyChanged(props);
+            }
+        }
+
+        return newEndpoints;
+    }
+
+    /**
+     * Locates the left endpoint by using the generic locateEndpoint and notifies
+     * listeners of changes, if any.
+     * @param notify Indicates whether notifications should be sent out.
+     */
+    private void locateLeftEndpoint(boolean notify) {
+        synchronized (m_lock) {
+            m_left = locateEndpoint(m_leftRepository, m_filterLeft, m_left, (getAttribute(LEFT_CARDINALITY) == null ? 1 : Integer.parseInt(getAttribute(LEFT_CARDINALITY))), m_rightClass, notify);
+        }
+    }
+
+    /**
+     * Locates the right endpoint by using the generic locateEndpoint and notifies
+     * listeners of changes, if any.
+     * @param notify Indicates whether notifications should be sent out.
+     */
+    private void locateRightEndpoint(boolean notify) {
+        synchronized (m_lock) {
+            m_right = locateEndpoint(m_rightRepository, m_filterRight, m_right, (getAttribute(RIGHT_CARDINALITY) == null ? 1 : Integer.parseInt(getAttribute(RIGHT_CARDINALITY))), m_leftClass, notify);
+        }
+    }
+
+    public boolean isSatisfied() {
+        return (!m_left.isEmpty()) && (!m_right.isEmpty());
+    }
+
+    @Override
+    public void handleEvent(Event event) {
+        // We get a topic which ends in '/*', but the event contains a specialized topic.
+        // for now, we chop of the star, and check whether the topic starts with that.
+        RepositoryObject entity = (RepositoryObject) event.getProperty(RepositoryObject.EVENT_ENTITY);
+        if ((event.getTopic().endsWith("ADDED")) || event.getTopic().endsWith("REMOVED")) {
+            if (m_leftClass.isInstance(entity) && m_filterLeft.match(entity.getDictionary())) {
+                locateLeftEndpoint(true);
+            }
+            if (m_rightClass.isInstance(entity) && m_filterRight.match(entity.getDictionary())) {
+                locateRightEndpoint(true);
+            }
+        }
+    }
+
+}