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 [11/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/repository/impl/RepositoryObjectImpl.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositoryObjectImpl.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositoryObjectImpl.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositoryObjectImpl.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,608 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import net.luminis.liq.client.repository.Associatable;
+import net.luminis.liq.client.repository.Association;
+import net.luminis.liq.client.repository.RepositoryObject;
+import net.luminis.liq.client.repository.RepositoryUtil;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Represents a value-object as is part of the repository.<br>
+ * It stores the 'member' values of the repository object, and allows putting tags on this object by using <code>put()</code>
+ * and <code>remove()</code>. It 'looks' like a dictionary to allow filtering of it, using an ldap filter.
+ */
+public class RepositoryObjectImpl<T extends RepositoryObject> extends Dictionary<String, Object> implements RepositoryObject, EventHandler {
+    private final Map<String, String> m_attributes = new HashMap<String, String>();
+    private final Map<String, String> m_tags = new HashMap<String, String>();
+    @SuppressWarnings("unchecked")
+    private final Map<Class, List<Association>> m_associations = new HashMap<Class, List<Association>>();
+
+    private final ChangeNotifier m_notifier;
+
+    private final String m_xmlNode;
+    private volatile boolean m_deleted = false;
+    private volatile boolean m_busy = false;
+
+    public RepositoryObjectImpl(ChangeNotifier notifier, String xmlNode) {
+        this((Map<String, String>) null, null, notifier, xmlNode);
+    }
+
+    public RepositoryObjectImpl(Map<String, String> attributes, ChangeNotifier notifier, String xmlNode) {
+        this(attributes, null, notifier, xmlNode);
+    }
+
+    public RepositoryObjectImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, String xmlNode) {
+        this(readMap(reader), readMap(reader), notifier, xmlNode);
+        readCustom(reader);
+    }
+
+    public RepositoryObjectImpl(Map<String, String> attributes, Map<String, String> tags, ChangeNotifier notifier, String xmlNode) {
+        m_xmlNode = xmlNode;
+        if (attributes != null) {
+            m_attributes.putAll(attributes);
+        }
+        if (tags != null) {
+            m_tags.putAll(tags);
+        }
+        if (notifier == null) {
+            throw new IllegalArgumentException();
+        }
+        m_notifier = notifier;
+    }
+
+    protected void notifyChanged(Properties props) {
+        if (props == null) {
+            props = new Properties();
+        }
+        props.put(EVENT_ENTITY, this);
+        m_notifier.notifyChanged(TOPIC_CHANGED_SUFFIX, props, m_busy);
+    }
+
+    /**
+     * Returns an enumeration of the values in this object's dictionary.
+     */
+
+    @Override
+    public Enumeration<Object> elements() {
+        synchronized (m_attributes) {
+            return new ValuesEnumeration();
+        }
+    }
+
+    /**
+     * Gets the object associated with this key. Will return null when the key is not used; if the key is available for both the
+     * tags and the object's basic information, an array of two Strings will be returned.
+     */
+
+    @Override
+    public Object get(Object key) {
+        synchronized (m_attributes) {
+            String manifest = m_attributes.get(key);
+            String tag = m_tags.get(key);
+
+            if (manifest == null) {
+                return tag;
+            }
+            else if (tag == null) {
+                return manifest;
+            }
+            else {
+                return new String[] { tag, manifest };
+            }
+        }
+    }
+
+    /**
+     * Return whether the dictionary is empty.
+     */
+
+    @Override
+    public boolean isEmpty() {
+        synchronized (m_attributes) {
+            return m_attributes.isEmpty() && m_tags.isEmpty();
+        }
+    }
+
+    /**
+     * Returns an enumeration of the keys in this object.
+     */
+
+    @Override
+    public Enumeration<String> keys() {
+        synchronized (m_attributes) {
+            Set<String> keys = new HashSet<String>();
+            keys.addAll(m_attributes.keySet());
+            keys.addAll(m_tags.keySet());
+            return new KeysEnumeration(keys.iterator());
+        }
+    }
+
+    /**
+     * @throws UnsupportedOperationException
+     */
+
+    @Override
+    public Object put(@SuppressWarnings("unused")
+    String key, @SuppressWarnings("unused")
+    Object value) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @throws UnsupportedOperationException
+     */
+
+    @Override
+    public Object remove(@SuppressWarnings("unused")
+    Object key) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns the number of keys in both this object's tags, and its 'member' keys.
+     */
+
+    @Override
+    public int size() {
+        synchronized (m_attributes) {
+            Set<String> keys = new HashSet<String>();
+            keys.addAll(m_attributes.keySet());
+            keys.addAll(m_tags.keySet());
+            return keys.size();
+        }
+    }
+
+    /**
+     * Helper class that implements an enumeration for use in <code>keys()</code>.
+     */
+    private class KeysEnumeration implements Enumeration<String> {
+        private final Iterator<String> m_iter;
+
+        public KeysEnumeration(Iterator<String> iter) {
+            m_iter = iter;
+        }
+
+
+        public boolean hasMoreElements() {
+            return m_iter.hasNext();
+        }
+
+
+        public String nextElement() {
+            return m_iter.next();
+        }
+
+    }
+
+    /**
+     * Helper class that implements an enumeration for use in <code>elements()</code>.
+     */
+    private class ValuesEnumeration implements Enumeration<Object> {
+        private final Enumeration<String> m_iter = keys();
+
+
+        public boolean hasMoreElements() {
+            return m_iter.hasMoreElements();
+        }
+
+
+        public Object nextElement() {
+            return get(m_iter.nextElement());
+        }
+
+    }
+
+
+    public String addAttribute(String key, String value) {
+        for (String s : getDefiningKeys()) {
+            if (s.equals(key)) {
+                throw new UnsupportedOperationException("The defining attribute " + key + " is not allowed to be changed.");
+            }
+        }
+        synchronized (m_attributes) {
+            ensureCurrent();
+            notifyChanged(null);
+            return m_attributes.put(key, value);
+        }
+    }
+
+
+    public String addTag(String key, String value) {
+        synchronized (m_attributes) {
+            ensureCurrent();
+            notifyChanged(null);
+            return m_tags.put(key, value);
+        }
+    }
+
+
+    public String getAttribute(String key) {
+        synchronized (m_attributes) {
+            return m_attributes.get(key);
+        }
+    }
+
+
+    public String getTag(String key) {
+        synchronized (m_attributes) {
+            return m_tags.get(key);
+        }
+    }
+
+
+    public Enumeration<String> getAttributeKeys() {
+        synchronized (m_attributes) {
+            return new KeysEnumeration(new HashSet<String>(m_attributes.keySet()).iterator());
+        }
+    }
+
+
+    public Dictionary<String, Object> getDictionary() {
+        return this;
+    }
+
+
+    public Enumeration<String> getTagKeys() {
+        synchronized (m_attributes) {
+            return new KeysEnumeration(new HashSet<String>(m_tags.keySet()).iterator());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void add(Association association, Class clazz) {
+        synchronized (m_associations) {
+            List<Association> associations = m_associations.get(clazz);
+            if (associations == null) {
+                associations = new ArrayList<Association>();
+                m_associations.put(clazz, associations);
+            }
+            associations.add(association);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void remove(Association association, Class clazz) {
+        synchronized (m_associations) {
+            List<Association> associations = m_associations.get(clazz);
+            if (associations != null) {
+                associations.remove(association);
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public <A extends Associatable> List<A> getAssociations(Class<A> clazz) {
+        synchronized (m_associations) {
+            List<A> result = new ArrayList<A>();
+            List<Association> associations = m_associations.get(clazz);
+            if (associations != null) {
+                for (Association association : associations) {
+                    List<A> otherSide = association.getTo(this);
+                    if (otherSide != null) {
+                        // If the other side is null, the association
+                        // is not satisfied.
+                        result.addAll(otherSide);
+                    }
+                }
+            }
+            return result;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public boolean isAssociated(Object obj, Class clazz) {
+        synchronized (m_associations) {
+            if (obj == null) {
+                return false;
+            }
+            List<Association> associations = m_associations.get(clazz);
+            if (associations != null) {
+                for (Association association : associations) {
+                    if (association.getTo(this).contains(obj)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public <TA extends Associatable, A extends Association> List<A> getAssociationsWith(Associatable other, Class<TA> clazz, Class<A> associationType) {
+        List<A> result = new ArrayList<A>();
+        synchronized (m_associations) {
+            if (other == null) {
+                return result;
+            }
+            List<Association> associations = m_associations.get(clazz);
+            if (associations != null) {
+                for (Association association : associations) {
+                    if (association.getTo(this).contains(other)) {
+                        result.add((A) association);
+                    }
+                }
+            }
+            return result;
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public boolean equals(Object o) {
+        synchronized(m_attributes) {
+            if ((o == null) || !(getClass().isInstance(o))) {
+                return false;
+            }
+            if (m_attributes.size() == 0) {
+                return this == o;
+            }
+            for (String s : getDefiningKeys()) {
+                String ourAttribute = m_attributes.get(s);
+                String otherAttribute = (String) ((RepositoryObjectImpl) o).m_attributes.get(s);
+                if ((ourAttribute == null) && (otherAttribute != null)) {
+                    return false;
+                }
+                else if ((ourAttribute != null) && (otherAttribute == null)) {
+                    return false;
+                }
+                else if ((ourAttribute == null) && (otherAttribute == null)) {
+                    continue;
+                }
+                else if (!otherAttribute.equals(ourAttribute)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Returns an array of keys which are considered to be defining for this object's
+     * attributes. The basic implementation just uses every key that is used; this
+     * function is intended to be overridden by deriving classes, providing a real
+     * set.
+     *
+     * Note that the array returned from this function should be read from only;
+     * writing to it will change the state of the object.
+     */
+    String[] getDefiningKeys() {
+        return m_attributes.keySet().toArray(new String[m_attributes.size()]);
+    }
+
+    @Override
+    public int hashCode() {
+        synchronized(m_attributes) {
+            return m_attributes.hashCode();
+        }
+    }
+
+    void marshal(HierarchicalStreamWriter writer) {
+        synchronized (m_attributes) {
+            writer.startNode(m_xmlNode);
+            writeMap(writer, m_attributes, "attributes");
+            writeMap(writer, m_tags, "tags");
+            writeCustom(writer);
+            writer.endNode();
+        }
+    }
+
+    /**
+     * This method is intended to be overridden by deriving classes, to read custom information
+     * from the XML representation of this object. This method should end with the writer at
+     * the same 'level' as before, that is, using equally many moveDown() and moveUp() calls.
+     * @param reader A reader to read from the XML stream.
+     */
+    protected void readCustom(HierarchicalStreamReader reader) {
+        // do nothing
+    }
+
+    /**
+     * This method is intended to be overridden by deriving classes, to write custom information
+     * to the XML representation of this object. This method should end with the writer at
+     * the same 'level' as before, that is, using equally many moveDown() and moveUp() calls.
+     * @param writer A writer to write to the XML stream.
+     */
+    protected void writeCustom(HierarchicalStreamWriter writer) {
+        // do nothing
+    }
+
+    static void writeMap(HierarchicalStreamWriter writer, Map<String, String> entries, String name) {
+        writer.startNode(name);
+        for (Map.Entry<String, String> entry : entries.entrySet()) {
+            writer.startNode(entry.getKey());
+            assert (entry.getValue() != null);
+            writer.setValue(entry.getValue());
+            writer.endNode();
+        }
+        writer.endNode();
+    }
+
+    static Map<String, String> readMap(HierarchicalStreamReader reader) {
+        reader.moveDown();
+        Map<String, String> result = new HashMap<String, String>();
+        while (reader.hasMoreChildren()) {
+            reader.moveDown();
+            result.put(reader.getNodeName(), reader.getValue());
+            reader.moveUp();
+        }
+        reader.moveUp();
+        return result;
+    }
+
+    /**
+     * Helper function to check the existence of keys in a map. Each attribute is required
+     * to be non-empty.
+     * @param attributes A map of attribute-value combinations.
+     * @param mandatory An array of attributes which have to be present in the map.
+     * @return <code>attributes</code> if this map meets the requirements. If not, <code>IllegalArgumentException</code>
+     * will be thrown.
+     */
+    static Map<String, String> checkAttributes(Map<String, String> attributes, String... mandatory) {
+        boolean[] booleans = new boolean[mandatory.length];
+        Arrays.fill(booleans, false);
+        return checkAttributes(attributes, mandatory, booleans);
+    }
+
+    /**
+     * Helper function to check the existence of keys in a map.
+     * @param attributes A map of attribute-value combinations.
+     * @param mandatory An array of attributes which have to be present in the map.
+     * @param emptyAttributeAllowed An array of booleans, indicating which of the attributes is allowed
+     * to be equal. Items in this array are matched by index on the elements in <code>mandatory</code>, so
+     * the length should be equal.
+     * @return <code>attributes</code> if this map meets the requirements. If not, <code>IllegalArgumentException</code>
+     * will be thrown.
+     */
+    static Map<String, String> checkAttributes(Map<String, String> attributes, String[] mandatory, boolean[] emptyAttributeAllowed) {
+        if (!(mandatory.length == emptyAttributeAllowed.length)) {
+            throw new IllegalArgumentException("The length of the mandatory- and the emptyAttributeAllow-array should be equal.");
+        }
+        for (int i = 0; i < mandatory.length; i++) {
+            String attribute = mandatory[i];
+            boolean emptyAllowed = emptyAttributeAllowed[i];
+            if (!attributes.containsKey(attribute)) {
+                throw new IllegalArgumentException(attribute + " is a mandatory attribute.");
+            }
+            else if ((!emptyAllowed) && (attributes.get(attribute).length() == 0)) {
+                throw new IllegalArgumentException(attribute + " is not allowed to be empty.");
+            }
+        }
+        return attributes;
+    }
+
+    public String getXmlNode() {
+        return m_xmlNode;
+    }
+
+    public void handleEvent(Event e) {
+    }
+
+    void setDeleted() {
+        m_deleted = true;
+    }
+
+    public boolean isDeleted() {
+        return m_deleted;
+    }
+
+    void ensureNotDeleted() {
+        if (isDeleted()) {
+            throw new IllegalStateException("This object is deleted, and should no longer be used.");
+        }
+    }
+
+    void setBusy(boolean busy) {
+        // setBusy should 'wait' until all altering operations have passed. To do so,
+        // it gets the locks for the other 'set' objects. Once it has all these locks,
+        // we are sure no thread is performing a set-action.
+        synchronized(m_attributes) {
+            synchronized(m_associations) {
+                if (m_busy && !busy) {
+                    m_associations.notifyAll();
+                    m_attributes.notifyAll();
+                }
+                m_busy = busy;
+            }
+        }
+    }
+
+    public boolean getBusy() {
+        return m_busy;
+    }
+
+    // NEVER CALL WITHOUT m_attributes lock
+    private void ensureNotBusy() {
+        boolean interrupted = false;
+        while (m_busy) {
+            try {
+                m_attributes.wait();
+            }
+            catch (InterruptedException e) {
+                interrupted = true;
+            }
+        }
+        if (interrupted) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    void ensureCurrent() {
+        ensureNotBusy();
+        ensureNotDeleted();
+    }
+
+    public String getDefinition() {
+        StringBuilder result = new StringBuilder();
+
+        result.append(getXmlNode().replaceAll("\\\\", "\\\\\\\\").replaceAll("-", "\\-"));
+
+        for (String key : getDefiningKeys()) {
+            String value = getAttribute(key);
+            if (value != null) {
+                result.append("-")
+                .append(key.replaceAll("\\\\", "\\\\\\\\").replaceAll("-", "\\-").replaceAll("\\/", "&#47"))
+                .append("-")
+                .append(value.replaceAll("\\\\", "\\\\\\\\").replaceAll("-", "\\-").replaceAll("\\/", "&#47"));
+                // About the &#47: the forward slash will be used by the preference admin, but we don't want that.
+            }
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Creates a filter string for use in associations, optionally with some
+     * additional properties. The basic implementation will use all <code>getDefiningKeys</code>.
+     * @param properties Properties indicating specifics of the filter to be created.
+     * @return A string representation of a filter, for use in <code>Association</code>s.
+     */
+    public String getAssociationFilter(Map<String, String> properties) {
+        StringBuilder filter = new StringBuilder("(&");
+
+        for (String key : getDefiningKeys()) {
+            filter.append("(" + key + "=" + RepositoryUtil.escapeFilterValue(getAttribute(key)) + ")");
+        }
+
+        filter.append(")");
+
+        return filter.toString();
+    }
+
+    /**
+     * Determines the cardinality of this endpoint of an association, given
+     * the passed properties.
+     * @param properties Properties indicating specifics of this endpoint.
+     * @return The necessary cardinality.
+     */
+    public int getCardinality(Map<String, String> properties) {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Returns a <code>Comparator</code> for this type of object, suitable
+     * for the endpoint properties that are passed.
+     * @return A <code>Comparator</code> for this type of object
+     */
+    public Comparator<T> getComparator() {
+        return null;
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySerializer.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySerializer.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySerializer.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySerializer.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,110 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Helper class that takes a RepositorySet<br>
+ * TODO We might move out xstream at some time in the future; before that
+ * time, it could be a smart idea to wrap xstream's writer in a delegate
+ * object, so this will not require changes to the repositories
+ * and objects.
+ */
+class RepositorySerializer implements Converter {
+    @SuppressWarnings("unchecked")
+    private final Map<String, ObjectRepositoryImpl> m_tagToRepo = new HashMap<String, ObjectRepositoryImpl>();
+
+    private final RepositorySet m_set;
+
+    private final XStream m_stream;
+
+    @SuppressWarnings("unchecked")
+    RepositorySerializer(RepositorySet set) {
+        m_set = set;
+        for (ObjectRepositoryImpl repo : m_set.getRepos()) {
+            m_tagToRepo.put(repo.getXmlNode(), repo);
+        }
+        m_stream = new XStream();
+        m_stream.alias("repository", getClass());
+        m_stream.registerConverter(this);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void marshal(Object target, HierarchicalStreamWriter writer, MarshallingContext context) {
+        for (ObjectRepositoryImpl repo : m_set.getRepos()) {
+            repo.marshal(writer);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+        while(reader.hasMoreChildren()) {
+            reader.moveDown();
+            String nodeName = reader.getNodeName();
+            ObjectRepositoryImpl o = m_tagToRepo.get(nodeName);
+            o.unmarshal(reader);
+            reader.moveUp();
+        }
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public boolean canConvert(Class target) {
+        return target == getClass();
+    }
+
+    @SuppressWarnings("unchecked")
+    public void toXML(OutputStream out) {
+        for (ObjectRepositoryImpl repo : m_set.getRepos()) {
+            repo.setBusy(true);
+        }
+        m_stream.toXML(this, out);
+        for (ObjectRepositoryImpl repo : m_set.getRepos()) {
+            repo.setBusy(false);
+        }
+    }
+
+    /**
+     * Reads the repositories with which this RepositoryRoot had been initialized with from the
+     * given XML file.
+     * @param in The input stream.
+     */
+    @SuppressWarnings("unchecked")
+    public void fromXML(InputStream in) {
+        // The repositories get cleared, since a user *could* add stuff before
+        // checking out.
+        for (ObjectRepositoryImpl repo : m_set.getRepos()) {
+            repo.setBusy(true);
+            repo.removeAll();
+        }
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+            if (in.available() > 0) {
+                m_stream.fromXML(in, this);
+            }
+        }
+        catch (IOException e) {
+            // This means the stream has been closed before we got it.
+            // Since the repository is now in a consistent state, just move on.
+            e.printStackTrace();
+        }
+        finally {
+            Thread.currentThread().setContextClassLoader(cl);
+        }
+        for (ObjectRepositoryImpl repo : m_set.getRepos()) {
+            repo.setBusy(false);
+        }
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySet.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySet.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySet.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/impl/RepositorySet.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,325 @@
+package net.luminis.liq.client.repository.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+
+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.RepositoryObject.WorkingState;
+import net.luminis.liq.repository.ext.CachedRepository;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.log.LogService;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.osgi.service.useradmin.User;
+
+/**
+ * This class encapsulates a set of <code>ObjectRepositoryImpl</code>s, and manages
+ * auxiliary information and functionality that is linked to that set.
+ */
+class RepositorySet {
+    private final static String PREFS_LOCAL_WORKING_STATE = "workingState";
+    private final static String PREFS_LOCAL_WORKING_STATE_VALUE = "workingStateValue";
+    private final static String PREFS_LOCAL_FILE_VERSION = "version";
+
+    private final User m_user;
+    private final Preferences m_prefs;
+    @SuppressWarnings("unchecked")
+    private final ObjectRepositoryImpl[] m_repos;
+    private final CachedRepository m_repository;
+    private final String m_name;
+    private final boolean m_writeAccess;
+
+    private final Map<RepositoryObject, WorkingState> m_workingState = new HashMap<RepositoryObject, WorkingState>();
+    private ServiceRegistration m_modifiedHandler;
+    private final ChangeNotifier m_notifier;
+    private final LogService m_log;
+
+    /* ********
+     * Basics
+     * ********/
+
+    @SuppressWarnings("unchecked")
+    /**
+     * Creates a new <code>RepositorySet</code>. Notes:
+     * <ul>
+     * <li>When storing association repositories in <code>repos</code>, it is wise to
+     * put these in last. This has to do with the method of deserialization, which assumes
+     * that endpoints of an association are available at the time of deserialization.</li>
+     * </ul>
+     */
+    RepositorySet(ChangeNotifier notifier, LogService log, User user, Preferences prefs, ObjectRepositoryImpl[] repos, CachedRepository repository, String name, boolean writeAccess) {
+        m_notifier = notifier;
+        m_log = log;
+        m_user = user;
+        m_prefs = prefs;
+        m_repos = repos;
+        m_repository = repository;
+        m_name = name;
+        m_writeAccess = writeAccess;
+    }
+
+    boolean isModified() {
+        boolean modified = false;
+        for (Map.Entry<RepositoryObject, WorkingState> entry : m_workingState.entrySet()) {
+            modified |= !(entry.getValue().equals(WorkingState.Unchanged));
+        }
+        return modified;
+    }
+
+    User getUser() {
+        return m_user;
+    }
+
+    @SuppressWarnings("unchecked")
+    ObjectRepositoryImpl[] getRepos() {
+        return m_repos;
+    }
+
+    String getName() {
+        return m_name;
+    }
+
+    /* ********
+     * Preferences
+     * ********/
+
+    void savePreferences() {
+        Preferences workingNode = m_prefs.node(PREFS_LOCAL_WORKING_STATE);
+        try {
+            workingNode.clear();
+        }
+        catch (BackingStoreException e) {
+            // Something went wrong clearing the node... Too bad, this means we
+            // cannot store the properties.
+            m_log.log(LogService.LOG_WARNING, "Could not store all preferences for " + workingNode.absolutePath());
+            e.printStackTrace();
+        }
+        for (Map.Entry<RepositoryObject, WorkingState> entry : m_workingState.entrySet()) {
+            workingNode.node(entry.getKey().getDefinition()).put(PREFS_LOCAL_WORKING_STATE_VALUE, entry.getValue().toString());
+        }
+
+        m_prefs.putLong(PREFS_LOCAL_FILE_VERSION, m_repository.getMostRecentVersion());
+    }
+
+    /**
+     * Only call this after the repository has been deserialized.
+     */
+    void loadPreferences() {
+        Preferences workingNode = m_prefs.node(PREFS_LOCAL_WORKING_STATE);
+        Map<String, WorkingState> entries = new HashMap<String, WorkingState>();
+        // First, get all nodes and their workingstate.
+        try {
+            for (String node : workingNode.childrenNames()) {
+                String state = workingNode.node(node).get(PREFS_LOCAL_WORKING_STATE_VALUE, WorkingState.Unchanged.toString());
+                entries.put(node, WorkingState.valueOf(state));
+            }
+        }
+        catch (BackingStoreException e) {
+            // Something went wrong reading from the store, just work with whatever we have in the map.
+            e.printStackTrace();
+        }
+        // Then, go through all objects and check whether they match a definition we know.
+        // This prevents calling getDefinition more than once per object.
+        for (ObjectRepository<RepositoryObject> repo : m_repos) {
+            for (RepositoryObject o : repo.get()) {
+                WorkingState state = entries.get(o.getDefinition());
+                if (state != null) {
+                    m_workingState.put(o, state);
+                }
+            }
+        }
+    }
+
+    /* ********
+     * Persistence
+     * ********/
+
+    boolean readLocal() throws IOException {
+        InputStream input = m_repository.getLocal(false);
+        if (input.available() > 0) {
+            read(input);
+            return true;
+        }
+        else {
+            try {
+                input.close();
+            }
+            catch (IOException e) {
+                // This does not matter now.
+            }
+            return false;
+        }
+    }
+
+    void read(InputStream input) {
+        new RepositorySerializer(this).fromXML(input);
+        try {
+            input.close();
+        }
+        catch (IOException e) {
+            // Not much we can do...
+            e.printStackTrace();
+        }
+    }
+
+    void writeLocal() throws IOException {
+        PipedInputStream input = new PipedInputStream();
+        final PipedOutputStream output = new PipedOutputStream(input);
+        new Thread(new Runnable() {
+            public void run() {
+                new RepositorySerializer(RepositorySet.this).toXML(output);
+                try {
+                    output.flush();
+                    output.close();
+                }
+                catch (IOException e) {
+                    // There is no way to tell this to the user, but the other side will
+                    // notice that the pipe is broken.
+                }
+            }
+        }, "write(" + m_name + ")").start();
+        m_repository.writeLocal(input);
+        input.close();
+    }
+
+    void commit() throws IOException {
+        if (!isCurrent()) {
+            throw new IllegalStateException("When committing the " + m_name + ", it should be current.");
+        }
+        writeLocal();
+        if (m_writeAccess) {
+            m_repository.commit();
+        }
+        resetModified(false);
+    }
+
+    void checkout() throws IOException {
+        read(m_repository.checkout(false));
+        resetModified(true);
+    }
+
+    void revert() throws IOException {
+        m_repository.revert();
+        read(m_repository.getLocal(false));
+        resetModified(false);
+    }
+
+    boolean isCurrent() throws IOException {
+        return m_repository.isCurrent();
+    }
+
+    @SuppressWarnings("unchecked")
+    void clearRepositories() {
+        for (ObjectRepositoryImpl repo : getRepos()) {
+            repo.setBusy(true);
+        }
+        for (ObjectRepositoryImpl repo : getRepos()) {
+            repo.removeAll();
+        }
+        for (ObjectRepositoryImpl repo : getRepos()) {
+            repo.setBusy(false);
+        }
+    }
+
+    /* ********
+     * Event handling
+     * ********/
+
+    void registerHandler(BundleContext context, String... topics) {
+        if (m_modifiedHandler != null) {
+            throw new IllegalStateException("A handler is already registered; only one can be used at a time.");
+        }
+        Dictionary<String, String[]> topic = new Hashtable<String, String[]>();
+        topic.put(EventConstants.EVENT_TOPIC, topics);
+        m_modifiedHandler = context.registerService(EventHandler.class.getName(), new ModifiedHandler(), topic);
+    }
+
+    public void unregisterHandler() {
+        m_modifiedHandler.unregister();
+        m_modifiedHandler = null;
+    }
+
+    WorkingState getWorkingState(RepositoryObject object) {
+        if (m_workingState.containsKey(object)) {
+            return m_workingState.get(object);
+        }
+        return null;
+    }
+
+    int getNumberWithWorkingState(Class<? extends RepositoryObject> clazz, WorkingState state) {
+        int result = 0;
+        for (Map.Entry<RepositoryObject, WorkingState> entry : m_workingState.entrySet()) {
+            if (clazz.isInstance(entry.getKey()) && state.equals(entry.getValue())) {
+                result ++;
+            }
+        }
+        return result;
+    }
+
+    private void resetModified(boolean fill) {
+        m_workingState.clear();
+        if (fill) {
+            for (ObjectRepository<? extends RepositoryObject> repo : m_repos) {
+                for (RepositoryObject object : repo.get()) {
+                    m_workingState.put(object, WorkingState.Unchanged);
+                }
+            }
+        }
+        m_notifier.notifyChanged(RepositoryAdmin.TOPIC_STATUSCHANGED_SUFFIX, null);
+    }
+
+    class ModifiedHandler implements EventHandler {
+
+        public void handleEvent(Event event) {
+            /*
+             * NOTE: if recalculating the state for every event turns out to be
+             * too expensive, we can cache the 'modified' state and not recalculate
+             * it every time.
+             */
+
+            boolean wasModified = isModified();
+            RepositoryObject object = (RepositoryObject) event.getProperty(RepositoryObject.EVENT_ENTITY);
+
+            WorkingState newState = WorkingState.Unchanged;
+            if (event.getTopic().endsWith("/ADDED")) {
+                newState = WorkingState.New;
+            }
+            else if (event.getTopic().endsWith("/CHANGED")) {
+                newState = WorkingState.Changed;
+            }
+            else if (event.getTopic().endsWith("/REMOVED")) {
+                newState = WorkingState.Removed;
+            }
+
+            if (!newState.equals(m_workingState.get(object))) {
+                m_workingState.put(object, newState);
+                Properties props = new Properties();
+                props.put(RepositoryObject.EVENT_ENTITY, object);
+                m_notifier.notifyChanged(RepositoryAdmin.TOPIC_STATUSCHANGED_SUFFIX, props);
+            }
+
+            if (!wasModified) {
+                m_notifier.notifyChanged(RepositoryAdmin.TOPIC_STATUSCHANGED_SUFFIX, null);
+            }
+        }
+    }
+
+    public boolean writeAccess() {
+        return m_writeAccess;
+    }
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Artifact2GroupAssociation.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Artifact2GroupAssociation.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Artifact2GroupAssociation.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Artifact2GroupAssociation.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,15 @@
+package net.luminis.liq.client.repository.object;
+
+import net.luminis.liq.client.repository.Association;
+
+/**
+ * Interface to a Artifact2GroupAssociation. Most functionality is defined by the generic Association.
+ */
+public interface Artifact2GroupAssociation extends Association<ArtifactObject, GroupObject> {
+    public static final String TOPIC_ENTITY_ROOT = Artifact2GroupAssociation.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/ArtifactObject.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/ArtifactObject.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/ArtifactObject.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/ArtifactObject.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,86 @@
+package net.luminis.liq.client.repository.object;
+
+import java.util.List;
+
+import net.luminis.liq.client.repository.RepositoryObject;
+
+/**
+ * Interface to an ArtifactObject. The basic functionality is defined by RepositoryObject, but extended for
+ * artifact-specific information.
+ */
+public interface ArtifactObject extends RepositoryObject {
+    /**
+     * Key to be used in the <code>ArtifactObject</code>'s attributes.
+     * Indicates the location of the persistent storage of the artifact.
+     */
+    public static final String KEY_URL = "url";
+    /**
+     * Key to be used in the <code>ArtifactObject</code>'s attributes.
+     * Indicates the PID of the resource processor that should be used to process this artifact.
+     * For a bundle, it is empty.
+     */
+    public static final String KEY_PROCESSOR_PID = "processorPid";
+    /**
+     * Key to be used in the <code>ArtifactObject</code>'s attributes.
+     * Indicates the mimetype of this artifact. For artifacts which do not
+     * have an adequately discriminating mimetype, it can be extended with
+     * something non-standard.
+     */
+    public static final String KEY_MIMETYPE = "mimetype";
+    /**
+     * Key to be used in the <code>ArtifactObject</code>'s attributes.
+     * Holds a human-readable name for this artifact.
+     */
+    public static final String KEY_ARTIFACT_NAME = "artifactName";
+    /**
+     * Key to be used in the <code>ArtifactObject</code>'s attributes.
+     * Holds a human-readable description for this artifact.
+     */
+    public static final String KEY_ARTIFACT_DESCRIPTION = "artifactDescription";
+    
+    public static final String TOPIC_ENTITY_ROOT = ArtifactObject.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+
+    /**
+     * Returns all <code>GroupObject</code>s this object is associated with. If there
+     * are none, an empty list will be returned.
+     */
+    public List<GroupObject> getGroups();
+    /**
+     * Returns all associations this artifact has with a given group.
+     */
+    public List<Artifact2GroupAssociation> getAssociationsWith(GroupObject group);
+
+    /**
+     * Returns the mimetype of this artifact.
+     */
+    public String getMimetype();
+    /**
+     * Returns the PID of the resource processor of this artifact.
+     */
+    public String getProcessorPID();
+    /**
+     * Sets the PID of the resource processor of this artifact.
+     */
+    public void setProcessorPID(String processorPID);
+    /**
+     * Returns the URL to this artifact.
+     */
+    public String getURL();
+    /**
+     * Return a descriptive name for this object. May return <code>null</code>.
+     */
+    public String getName();
+    /**
+     * Returns a description for this object. May return <code>null</code>.
+     */
+    public String getDescription();
+    /**
+     * Sets a description for this artifact.
+     */
+    public void setDescription(String value);
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentArtifact.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentArtifact.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentArtifact.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentArtifact.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,46 @@
+package net.luminis.liq.client.repository.object;
+
+import java.net.URL;
+
+/**
+ * Interface to a deployment artifact, which is used to gather information about
+ * the deployment of a single artifact.
+ */
+public interface DeploymentArtifact {
+
+    /**
+     * Key, intended to be used for artifacts which are bundles and will publish
+     * a resource processor (see OSGi compendium section 114.10).
+     */
+    public static final String DIRECTIVE_ISCUSTOMIZER = "DeploymentPackage-Customizer";
+    
+    /**
+     * Key, intended to be used for resources which require a resource processor
+     * (see OSGi compendium section 114.10).
+     */
+    public static final String DIRECTIVE_KEY_PROCESSORID = "Resource-Processor";
+
+    /**
+     * Key, intended to be used for matching processed (see ArtifactPreprocessor) to their
+     * 'original' one.
+     */
+    public static final String DIRECTIVE_KEY_BASEURL = "Base-Url";
+
+    /**
+     * @return the URL for this deployment artifact.
+     */
+    public String getUrl();
+    
+    /**
+     * @param key A key String, such as the <code>DIRECTIVE_</code> constants in
+     * <code>DeploymentArtifact</code>.
+     * @return the value for the given directive key, or <code>null</code> if not found.
+     */
+    public String getDirective(String key);
+    
+    /**
+     * @return an array of all keys that are used in this object, to be used in <code>getDirective</code>.
+     */
+    public String[] getKeys();
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentVersionObject.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentVersionObject.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentVersionObject.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/DeploymentVersionObject.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,40 @@
+package net.luminis.liq.client.repository.object;
+
+import net.luminis.liq.client.repository.RepositoryObject;
+
+/**
+ * The interface to a DeploymentVersion. The basic functionality is defined
+ * by RepositoryObject, but extended for deployment version-specific information.
+ *
+ * DeploymentVersions need some additional information about the artifacts they
+ * are associated with; see DeploymentArtifact.
+ */
+public interface DeploymentVersionObject extends RepositoryObject {
+
+    public static final String KEY_GATEWAYID = "gatewayID";
+    public static final String KEY_VERSION = "version";
+    
+    public static final String TOPIC_ENTITY_ROOT = DeploymentVersionObject.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+
+    /**
+     * Gets the gateway which is related to this version.
+     */
+    public String getGatewayID();
+
+    /**
+     * Gets the version number of this deployment version.
+     */
+    public String getVersion();
+
+    /**
+     * @return an array of all deployment artifacts that will be part of this deployment version.
+     * The order of the artifacts in the array is equal to the order they should appear in a 
+     * deployment package.
+     */
+    public DeploymentArtifact[] getDeploymentArtifacts();
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GatewayObject.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GatewayObject.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GatewayObject.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GatewayObject.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,41 @@
+package net.luminis.liq.client.repository.object;
+
+import java.util.List;
+
+import net.luminis.liq.client.repository.RepositoryObject;
+
+public interface GatewayObject extends RepositoryObject {
+    public static final String KEY_ID = "id";
+    public static final String KEY_AUTO_APPROVE = "autoapprove";
+
+    public static final String TOPIC_ENTITY_ROOT = GatewayObject.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+
+    /**
+     * Returns all <code>LicenseObject</code>s this object is associated with. If there
+     * are none, an empty list will be returned.
+     */
+    public List<LicenseObject> getLicenses();
+    /**
+     * Returns all associations this gateway has with a given license.
+     */
+    public List<License2GatewayAssociation> getAssociationsWith(LicenseObject license);
+    /**
+     * Gets the ID of this GatewayObject.
+     */
+    public String getID();
+
+    /**
+     * Enable or disable automatic approval.
+     */
+    public void setAutoApprove(boolean approve);
+
+    /**
+     * Get the auto approval value of this gateway.
+     */
+    public boolean getAutoApprove();
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Group2LicenseAssociation.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Group2LicenseAssociation.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Group2LicenseAssociation.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/Group2LicenseAssociation.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,15 @@
+package net.luminis.liq.client.repository.object;
+
+import net.luminis.liq.client.repository.Association;
+
+/**
+ * Interface to a Group2LicenseAssociation. Most functionality is defined by the generic Association.
+ */
+public interface Group2LicenseAssociation extends Association<GroupObject, LicenseObject> {
+    public static final String TOPIC_ENTITY_ROOT = Group2LicenseAssociation.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GroupObject.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GroupObject.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GroupObject.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/GroupObject.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,60 @@
+package net.luminis.liq.client.repository.object;
+
+import java.util.List;
+
+import net.luminis.liq.client.repository.RepositoryObject;
+
+/**
+ * Interface to a GroupObject. The basic functionality is defined by RepositoryObject, but extended for
+ * Group-specific information.
+ */
+public interface GroupObject extends RepositoryObject {
+    public static final String KEY_DESCRIPTION = "description";
+    public static final String KEY_NAME = "name";
+
+    public static final String TOPIC_ENTITY_ROOT = GroupObject.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+
+    /**
+     * Returns all <code>ArtifactObject</code>s this object is associated with. If there
+     * are none, an empty list will be returned.
+     */
+    public List<ArtifactObject> getArtifacts();
+    /**
+     * Returns all <code>LicenseObject</code>s this object is associated with. If there
+     * are none, an empty list will be returned.
+     */
+    public List<LicenseObject> getLicenses();
+
+    /**
+     * Returns all associations this group has with a given bundle.
+     */
+    public List<Artifact2GroupAssociation> getAssociationsWith(ArtifactObject artifact);
+    /**
+     * Returns all associations this group has with a given license.
+     */
+    public List<Group2LicenseAssociation> getAssociationsWith(LicenseObject license);
+
+    /**
+     * Returns the name of this bundle.
+     */
+    public String getName();
+    /**
+     * Sets the name of this bundle.
+     */
+    public void setName(String name);
+    /**
+     * Returns the description of this bundle.
+     */
+    public String getDescription();
+    /**
+     * Sets the description of this bundle.
+     */
+    public void setDescription(String description);
+
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/License2GatewayAssociation.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/License2GatewayAssociation.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/License2GatewayAssociation.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/License2GatewayAssociation.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,15 @@
+package net.luminis.liq.client.repository.object;
+
+import net.luminis.liq.client.repository.Association;
+
+/**
+ * Interface to a License2GatewayAssociation. Most functionality is defined by the generic Association.
+ */
+public interface License2GatewayAssociation extends Association<LicenseObject, GatewayObject> {
+    public static final String TOPIC_ENTITY_ROOT = License2GatewayAssociation.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/LicenseObject.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/LicenseObject.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/LicenseObject.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/object/LicenseObject.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,58 @@
+package net.luminis.liq.client.repository.object;
+
+import java.util.List;
+
+import net.luminis.liq.client.repository.RepositoryObject;
+
+/**
+ * Interface to a LicenseObject. The basic functionality is defined by RepositoryObject, but extended for
+ * License-specific information.
+ */
+public interface LicenseObject extends RepositoryObject {
+    public static final String TOPIC_ENTITY_ROOT = LicenseObject.class.getSimpleName() + "/";
+    
+    public static final String TOPIC_ADDED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ADDED_SUFFIX;
+    public static final String TOPIC_REMOVED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_REMOVED_SUFFIX;
+    public static final String TOPIC_CHANGED = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_CHANGED_SUFFIX;
+    public static final String TOPIC_ALL = PUBLIC_TOPIC_ROOT + TOPIC_ENTITY_ROOT + TOPIC_ALL_SUFFIX;
+
+    public static final String KEY_DESCRIPTION = "description";
+    public static final String KEY_NAME = "name";
+
+    /**
+     * Returns all <code>GroupObject</code>s this object is associated with. If there
+     * are none, an empty list will be returned.
+     */
+    public List<GroupObject> getGroups();
+    /**
+     * Returns all <code>GatewayObject</code>s this object is associated with. If there
+     * are none, an empty list will be returned.
+     */
+    public List<GatewayObject> getGateways();
+
+    /**
+     * Returns all associations this license has with a given group.
+     */
+    public List<Group2LicenseAssociation> getAssociationsWith(GroupObject group);
+    /**
+     * Returns all associations this license has with a given gateway.
+     */
+    public List<License2GatewayAssociation> getAssociationsWith(GatewayObject gateway);
+
+    /**
+     * Returns the name of this bundle.
+     */
+    public String getName();
+    /**
+     * Sets the name of this bundle.
+     */
+    public void setName(String name);
+    /**
+     * Returns the description of this bundle.
+     */
+    public String getDescription();
+    /**
+     * Sets the description of this bundle.
+     */
+    public void setDescription(String description);
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Artifact2GroupAssociationRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Artifact2GroupAssociationRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Artifact2GroupAssociationRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Artifact2GroupAssociationRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,12 @@
+package net.luminis.liq.client.repository.repository;
+
+import net.luminis.liq.client.repository.AssociationRepository;
+import net.luminis.liq.client.repository.object.Artifact2GroupAssociation;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.GroupObject;
+
+/**
+ * Interface to a Artifact2GroupAssociationRepository. The functionality is defined by the generic AssociationRepository.
+ */
+public interface Artifact2GroupAssociationRepository extends AssociationRepository<ArtifactObject, GroupObject, Artifact2GroupAssociation> {
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/ArtifactRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/ArtifactRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/ArtifactRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/ArtifactRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,92 @@
+package net.luminis.liq.client.repository.repository;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import net.luminis.liq.client.repository.ObjectRepository;
+import net.luminis.liq.client.repository.helper.PropertyResolver;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.GatewayObject;
+
+/**
+ * Interface to a ArtifactRepository. The functionality is defined by the generic AssociationRepository.
+ */
+public interface ArtifactRepository extends ObjectRepository<ArtifactObject>{
+	/**
+	 * Gets a list of all ArtifactObject's which are resource processing bundles.
+	 */
+	public List<ArtifactObject> getResourceProcessors();
+	
+	/**
+	 * Tries to import an artifact into storage, while extracting necessary metadata.
+	 * @param artifact a URL pointing to the 'physical' artifact.
+	 * @param upload Indicates whether this artifact should be uploaded to our own OBR.
+	 * @return An <code>ArtifactObject</code> representing the passed in artifact, if
+	 * (a) the artifact is recognized, (b) there is storage available and (c) there is
+	 * a resource processor available for this type of artifact.
+	 * @throws IllegalArgumentException when the <code>artifact</code> cannot be processed.
+	 * @throws IOException when there is a problem transferring the <code>artifact</code> to storage.
+	 */
+	public ArtifactObject importArtifact(URL artifact, boolean upload) throws IllegalArgumentException, IOException;
+	
+	/**
+	 * Checks whether an artifact is 'usable', that is, there is a resource processor available for it,
+	 * if necessary.
+	 * @param artifact A URL pointing to an artifact.
+	 * @return <code>true</code> if the artifact is recognized, and a processor for it is available. <code>false</code>
+	 * otherwise, including when the artifact cannot be reached.
+	 */
+	public boolean recognizeArtifact(URL artifact);
+	
+    /**
+     * Tries to import an artifact into storage, while extracting necessary metadata.
+     * @param artifact a URL pointing to the 'physical' artifact.
+     * @param mimetype a String giving this object's mimetype.
+     * @param upload Indicates whether this artifact should be uploaded to our own OBR.
+     * @return An <code>ArtifactObject</code> representing the passed in artifact, if
+     * (a) there is storage available and (b) there is a resource processor 
+     * available for this type of artifact.
+     * @throws IllegalArgumentException when the <code>artifact</code> cannot be processed.
+     * @throws IOException when there is a problem transferring the <code>artifact</code> to storage.
+     */
+	public ArtifactObject importArtifact(URL artifact, String mimetype, boolean upload) throws IllegalArgumentException, IOException;
+	
+	/**
+	 * Tries to locate a preprocessor for the passed artifact, an processes it. If no processing
+	 * needs to be done, the original artifact's URL will be returned.
+	 * @param artifact An artifact 
+	 * @param props A tree of properties objects, to be used for replacement.
+	 * @param gatewayID The gatewayID of the gateway for which this artifact is being processed.
+	 * @param version The deployment version for which this artifact is being processed.
+	 * @return A URL to a new, processed artifact, or to the original one, in case nothing needed to be processed.
+     * @throws IOException Thrown if reading the original artifact goes wrong, or storing the processed one.
+	 */
+	public String preprocessArtifact(ArtifactObject artifact, GatewayObject gateway, String gatewayID, String version) throws IOException ;
+	
+    /**
+     * Indicates whether the template should be processed again, given the properties, and the version to which it
+     * should be compared. 
+     * @param url A string representing a URL to the original artifact.
+     * @param props A PropertyResolver which can be used to fill in 'holes' in the template.
+     * @param gatewayID The gatewayID of the gateway for which this artifact is being processed.
+     * @param version The deployment version for which this artifact is being processed.
+     * @param lastVersion The deployment version to which the current one should be compared.
+     * @param newVersion The new, potential version.
+     * @param obrBase A base OBR to upload the new artifact to.
+     * @return Whether or not a new version has to be created.
+     * @throws IOException 
+     */
+    public boolean needsNewVersion(ArtifactObject artifact, GatewayObject gateway, String gatewayID, String fromVersion);
+	
+	/**
+	 * Sets the OBR that this artifact repository should use to upload artifacts to.
+	 */
+	public void setObrBase(URL obrBase);
+
+    /**
+     * Gets the OBR that this artifact repository should use to upload artifacts to.
+     * Note that this method may return <code>null</code> if no base was set earlier.
+     */
+    public URL getObrBase();
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/DeploymentVersionRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/DeploymentVersionRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/DeploymentVersionRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/DeploymentVersionRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,45 @@
+package net.luminis.liq.client.repository.repository;
+
+import java.util.List;
+import java.util.Map;
+
+import net.luminis.liq.client.repository.ObjectRepository;
+import net.luminis.liq.client.repository.object.DeploymentArtifact;
+import net.luminis.liq.client.repository.object.DeploymentVersionObject;
+
+/**
+ * Interface to a DeploymentVersionRepository. The functionality is defined by the generic ObjectRepository.
+ */
+public interface DeploymentVersionRepository extends ObjectRepository<DeploymentVersionObject> {
+    /**
+     * Creates a new inhabitant based on the given attributes and bundle URLs. The object
+     * will be stored in this repository's store, and will be returned.
+     * @throws IllegalArgumentException Will be thrown when the attributes cannot be accepted.
+     */
+    public DeploymentVersionObject create(Map<String, String> attributes, Map<String, String> tags, DeploymentArtifact[] artifacts);
+
+    /**
+     * Gets all available deployment versions for this gateway. If none can be
+     * found, an empty list will be returned.
+     * @param gatewayID The gateway to be used.
+     * @return A list of <code>DeploymentVersionObject</code>s which are related to
+     * this gateway, sorted lexically by version.
+     */
+    public List<DeploymentVersionObject> getDeploymentVersions(String gatewayID);
+
+    /**
+     * Get the most recent known deployment version for a given gateway.
+     * @param gatewayID The gateway to be used.
+     * @return A <code>DeploymentVersionObject</code> which is the most recent one to be deployed
+     * to the gateway. If none can be found, <code>null</code> will be returned.
+     */
+    public DeploymentVersionObject getMostRecentDeploymentVersion(String gatewayID);
+
+    /**
+     * Creates a DeploymentArtifact object.
+     * @param url The url to be used in this object.
+     * @param directives A map of directives to be packed into the object.
+     * @return The newly created deployment artifact object.
+     */
+    public DeploymentArtifact createDeploymentArtifact(String url, Map<String, String> directives);
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GatewayRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GatewayRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GatewayRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GatewayRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,10 @@
+package net.luminis.liq.client.repository.repository;
+
+import net.luminis.liq.client.repository.ObjectRepository;
+import net.luminis.liq.client.repository.object.GatewayObject;
+
+/**
+ * Interface to a GatewayRepository. The functionality is defined by the generic AssociationRepository.
+ */
+public interface GatewayRepository extends ObjectRepository<GatewayObject>{
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Group2LicenseAssociationRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Group2LicenseAssociationRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Group2LicenseAssociationRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/Group2LicenseAssociationRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,12 @@
+package net.luminis.liq.client.repository.repository;
+
+import net.luminis.liq.client.repository.AssociationRepository;
+import net.luminis.liq.client.repository.object.Group2LicenseAssociation;
+import net.luminis.liq.client.repository.object.GroupObject;
+import net.luminis.liq.client.repository.object.LicenseObject;
+
+/**
+ * Interface to a Group2LicenseAssociationRepository. The functionality is defined by the generic AssociationRepository.
+ */
+public interface Group2LicenseAssociationRepository extends AssociationRepository<GroupObject, LicenseObject, Group2LicenseAssociation> {
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GroupRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GroupRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GroupRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/GroupRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,10 @@
+package net.luminis.liq.client.repository.repository;
+
+import net.luminis.liq.client.repository.ObjectRepository;
+import net.luminis.liq.client.repository.object.GroupObject;
+
+/**
+ * Interface to a GroupRepository. The functionality is defined by the generic AssociationRepository.
+ */
+public interface GroupRepository extends ObjectRepository<GroupObject> {
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/License2GatewayAssociationRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/License2GatewayAssociationRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/License2GatewayAssociationRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/License2GatewayAssociationRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,20 @@
+package net.luminis.liq.client.repository.repository;
+
+import net.luminis.liq.client.repository.AssociationRepository;
+import net.luminis.liq.client.repository.object.GatewayObject;
+import net.luminis.liq.client.repository.object.License2GatewayAssociation;
+import net.luminis.liq.client.repository.object.LicenseObject;
+
+/**
+ * Interface to a License2GatewayAssociationRepository. The functionality is defined by the generic AssociationRepository.
+ */
+public interface License2GatewayAssociationRepository extends AssociationRepository<LicenseObject, GatewayObject, License2GatewayAssociation> {
+    /**
+     * Creates an assocation from a given license to multiple gateways, which correspond to the given
+     * filter string. For parameters to use in the filter, see <code>GatewayObject</code>'s <code>KEY_</code> constants.
+     * @param license A license object for the left side of this association.
+     * @param gatewayFilter An LDAP-filter for the gateways to use.
+     * @return The newly created association.
+     */
+    public License2GatewayAssociation createLicense2GatewayFilter(LicenseObject license, String gatewayFilter);
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/LicenseRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/LicenseRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/LicenseRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/repository/LicenseRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,10 @@
+package net.luminis.liq.client.repository.repository;
+
+import net.luminis.liq.client.repository.ObjectRepository;
+import net.luminis.liq.client.repository.object.LicenseObject;
+
+/**
+ * Interface to a LicenseRepository. The functionality is defined by the generic AssociationRepository.
+ */
+public interface LicenseRepository extends ObjectRepository<LicenseObject>{
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayObject.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayObject.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayObject.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayObject.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,191 @@
+package net.luminis.liq.client.repository.stateful;
+
+import java.util.List;
+
+import net.luminis.liq.client.repository.RepositoryObject;
+import net.luminis.liq.client.repository.object.ArtifactObject;
+import net.luminis.liq.client.repository.object.DeploymentArtifact;
+import net.luminis.liq.client.repository.object.GatewayObject;
+import net.luminis.liq.client.repository.object.License2GatewayAssociation;
+import net.luminis.liq.client.repository.object.LicenseObject;
+import net.luminis.liq.log.LogEvent;
+
+/**
+ * <code>StatefulGatewayObject</code> represents the information that a <code>GatewayObject</code>
+ * has, plus added functionality for gathering information from a deployment repository and,
+ * optionally, from an AuditLog.
+ */
+public interface StatefulGatewayObject extends RepositoryObject {
+
+    public static final String TOPIC_ADDED = StatefulGatewayObject.class.getName().replace('.', '/') + "/ADDED";
+    public static final String TOPIC_REMOVED = StatefulGatewayObject.class.getName().replace('.', '/') + "/REMOVED";
+    public static final String TOPIC_CHANGED = StatefulGatewayObject.class.getName().replace('.', '/') + "/CHANGED";
+    public static final String TOPIC_STATUS_CHANGED = StatefulGatewayObject.class.getName().replace('.', '/') + "/STATUS_CHANGED";
+    /** Indicates a change to the audit events for the StatefulGatewayObject in "entity".*/
+    public static final String TOPIC_AUDITEVENTS_CHANGED = StatefulGatewayObject.class.getName().replace('.', '/') + "/AUDITEVENTS_CHANGED";
+    /** Key used in the event with topic <code>TOPIC_AUDITEVENTS_CHANGED</code>. Contains a List<LogDescriptor> containing all
+     *  events we have not seen yet. NOTE: The first auditevent "change" causing the <code>StatefulGatewayObject</code> to
+     *  be instantiated will trigger a <code>TOPIC_AUDITEVENTS_CHANGED</code> event *before* a <code>TOPIC_ADDED</code> event. */
+    public static final String KEY_AUDITEVENTS = "auditevents";
+    public static final String TOPIC_ALL = StatefulGatewayObject.class.getName().replace('.', '/') + "/*";
+
+    public final static String KEY_ID = GatewayObject.KEY_ID;
+    public final static String KEY_REGISTRATION_STATE = "KEY_REGISTRATION_STATE";
+    public final static String KEY_STORE_STATE = "KEY_STORE_STATE";
+    public final static String KEY_PROVISIONING_STATE = "KEY_PROVISIONING_STATE";
+    public final static String KEY_LAST_INSTALL_VERSION = "KEY_LAST_INSTALL_VERSION";
+    public final static String KEY_LAST_INSTALL_SUCCESS = "KEY_LAST_INSTALL_SUCCESS";
+    public final static String KEY_ACKNOWLEDGED_INSTALL_VERSION = "KEY_ACKNOWLEDGED_INSTALL_VERSION";
+    public final static String[] KEYS_ALL = new String[] {KEY_ID, KEY_REGISTRATION_STATE, KEY_STORE_STATE, KEY_PROVISIONING_STATE, KEY_LAST_INSTALL_VERSION, KEY_LAST_INSTALL_SUCCESS, KEY_ACKNOWLEDGED_INSTALL_VERSION};
+
+    /**
+     * Represents a current deployment package version which cannot be found, i.e.,
+     * either there is no information about deployment packages in the AuditLog,
+     * or no AuditLog is available.
+     */
+    public final static String UNKNOWN_VERSION = "(unknown)";
+
+    /**
+     * Gets the current registration status of the gateway.
+     */
+    public RegistrationState getRegistrationState();
+
+    /**
+     * Gets the current store status of the gateway.
+     */
+    public StoreState getStoreState();
+
+    /**
+     * Gets the current provisioning status of the gateway.
+     */
+    public ProvisioningState getProvisioningState();
+
+    /**
+     * Gets the most recent deployment package version on the gateway, according
+     * to the deployment repository. If no version can be determined,
+     * <code>UNKNOWN_VERSION</code> will be returned.
+     */
+    public String getCurrentVersion();
+
+    /**
+     * Gets the list of AuditLog Events for this gateway. If no auditlog events
+     * can be found, and empty list will be returned. The events are ordered ascending by timestamp.
+     */
+    public List<LogEvent> getAuditEvents();
+
+    /**
+     * Registers this gateway, which for now only exists in the AuditLog, into the
+     * <code>GatewayRepository</code>.
+     * @throws IllegalStateException when the precondition is not met, i.e., the
+     * gateway is not known only in the AuditLog, but also in the <code>GatewayRepository</code>.
+     */
+    public void register() throws IllegalStateException;
+
+    /**
+     * Indicates whether this <code>StatefulGatewayObject</code> is backed by a <code>GatewayObject</code>.
+     * @return whether this <code>StatefulGatewayObject</code> is backed by a <code>GatewayObject</code>.
+     */
+    public boolean isRegistered();
+
+    /**
+     * Approves all differences between what is currently in the shop and gateway operator
+     * repository, and the deployment repository. This will generate a new version in the
+     * deployment repository.
+     * @return The number of the new version.
+     * @throws IllegalStateException when it is currently not possible to create a deployment version. 
+     */
+    public String approve();
+
+    /**
+     * Indicates whether an <code>approve()</code> is necessary, i.e., there is a difference between
+     * the set of bundles for this gateway according to the shop, and according to the deployment
+     * repository.
+     * @return <code>true</code> if there is a difference between the shop and deployment repository;
+     * <code>false</code> otherwise.
+     */
+    public boolean needsApprove();
+
+    /**
+     * Returns the auto-approval flag for this gateway.
+     * @return <code>true</code> if auto approve has been set;
+     * <code>false</code> otherwise.
+     */
+    public boolean getAutoApprove();
+
+    /**
+     * Set the auto approve value for this gateway, the property is stored within the gateway
+     * @param approve <code>true</code> to enable auto approve;
+     * <code>false</code> otherwise.
+     */
+    public void setAutoApprove(boolean approve);
+
+    /**
+     * Gets the list of artifact objects that should be on the gateway, according to the shop.
+     * @return the list of artifact objects that should be on the gateway, according to the shop.
+     */
+    public ArtifactObject[] getArtifactsFromShop();
+
+    /**
+     * Gets the list of deployment artifacts that should be on the gateway, according to the deployment repository.
+     * @return the list of artifact objects that should be on the gateway, according to the deployment repository.
+     */
+    public DeploymentArtifact[] getArtifactsFromDeployment();
+
+    /**
+     * Returns the latest available installed version in the auditlog.
+     * @return The latest version statement from the auditlog for an install if
+     * one is available; otherwise, an empty string.
+     */
+    public String getLastInstallVersion();
+
+    /**
+     * Returns whether the last install on the gateway way successful.
+     * @return <code>true</code> if there information about a last install and
+     * that was successful, <code>false</code> otherwise.
+     */
+    public boolean getLastInstallSuccess();
+
+    /**
+     * Signals to the object that the outcome of a given install on the gateway
+     * is 'seen', and that the <code>ProvisioningState</code> can now return to <code>Idle</code>.
+     * @param version A string representing a version.
+     */
+    public void acknowledgeInstallVersion(String version);
+
+    /**
+     * Gets the underlying <code>GatewayObject</code> of this <code>StatefulGatewayObject</code>.
+     * @return The <code>GatewayObject</code> linked to this <code>StatefulGatewayObject</code>; if none
+     * is available, an <code>IllegalStateException</code> will be thrown.
+     */
+    public GatewayObject getGatewayObject();
+
+    /**
+     * Returns all <code>LicenseObject</code>s this object is associated with. If there
+     * are none, an empty list will be returned.
+     */
+    public List<LicenseObject> getLicenses();
+
+    /**
+     * Returns all associations this gateway has with a given license.
+     */
+    public List<License2GatewayAssociation> getAssociationsWith(LicenseObject license);
+
+    /**
+     * Gets the ID of this GatewayObject.
+     */
+    public String getID();
+
+    public enum RegistrationState {
+        Unregistered, Registered;
+    }
+
+    public enum StoreState {
+        New, Unapproved, Approved;
+    }
+
+    public enum ProvisioningState {
+        Idle, InProgress, OK, Failed;
+    }
+
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayRepository.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayRepository.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayRepository.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/StatefulGatewayRepository.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,37 @@
+package net.luminis.liq.client.repository.stateful;
+
+import java.util.Map;
+
+import net.luminis.liq.client.repository.ObjectRepository;
+
+/**
+ * Represents a repository of <ode>StatefulGatewayObject</code>'s.
+ */
+public interface StatefulGatewayRepository extends ObjectRepository<StatefulGatewayObject> {
+
+    /**
+     * Registers a gateway with given attributes. This will result in the creation
+     * of a <code>GatewayObject</code> in the <code>GatewayRepository</code>, and
+     * the creation of a <code>StatefulGatewayObject</code>, which will also be
+     * returned.
+     * @param attributes The attributes to create the <code>GatewayObject</code> with.
+     * @return The newly registered gateway object.
+     */
+    public StatefulGatewayObject preregister(Map<String, String> attributes, Map<String, String> tags);
+
+    /**
+     * Unregisters a gateway, removing it from the <code>GatewayRepository</code>. Note
+     * that a <code>StatefulGatewayObject</code> might stay around if it is backed
+     * by audit log entries. If the given ID is not that of an existing <code>GatewayObject</code>,
+     * an <code>IllegalArgumentException</code> will be thrown.
+     * @param gatewayID A string representing a gateway ID.
+     */
+    public void unregister(String gatewayID);
+
+    /**
+     * Explicitly instruct the <code>StatefulGatewayRepository</code> to update
+     * its contents; for instance, after syncing the audit log.
+     */
+    public void refresh();
+
+}

Added: incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/impl/Activator.java
URL: http://svn.apache.org/viewvc/incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/impl/Activator.java?rev=788992&view=auto
==============================================================================
--- incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/impl/Activator.java (added)
+++ incubator/ace/trunk/server/src/net/luminis/liq/client/repository/stateful/impl/Activator.java Sat Jun 27 15:53:04 2009
@@ -0,0 +1,70 @@
+package net.luminis.liq.client.repository.stateful.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import net.luminis.liq.client.repository.RepositoryAdmin;
+import net.luminis.liq.client.repository.helper.bundle.BundleHelper;
+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.ArtifactRepository;
+import net.luminis.liq.client.repository.repository.DeploymentVersionRepository;
+import net.luminis.liq.client.repository.repository.GatewayRepository;
+import net.luminis.liq.client.repository.stateful.StatefulGatewayRepository;
+import net.luminis.liq.server.log.store.LogStore;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.log.LogService;
+
+/**
+ * Activator for the StatefulGatewayRepository bundle.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        StatefulGatewayRepositoryImpl statefulGatewayRepositoryImpl = new StatefulGatewayRepositoryImpl();
+        manager.add(createService()
+            .setInterface(StatefulGatewayRepository.class.getName(), null)
+            .setImplementation(statefulGatewayRepositoryImpl)
+            .add(createServiceDependency().setService(ArtifactRepository.class).setRequired(true))
+            .add(createServiceDependency().setService(GatewayRepository.class).setRequired(true))
+            .add(createServiceDependency().setService(DeploymentVersionRepository.class).setRequired(true))
+            .add(createServiceDependency().setService(LogStore.class, "(&("+Constants.OBJECTCLASS+"="+LogStore.class.getName()+")(name=auditlog))").setRequired(false))
+            .add(createServiceDependency().setService(BundleHelper.class).setRequired(true))
+            .add(createServiceDependency().setService(EventAdmin.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+        Dictionary<String, String[]> topic = new Hashtable<String, String[]>();
+        topic.put(EventConstants.EVENT_TOPIC, new String[] {
+            ArtifactObject.TOPIC_ALL,
+            Artifact2GroupAssociation.TOPIC_ALL,
+            GroupObject.TOPIC_ALL,
+            Group2LicenseAssociation.TOPIC_ALL,
+            LicenseObject.TOPIC_ALL,
+            License2GatewayAssociation.TOPIC_ALL,
+            GatewayObject.TOPIC_ALL,
+            DeploymentVersionObject.TOPIC_ALL,
+            RepositoryAdmin.TOPIC_REFRESH, RepositoryAdmin.TOPIC_LOGIN});
+        manager.add(createService()
+            .setInterface(EventHandler.class.getName(), topic)
+            .setImplementation(statefulGatewayRepositoryImpl));
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // service deregistration will happen automatically.
+    }
+
+}