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

svn commit: r1463576 [3/8] - in /ace/trunk: org.apache.ace.client.repository.api/ org.apache.ace.client.repository.helper.base/ org.apache.ace.client.repository.helper.bundle/ org.apache.ace.client.repository.helper.configuration/ org.apache.ace.client...

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationRepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationRepositoryImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationRepositoryImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationRepositoryImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.Map;
+
+import org.apache.ace.client.repository.object.Artifact2FeatureAssociation;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.client.repository.object.FeatureObject;
+import org.apache.ace.client.repository.repository.Artifact2FeatureAssociationRepository;
+import org.osgi.framework.InvalidSyntaxException;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the Artifact2FeatureAssociationRepository. For 'what it does', see Artifact2FeatureAssociationRepository,
+ * for 'how it works', see AssociationRepositoryImpl.
+ */
+public class Artifact2FeatureAssociationRepositoryImpl extends AssociationRepositoryImpl<ArtifactObject, FeatureObject, Artifact2FeatureAssociationImpl, Artifact2FeatureAssociation> implements Artifact2FeatureAssociationRepository {
+    private final static String XML_NODE = "artifacts2features";
+
+    private final ArtifactRepositoryImpl m_artifactRepository;
+    private final FeatureRepositoryImpl m_featureRepository;
+
+    public Artifact2FeatureAssociationRepositoryImpl(ArtifactRepositoryImpl artifactRepository, FeatureRepositoryImpl featureRepository, ChangeNotifier notifier) {
+        super(notifier, XML_NODE);
+        m_artifactRepository = artifactRepository;
+        m_featureRepository = featureRepository;
+    }
+
+    @Override
+    Artifact2FeatureAssociationImpl createNewInhabitant(Map<String, String> attributes) {
+        try {
+            return new Artifact2FeatureAssociationImpl(attributes, this, m_artifactRepository, m_featureRepository);
+        }
+        catch (InvalidSyntaxException e) {
+            throw new IllegalArgumentException("Unable to create association: ", e);
+        }
+    }
+
+    @Override
+    Artifact2FeatureAssociationImpl createNewInhabitant(Map<String, String> attributes, Map<String, String> tags) {
+        try {
+            return new Artifact2FeatureAssociationImpl(attributes, tags, this, m_artifactRepository, m_featureRepository);
+        }
+        catch (InvalidSyntaxException e) {
+            throw new IllegalArgumentException("Unable to create association: ", e);
+        }
+    }
+
+    @Override
+    Artifact2FeatureAssociationImpl createNewInhabitant(HierarchicalStreamReader reader) {
+        try {
+            return new Artifact2FeatureAssociationImpl(reader, this, m_artifactRepository, m_featureRepository);
+        }
+        catch (InvalidSyntaxException e) {
+            throw new IllegalArgumentException("Unable to create association: ", e);
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactObjectImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactObjectImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactObjectImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactObjectImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ace.client.repository.helper.ArtifactHelper;
+import org.apache.ace.client.repository.object.Artifact2FeatureAssociation;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.client.repository.object.FeatureObject;
+
+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 final 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;
+    }
+
+    ArtifactObjectImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, ArtifactRepositoryImpl repo) {
+        super(reader, notifier, XML_NODE);
+        m_repo = repo;
+    }
+    
+    private static String[] completeMandatoryAttributes(String[] mandatory) {
+        String[] result = new String[mandatory.length + 1];
+        for (int i = 0; i < mandatory.length; i++) {
+            result[i] = mandatory[i];
+        }
+        result[mandatory.length] = KEY_MIMETYPE;
+        return result;
+    }
+
+    public List<FeatureObject> getFeatures() {
+        return getAssociations(FeatureObject.class);
+    }
+
+    public List<Artifact2FeatureAssociation> getAssociationsWith(FeatureObject feature) {
+        return getAssociationsWith(feature, FeatureObject.class, Artifact2FeatureAssociation.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();
+    }
+
+    public String getURL() {
+        return getAttribute(KEY_URL);
+    }
+
+    public String getResourceId() {
+        return getAttribute(KEY_RESOURCE_ID);
+    }
+
+    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);
+    }
+
+    public void setDescription(String value) {
+        addAttribute(KEY_ARTIFACT_DESCRIPTION, value);
+    }
+
+    @Override
+    String[] getDefiningKeys() {
+        return getHelper().getDefiningKeys().clone();
+    }
+    
+    private ArtifactHelper getHelper() {
+        // getMimetype is safe, as is getHelper, and m_repo is final, so no
+        // need to synchronize here...
+        return m_repo.getHelper(getMimetype());
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ArtifactRepositoryImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,603 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+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 org.apache.ace.client.repository.RepositoryUtil;
+import org.apache.ace.client.repository.helper.ArtifactHelper;
+import org.apache.ace.client.repository.helper.ArtifactPreprocessor;
+import org.apache.ace.client.repository.helper.ArtifactRecognizer;
+import org.apache.ace.client.repository.helper.ArtifactResource;
+import org.apache.ace.client.repository.helper.bundle.BundleHelper;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.client.repository.object.TargetObject;
+import org.apache.ace.client.repository.repository.ArtifactRepository;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+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";
+
+    // Injected by Dependency Manager
+    private volatile BundleContext m_context;
+    private volatile LogService m_log;
+    private volatile ConnectionFactory m_connectionFactory;
+
+    private final Map<String, ArtifactHelper> m_helpers = new HashMap<String, ArtifactHelper>();
+    private URL m_obrBase;
+
+    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));
+        ArtifactObjectImpl ao = new ArtifactObjectImpl(helper.checkAttributes(attributes), helper.getMandatoryAttributes(), tags, this, this);
+        if ((ao.getAttribute("upload") != null) && (m_obrBase != null)) {
+            try {
+                ao.addAttribute(ArtifactObject.KEY_URL, new URL(m_obrBase, ao.getDefinition() + ao.getAttribute("upload")).toString());
+            }
+            catch (MalformedURLException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+        return ao;
+    }
+
+    @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);
+
+        ArtifactResource resource = convertToArtifactResource(url);
+
+        // 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);
+            try {
+                if (mimetype != null) {
+                    if (candidate.canHandle(mimetype)) {
+                        recognizer = candidate;
+                        break;
+                    }
+                }
+                else {
+                    String candidateMime = candidate.recognize(resource);
+                    if (candidateMime != null) {
+                        foundMimetype = candidateMime;
+                        recognizer = candidate;
+                        break;
+                    }
+                }
+            }
+            finally {
+                m_context.ungetService(ref);
+            }
+        }
+
+        if (recognizer == null) {
+            throw new IllegalArgumentException("There is no artifact recognizer that recognizes artifact " + ((mimetype != null) ? mimetype : url));
+        }
+
+        // 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 {
+        ArtifactResource resource = convertToArtifactResource(artifact);
+
+        Map<String, String> attributes = recognizer.extractMetaData(resource);
+        Map<String, String> tags = new HashMap<String, String>();
+
+        helper.checkAttributes(attributes);
+        attributes.put(ArtifactObject.KEY_ARTIFACT_DESCRIPTION, "");
+        if (overwrite) {
+            attributes.put(ArtifactObject.KEY_MIMETYPE, mimetype);
+        }
+
+        String artifactURL = artifact.toString();
+
+        attributes.put(ArtifactObject.KEY_URL, artifactURL);
+
+        if (upload) {
+            attributes.put("upload", recognizer.getExtension(resource));
+        }
+
+        ArtifactObject result = create(attributes, tags);
+
+        if (upload) {
+            try {
+                upload(artifact, result.getDefinition() + attributes.get("upload"), mimetype);
+            }
+            catch (IOException ex) {
+                remove(result);
+                throw ex;
+            }
+            finally {
+                try {
+                    attributes.remove("upload");
+                }
+                catch (Exception ex) {
+                    // Not much we can do
+                }
+            }
+        }
+        return result;
+
+    }
+
+    /**
+     * 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 = openInputStream(artifact);
+        }
+        catch (IOException ioe) {
+            throw new IllegalArgumentException("Artifact " + artifact + " 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.
+        String artifactName = artifact.toString();
+        for (byte b : artifactName.substring(artifactName.lastIndexOf('/') + 1).getBytes()) {
+            if (!(((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z')) || ((b >= '0') && (b <= '9')) || (b == '.') || (b == '-') || (b == '_'))) {
+                throw new IllegalArgumentException("Artifact " + artifactName + "'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 definition, 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 = openInputStream(artifact);
+
+            url = new URL(m_obrBase, definition);
+
+            URLConnection connection = m_connectionFactory.createConnection(url);
+
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+            connection.setUseCaches(false);
+
+            connection.setRequestProperty("Content-Type", mimetype);
+            if (connection instanceof HttpURLConnection) {
+                // ACE-294: enable streaming mode causing only small amounts of memory to be
+                // used for this commit. Otherwise, the entire input stream is cached into
+                // memory prior to sending it to the server...
+                ((HttpURLConnection) connection).setChunkedStreamingMode(8192);
+            }
+
+            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, TargetObject target, String targetID, String version) throws IOException {
+        ArtifactPreprocessor preprocessor = getHelper(artifact.getMimetype()).getPreprocessor();
+        if (preprocessor == null) {
+            return artifact.getURL();
+        }
+        else {
+            return preprocessor.preprocess(artifact.getURL(), new TargetPropertyResolver(target), targetID, version, m_obrBase);
+        }
+    }
+
+    public boolean needsNewVersion(ArtifactObject artifact, TargetObject target, String targetID, String fromVersion) {
+        ArtifactPreprocessor preprocessor = getHelper(artifact.getMimetype()).getPreprocessor();
+        if (preprocessor == null) {
+            return false;
+        }
+        else {
+            return preprocessor.needsNewVersion(artifact.getURL(), new TargetPropertyResolver(target), targetID, fromVersion);
+        }
+    }
+
+    public URL getObrBase() {
+        return m_obrBase;
+    }
+
+    /**
+     * Custom comparator which sorts service references by service rank, highest rank first.
+     */
+    private static Comparator<ServiceReference> SERVICE_RANK_COMPARATOR = new Comparator<ServiceReference>() { // TODO
+                                                                                                               // ServiceReferences
+                                                                                                               // are
+                                                                                                               // comparable
+                                                                                                               // by
+                                                                                                               // default
+                                                                                                               // now
+        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;
+        }
+    };
+
+    private InputStream openInputStream(URL artifactURL) throws IOException {
+        URLConnection connection = m_connectionFactory.createConnection(artifactURL);
+        return connection.getInputStream();
+    }
+
+    /**
+     * Converts a given URL to a {@link ArtifactResource} that abstracts the way we access the contents of the URL away
+     * from the URL itself. This way, we can avoid having to pass authentication credentials, or a
+     * {@link ConnectionFactory} to the artifact recognizers.
+     * 
+     * @param url
+     *            the URL to convert, can be <code>null</code> in which case <code>null</code> is returned.
+     * @return an {@link ArtifactResource}, or <code>null</code> if the given URL was <code>null</code>.
+     */
+    private ArtifactResource convertToArtifactResource(final URL url) {
+        if (url == null) {
+            return null;
+        }
+
+        return new ArtifactResource() {
+            public URL getURL() {
+                return url;
+            }
+
+            public InputStream openStream() throws IOException {
+                // Take care of the fact that an URL could need credentials to be accessible!!!
+                URLConnection conn = m_connectionFactory.createConnection(getURL());
+                conn.setUseCaches(true);
+                return conn.getInputStream();
+            }
+        };
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.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 org.apache.ace.client.repository.Associatable;
+import org.apache.ace.client.repository.Association;
+import org.apache.ace.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);
+            }
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationRepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationRepositoryImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationRepositoryImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/AssociationRepositoryImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ace.client.repository.Association;
+import org.apache.ace.client.repository.AssociationRepository;
+import org.apache.ace.client.repository.RepositoryObject;
+
+
+/**
+ * A basic association repository. It cannot be directly instantiated, since some functionality
+ * is delagated to deriving classes, being
+ * <bl>
+ * <li>The creation of new inhabitants</li>
+ * <li>The generation of filter strings based on objects</li>
+ * </bl>
+ *
+ * @param <L> The left side of the associations which will be stored in this repository.
+ * @param <R> The right side of the associations which will be stored in this repository.
+ * @param <I> An implementation of the association from <code>L</code> to <code>R</code>.
+ * @param <T> The non-generic Association interface that <code>I</code> implements.
+ */
+public abstract class AssociationRepositoryImpl<L extends RepositoryObject, R extends RepositoryObject, I extends AssociationImpl<L, R, T>, T extends Association<L, R>> extends ObjectRepositoryImpl<I, T> implements AssociationRepository<L, R, T> {
+    private Object m_lock = new Object();
+
+    public AssociationRepositoryImpl(ChangeNotifier notifier, String xmlNode) {
+        super(notifier, xmlNode);
+    }
+
+    @SuppressWarnings("unchecked")
+    public T create(String left, int leftCardinality, String right, int rightCardinality) {
+        synchronized (m_lock) {
+            T association = null;
+            try {
+                Map<String, String> attributes = new HashMap<String, String>();
+                attributes.put(Association.LEFT_ENDPOINT, left);
+                attributes.put(Association.RIGHT_ENDPOINT, right);
+                attributes.put(Association.LEFT_CARDINALITY, "" + leftCardinality);
+                attributes.put(Association.RIGHT_CARDINALITY, "" + rightCardinality);
+                association = (T) createNewInhabitant(attributes);
+                add(association);
+            }
+            catch (Exception e) {
+                // We have not been able to instantiate our constructor. Not much to do about that.
+                e.printStackTrace();
+            }
+            return association;
+        }
+    }
+
+    public T create(String left, String right) {
+        return create(left, Integer.MAX_VALUE, right, Integer.MAX_VALUE);
+    }
+
+    public T create(L left, Map<String, String> leftProps, R right, Map<String, String> rightProps) {
+        return create(left.getAssociationFilter(leftProps), left.getCardinality(leftProps),
+            right.getAssociationFilter(rightProps), right.getCardinality(rightProps));
+    }
+
+    public T create(L left, R right) {
+        return create(left, null, right, null);
+    }
+
+    public T create(List<L> left, List<R> right) {
+        if ((left == null) || left.isEmpty()) {
+            throw new IllegalArgumentException("The left side of the association cannot be empty.");
+        }
+        if ((right == null) || right.isEmpty()) {
+            throw new IllegalArgumentException("The right side of the association cannot be empty.");
+        }
+
+        StringBuilder leftFilter = new StringBuilder("(|");
+        for (L l : left) {
+            leftFilter.append(l.getAssociationFilter(null));
+        }
+        leftFilter.append(")");
+
+        StringBuilder rightFilter = new StringBuilder("(|");
+        for (R r : right) {
+            rightFilter.append(r.getAssociationFilter(null));
+        }
+        rightFilter.append(")");
+
+        return create(leftFilter.toString(), Integer.MAX_VALUE, rightFilter.toString(), Integer.MAX_VALUE);
+    }
+
+    @Override
+    public void remove(T entity) {
+        synchronized (m_lock) {
+            super.remove(entity);
+            entity.remove();
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifier.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifier.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifier.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifier.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.Properties;
+
+/**
+ * This interface defines a mechanism for notifying both internally interested parties, and
+ * external listeners, of changes to the a given 'inhabitant' of the model.
+ */
+public interface ChangeNotifier {
+    /**
+     * Notifies both internal and external listeners of a change to some object.
+     * @param topic A topic, as defined in the interface definitions of the various objects.
+     * Note that this is not a <i>full</i> topic, but merely the 'last part', such as "ADDED";
+     * this allows the ChangeNotifier to generate internal or external topics.
+     * @param props Properties to pack with the event. May be null.
+     */
+    public void notifyChanged(String topic, Properties props);
+
+    /**
+     * Notifies both internal and external listeners of a change to some object.
+     * @param topic A topic, as defined in the interface definitions of the various objects.
+     * Note that this is not a <i>full</i> topic, but merely the 'last part', such as "ADDED";
+     * this allows the ChangeNotifier to generate internal or external topics.
+     * @param props Properties to pack with the event. May be null.
+     * @param internalOnly Indicates this event is only for internal use, and the external
+     * events should not be sent.
+     */
+    public void notifyChanged(String topic, Properties props, boolean internalOnly);
+
+    /**
+     * Gets a topic name which allows subscription to all topics that this ChangeNotifier can send.
+     * @param publicTopic Indicates whether we are interested in the public (<code>true</code>) or the
+     * private topic (<code>false</code>).
+     * @return A topic name.
+     */
+    String getTopicAll(boolean publicTopic);
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.Dictionary;
+import java.util.Properties;
+
+import org.apache.ace.client.repository.SessionFactory;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * ChangeNotifierImpl provides a basic implementation of a ChangeNotifier, intended to be used
+ * by classes related to the RepositoryAdmin.<br>
+ * <br>
+ * Topics are built up in the following fashion:
+ * <ul>
+ * <li><b>...TopicRoot</b> All topics start with a TopicRoot, which is the same for all related classes, and ends with a "/".
+ * There can be internal and external topics, hence two TopicRoot parameters in the constructor.</li>
+ * <li><b>entityRoot</b> This is followed by a class-specific root, usually consisting of the classname with an added "/".</li>
+ * <li>Finally, for each call to <code>notifyChanged</code>, a topic can be specified, which is something like
+ * "CHANGED" or "ADDED".</li>
+ * </ul>
+ */
+public class ChangeNotifierImpl implements ChangeNotifier {
+
+    private final EventAdmin m_eventAdmin;
+    private final String m_privateTopicRoot;
+    private final String m_publicTopicRoot;
+    private final String m_entityRoot;
+    private final String m_sessionID;
+
+    /**
+     * Creates a new ChangeNotifierImpl.
+     * @param eventAdmin An EventAdmin to send events to.
+     * @param privateTopicRoot The root of all private topics; see TopicRoot in the description of this class.
+     * @param publicTopicRoot The root of all public topics; see TopicRoot in the description of this class.
+     * @param entityRoot A class-specific root for the class which will use this ChangeNotifierImpl.
+     */
+    ChangeNotifierImpl(EventAdmin eventAdmin, String privateTopicRoot, String publicTopicRoot, String entityRoot, String sessionID) {
+        m_eventAdmin = eventAdmin;
+        m_privateTopicRoot = privateTopicRoot;
+        m_publicTopicRoot = publicTopicRoot;
+        m_entityRoot = entityRoot;
+        m_sessionID = sessionID;
+    }
+
+    private Properties addSession(Properties props) {
+        if (props == null) {
+            props = new Properties();
+        }
+        props.put(SessionFactory.SERVICE_SID, m_sessionID);
+        return props;
+    }
+
+    public void notifyChanged(String topic, Properties props, boolean internalOnly) {
+        props = addSession(props);
+        m_eventAdmin.sendEvent(new Event(m_privateTopicRoot + m_entityRoot + topic,(Dictionary) props));
+        if (!internalOnly) {
+            m_eventAdmin.postEvent(new Event(m_publicTopicRoot + m_entityRoot + topic, (Dictionary) props));
+        }
+    }
+
+    public void notifyChanged(String topic, Properties props) {
+        notifyChanged(topic, props, false);
+    }
+
+    public String getTopicAll(boolean publicTopic) {
+        if (publicTopic) {
+            return m_publicTopicRoot + m_entityRoot + "*";
+        }
+        else {
+            return m_privateTopicRoot + m_entityRoot + "*";
+        }
+    }
+
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierManager.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierManager.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierManager.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ChangeNotifierManager.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * ChangeNotifierManager handles a number of ChangeNotifiers, so there is only
+ * one dependency on EventAdmin; this manager directs all calls from the ChangeNotifiers
+ * to the 'real' EventAdmin.
+ */
+public class ChangeNotifierManager implements EventAdmin {
+
+    private volatile EventAdmin m_eventAdmin; /* Will be injected by dependency manager */
+
+    /**
+     * Creates and configures a ChangeNotifier for use with the given topics.
+     * @param privateTopicRoot The root of all private topics; see TopicRoot in the description of {@link ChangeNotifierImpl}.
+     * @param publicTopicRoot The root of all public topics; see TopicRoot in the description of {@link ChangeNotifierImpl}.
+     * @param entityRoot A class-specific root for the class which will use this ChangeNotifierImpl.
+     * @return The newly configured ChangeNotifier.
+     */
+    public ChangeNotifier getConfiguredNotifier(String privateTopicRoot, String publicTopicRoot, String entityRoot, String sessionID) {
+        return new ChangeNotifierImpl(this, privateTopicRoot, publicTopicRoot, entityRoot, sessionID);
+    }
+
+    public void postEvent(Event event) {
+        m_eventAdmin.postEvent(event);
+    }
+
+    public void sendEvent(Event event) {
+        m_eventAdmin.sendEvent(event);
+    }
+
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentArtifactImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentArtifactImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentArtifactImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentArtifactImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ace.client.repository.object.DeploymentArtifact;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+
+/**
+ * This class is a basic implementation of DeploymentArtifact, with additional facilities
+ * for serializing this artifacts with its directives.
+ */
+public class DeploymentArtifactImpl implements DeploymentArtifact {
+    private final static String XML_NODE = "deploymentArtifact";
+    private final static String XML_NODE_URL = "url";
+    private final static String XML_NODE_DIRECTIVES = "directives";
+    private final String m_url;
+    private final Map<String, String> m_directives = new HashMap<String, String>();
+
+    /**
+     * Creates a new DeploymentArtifactImpl.
+     * @param url The url to the new artifact, as a string; should not be null;
+     */
+    public DeploymentArtifactImpl(String url) {
+        if (url == null) {
+            throw new IllegalArgumentException("The url should not be null.");
+        }
+        m_url = url;
+    }
+
+    /**
+     * Creates a new DeploymentArtifactImpl by deserializing it from an XML stream.
+     * @param reader A stream reader for the XML representation of this object.
+     */
+    public DeploymentArtifactImpl(HierarchicalStreamReader reader) {
+        reader.moveDown();
+        m_url = reader.getValue();
+        reader.moveUp();
+
+        reader.moveDown();
+        while (reader.hasMoreChildren()) {
+            reader.moveDown();
+            m_directives.put(reader.getNodeName(), reader.getValue());
+            reader.moveUp();
+        }
+        reader.moveUp();
+
+    }
+
+    /**
+     * Writes this object to an XML stream.
+     */
+    public void marshal(HierarchicalStreamWriter writer) {
+        writer.startNode(XML_NODE);
+
+        writer.startNode(XML_NODE_URL);
+        writer.setValue(m_url);
+        writer.endNode(); // url node
+
+        writer.startNode(XML_NODE_DIRECTIVES);
+        for (Map.Entry<String, String> entry : m_directives.entrySet()) {
+            writer.startNode(entry.getKey());
+            assert (entry.getValue() != null);
+            writer.setValue(entry.getValue());
+            writer.endNode(); // this directive entry
+        }
+        writer.endNode(); // directives node
+
+        writer.endNode(); // deploymentartifact node
+    }
+
+    /**
+     * Adds a directive to this object.
+     */
+    void addDirective(String key, String value) {
+        if ((key == null) || (value == null)) {
+            throw new IllegalArgumentException("Neither the key nor the value should be null.");
+        }
+        m_directives.put(key, value);
+    }
+
+    public String getDirective(String key) {
+        return m_directives.get(key);
+    }
+
+    public String[] getKeys() {
+        return m_directives.keySet().toArray(new String[m_directives.size()]);
+    }
+
+    public String getUrl() {
+        return m_url;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if ((other == null) || !getClass().equals(other.getClass())) {
+            return false;
+        }
+
+        DeploymentArtifactImpl dai = (DeploymentArtifactImpl) other;
+
+        boolean result = true;
+
+        result &= getUrl().equals(dai.getUrl());
+
+        result &= (getKeys().length == dai.getKeys().length);
+
+        for (String key : getKeys()) {
+            result &= getDirective(key).equals(dai.getDirective(key));
+        }
+
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = getUrl().hashCode();
+
+        for (String key : getKeys()) {
+            result ^= getDirective(key).hashCode();
+        }
+
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder(getUrl() + " [ ");
+        for (String key : getKeys()) {
+            result.append(key)
+            .append(": ")
+            .append(getDirective(key))
+            .append(" ");
+        }
+        result.append("]");
+
+        return result.toString();
+    }
+
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionObjectImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionObjectImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionObjectImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionObjectImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ace.client.repository.object.DeploymentArtifact;
+import org.apache.ace.client.repository.object.DeploymentVersionObject;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Implementation class for the DeploymentVersionObject. For 'what it does', see DeploymentVersionObject,
+ * for 'how it works', see RepositoryObjectImpl.
+ */
+public class DeploymentVersionObjectImpl extends RepositoryObjectImpl<DeploymentVersionObject> implements DeploymentVersionObject {
+    private final static String XML_NODE = "deploymentversion";
+    private final static String ARTIFACTS_XML_NODE = "artifacts";
+    private DeploymentArtifact[] m_deploymentArtifacts;
+
+    /**
+     * Creates a new <code>DeploymentVersionObjectImpl</code>.
+     * @param attributes A map of attributes; must include <code>KEY_TARGETID</code>, <code>KEY_VERSION</code>.
+     * @param deploymentArtifacts A (possibly empty) array of DeploymentArtifacts.
+     * @param notifier A change notifier to be used by this object.
+     */
+    DeploymentVersionObjectImpl(Map<String, String> attributes, ChangeNotifier notifier) {
+        super(checkAttributes(attributes, new String[] {KEY_TARGETID, KEY_VERSION}, new boolean[] {false, false}), notifier, XML_NODE);
+    }
+
+    DeploymentVersionObjectImpl(Map<String, String> attributes, Map<String, String> tags, ChangeNotifier notifier) {
+        super(checkAttributes(attributes, new String[] {KEY_TARGETID, KEY_VERSION}, new boolean[] {false, false}), tags, notifier, XML_NODE);
+    }
+
+    DeploymentVersionObjectImpl(HierarchicalStreamReader reader, ChangeNotifier notifier) {
+        super(reader, notifier, XML_NODE);
+    }
+
+    synchronized void setDeploymentArtifacts(DeploymentArtifact[] deploymentArtifacts) {
+        if (m_deploymentArtifacts != null) {
+            throw new IllegalStateException("Deployment artifacts are already set; this can only be done once.");
+        }
+        if (deploymentArtifacts == null) {
+            throw new IllegalArgumentException("The argument should not be null.");
+        }
+        else {
+            m_deploymentArtifacts = deploymentArtifacts;
+        }
+    }
+
+    @Override
+    protected void readCustom(HierarchicalStreamReader reader) {
+        List<DeploymentArtifact> result = new ArrayList<DeploymentArtifact>();
+        reader.moveDown();
+        while (reader.hasMoreChildren()) {
+            reader.moveDown();
+            DeploymentArtifactImpl deploymentArtifactImpl = new DeploymentArtifactImpl(reader);
+            result.add(deploymentArtifactImpl);
+            reader.moveUp();
+        }
+        setDeploymentArtifacts(result.toArray(new DeploymentArtifact[result.size()]));
+        reader.moveUp();
+    }
+
+    @Override
+    protected synchronized void writeCustom(HierarchicalStreamWriter writer) {
+        if (m_deploymentArtifacts == null) {
+            throw new IllegalStateException("This object is not fully initialized, so it cannot be serialized.");
+        }
+        writer.startNode(ARTIFACTS_XML_NODE);
+        for (DeploymentArtifact da : m_deploymentArtifacts) {
+            ((DeploymentArtifactImpl) da).marshal(writer);
+        }
+        writer.endNode();
+    }
+
+    private static String[] DEFINING_KEYS = new String[] {KEY_TARGETID, KEY_VERSION};
+    @Override
+    String[] getDefiningKeys() {
+        return DEFINING_KEYS;
+    }
+
+    public String getTargetID() {
+        return getAttribute(KEY_TARGETID);
+    }
+
+    public String getVersion() {
+        return getAttribute(KEY_VERSION);
+    }
+
+    public synchronized DeploymentArtifact[] getDeploymentArtifacts() {
+        if (m_deploymentArtifacts == null) {
+            throw new IllegalStateException("This object is not fully initialized yet.");
+        }
+        return m_deploymentArtifacts.clone();
+    }
+}
+

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionRepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionRepositoryImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionRepositoryImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/DeploymentVersionRepositoryImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ace.client.repository.RepositoryUtil;
+import org.apache.ace.client.repository.object.DeploymentArtifact;
+import org.apache.ace.client.repository.object.DeploymentVersionObject;
+import org.apache.ace.client.repository.repository.DeploymentVersionRepository;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.Version;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the TargetRepository. For 'what it does', see TargetRepository,
+ * for 'how it works', see ObjectRepositoryImpl.
+ * TODO: For now, this class reuses the functionality of ObjectRepositoryImpl. In the future, it
+ * might be useful to create a custom implementation for performance reasons.
+ */
+public class DeploymentVersionRepositoryImpl extends ObjectRepositoryImpl<DeploymentVersionObjectImpl, DeploymentVersionObject> implements DeploymentVersionRepository {
+    private final static String XML_NODE = "deploymentversions";
+
+    public DeploymentVersionRepositoryImpl(ChangeNotifier notifier) {
+        super(notifier, XML_NODE);
+    }
+
+    /*
+     * The mechanism below allows us to insert the artifacts that are passed to create
+     * into the newInhabitant, while still using the nice handling of ObjectRepositoryImpl.
+     */
+    private DeploymentArtifact[] m_tempDeploymentArtifacts;
+    private final Object m_creationLock = new Object();
+
+    @Override
+    DeploymentVersionObjectImpl createNewInhabitant(Map<String, String> attributes) {
+        synchronized (m_creationLock) {
+            DeploymentVersionObjectImpl result = new DeploymentVersionObjectImpl(attributes, this);
+            result.setDeploymentArtifacts(m_tempDeploymentArtifacts);
+            m_tempDeploymentArtifacts = null;
+            return result;
+        }
+    }
+
+    @Override
+    DeploymentVersionObjectImpl createNewInhabitant(Map<String, String> attributes, Map<String, String> tags) {
+        synchronized (m_creationLock) {
+            DeploymentVersionObjectImpl result = new DeploymentVersionObjectImpl(attributes, tags, this);
+            result.setDeploymentArtifacts(m_tempDeploymentArtifacts);
+            m_tempDeploymentArtifacts = null;
+            return result;
+        }
+    }
+
+    @Override
+    DeploymentVersionObjectImpl createNewInhabitant(HierarchicalStreamReader reader) {
+        return new DeploymentVersionObjectImpl(reader, this);
+    }
+
+    public DeploymentVersionObject create(Map<String, String> attributes, Map<String, String> tags, DeploymentArtifact[] artifacts) {
+        synchronized (m_creationLock) {
+            m_tempDeploymentArtifacts = artifacts;
+            return super.create(attributes, tags);
+        }
+    }
+
+    private Comparator<DeploymentVersionObject> versionComparator = new Comparator<DeploymentVersionObject>() {
+        public int compare(DeploymentVersionObject o1, DeploymentVersionObject o2) {
+            return Version.parseVersion(o1.getVersion()).compareTo(Version.parseVersion(o2.getVersion()));
+        }
+    };
+
+    public List<DeploymentVersionObject> getDeploymentVersions(String targetID) {
+        List<DeploymentVersionObject> result = null;
+            try {
+                result = get(createFilter("(" + DeploymentVersionObject.KEY_TARGETID + "=" + RepositoryUtil.escapeFilterValue(targetID) + ")"));
+                Collections.sort(result, versionComparator);
+            }
+            catch (InvalidSyntaxException e) {
+                // Too bad, probably an illegal targetID.
+                result = new ArrayList<DeploymentVersionObject>();
+            }
+        return result;
+    }
+
+    public DeploymentVersionObject getMostRecentDeploymentVersion(String targetID) {
+        List<DeploymentVersionObject> versions = getDeploymentVersions(targetID);
+        DeploymentVersionObject result = null;
+        if ((versions != null) && (versions.size() > 0)) {
+            result = versions.get(versions.size() - 1);
+        }
+        return result;
+    }
+
+    public DeploymentArtifact createDeploymentArtifact(String url, Map<String, String> directives) {
+        DeploymentArtifactImpl result =  new DeploymentArtifactImpl(url);
+        for (Map.Entry<String, String> entry : directives.entrySet()) {
+            result.addDirective(entry.getKey(), entry.getValue());
+        }
+        return result;
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Distribution2TargetAssociationImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Distribution2TargetAssociationImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Distribution2TargetAssociationImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Distribution2TargetAssociationImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.client.repository.impl;
+
+import java.util.Map;
+
+import org.apache.ace.client.repository.object.TargetObject;
+import org.apache.ace.client.repository.object.Distribution2TargetAssociation;
+import org.apache.ace.client.repository.object.DistributionObject;
+import org.osgi.framework.InvalidSyntaxException;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the Distribution2TargetAssociation. For 'what it does', see Distribution2TargetAssociation,
+ * for 'how it works', see AssociationImpl.
+ */
+public class Distribution2TargetAssociationImpl extends AssociationImpl<DistributionObject, TargetObject, Distribution2TargetAssociation> implements Distribution2TargetAssociation {
+    private final static String XML_NODE = "distribution2target";
+
+    public Distribution2TargetAssociationImpl(Map<String, String> attributes, ChangeNotifier notifier, DistributionRepositoryImpl distributionRepository, TargetRepositoryImpl targetRepository) throws InvalidSyntaxException {
+        super(attributes, notifier, DistributionObject.class, TargetObject.class, distributionRepository, targetRepository, XML_NODE);
+    }
+    public Distribution2TargetAssociationImpl(Map<String, String> attributes, Map<String, String> tags, ChangeNotifier notifier, DistributionRepositoryImpl distributionRepository, TargetRepositoryImpl targetRepository) throws InvalidSyntaxException {
+        super(attributes, tags, notifier, DistributionObject.class, TargetObject.class, distributionRepository, targetRepository, XML_NODE);
+    }
+    public Distribution2TargetAssociationImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, DistributionRepositoryImpl distributionRepository, TargetRepositoryImpl targetRepository) throws InvalidSyntaxException {
+        super(reader, notifier, DistributionObject.class, TargetObject.class, null, null, distributionRepository, targetRepository, XML_NODE);
+    }
+}