ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject svn commit: r1463576 [2/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...
Date Tue, 02 Apr 2013 14:53:35 GMT
Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/ArtifactPreprocessorBase.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,304 @@
+/*
+ * 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.helper.base;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.ace.client.repository.helper.ArtifactPreprocessor;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+
+/**
+ * This class can be used as a base class for artifact preprocessors. It comes with its own upload() method, which will
+ * be used by all artifact preprocessors anyway.
+ */
+public abstract class ArtifactPreprocessorBase implements ArtifactPreprocessor {
+
+    /** 64k buffers should be enough for everybody... */
+    protected static final int BUFFER_SIZE = 64 * 1024;
+
+    protected final ConnectionFactory m_connectionFactory;
+    private final ExecutorService m_executor;
+
+    /**
+     * Creates a new {@link ArtifactPreprocessorBase} instance.
+     * 
+     * @param connectionFactory
+     *            the connection factory to use, cannot be <code>null</code>.
+     */
+    protected ArtifactPreprocessorBase(ConnectionFactory connectionFactory) {
+        m_connectionFactory = connectionFactory;
+        m_executor = Executors.newCachedThreadPool();
+    }
+
+    /**
+     * Creates a new URL for given (file) name and OBR base URL.
+     * 
+     * @param name
+     *            the name of the file to create the URL for;
+     * @param obrBase
+     *            the OBR base URL to use.
+     * @return a new URL for the file, never <code>null</code>.
+     * @throws MalformedURLException
+     *             in case of invalid characters in the given name.
+     */
+    protected URL determineNewUrl(String name, URL obrBase) throws MalformedURLException {
+        return new URL(obrBase, name);
+    }
+
+    /**
+     * Silently closes the given {@link Closeable} instance.
+     * 
+     * @param closable
+     *            the closeable to close, may be <code>null</code>.
+     */
+    protected final void silentlyClose(Closeable closable) {
+        if (closable != null) {
+            try {
+                closable.close();
+            }
+            catch (IOException e) {
+                // Ignore; nothing we can/will do about here...
+            }
+        }
+    }
+
+    /**
+     * Gets a stream to write an artifact to, which will be uploaded asynchronously to the OBR.
+     * 
+     * @param name
+     *            The name of the artifact.
+     * @param obrBase
+     *            The base URL of the obr to which this artifact should be written.
+     * @param inputStream
+     *            the input stream with data to upload.
+     */
+    protected final Future<URL> uploadAsynchronously(final String name, final URL obrBase, final InputStream inputStream) {
+        return m_executor.submit(new Callable<URL>() {
+            public URL call() throws IOException {
+                return upload(inputStream, name, obrBase);
+            }
+        });
+    }
+
+    /**
+     * Converts a given URL to a {@link File} object.
+     * 
+     * @param url
+     *            the URL to convert, cannot be <code>null</code>.
+     * @return a {@link File} object, never <code>null</code>.
+     */
+    protected final File urlToFile(URL url) {
+        File file;
+        try {
+            file = new File(url.toURI());
+        }
+        catch (URISyntaxException e) {
+            file = new File(url.getPath());
+        }
+        return file;
+    }
+
+    /**
+     * Uploads an artifact synchronously to an OBR.
+     * 
+     * @param input
+     *            A inputstream from which the artifact can be read.
+     * @param name
+     *            The name of the artifact. If the name is not unique, an IOException will be thrown.
+     * @param obrBase
+     *            The base URL of the obr to which this artifact should be written.
+     * @return A URL to the uploaded artifact; this is identical to calling <code>determineNewUrl(name, obrBase)</code>
+     * @throws IOException
+     *             If there was an error reading from <code>input</code>, or if there was a problem communicating with
+     *             the OBR.
+     */
+    private URL upload(InputStream input, String name, URL obrBase) throws IOException {
+        if (obrBase == null) {
+            throw new IOException("There is no storage available for this artifact.");
+        }
+        if ((name == null) || (input == null)) {
+            throw new IllegalArgumentException("None of the parameters can be null.");
+        }
+
+        URL url = null;
+        try {
+            url = determineNewUrl(name, obrBase);
+
+            if (!urlPointsToExistingFile(url)) {
+                if ("file".equals(url.getProtocol())) {
+                    uploadToFile(input, url);
+                }
+                else {
+                    uploadToRemote(input, url);
+                }
+            }
+        }
+        catch (IOException ioe) {
+            throw new IOException("Error uploading " + name + ": " + ioe.getMessage());
+        }
+        finally {
+            silentlyClose(input);
+        }
+
+        return url;
+    }
+
+    /**
+     * Uploads an artifact to a local file location.
+     * 
+     * @param input
+     *            the input stream of the (local) artifact to upload.
+     * @param url
+     *            the URL of the (file) artifact to upload to.
+     * @throws IOException
+     *             in case of I/O problems.
+     */
+    private void uploadToFile(InputStream input, URL url) throws IOException {
+        File file = urlToFile(url);
+
+        OutputStream output = null;
+
+        try {
+            output = new FileOutputStream(file);
+
+            byte[] buffer = new byte[BUFFER_SIZE];
+            for (int count = input.read(buffer); count != -1; count = input.read(buffer)) {
+                output.write(buffer, 0, count);
+            }
+        }
+        finally {
+            silentlyClose(output);
+        }
+    }
+
+    /**
+     * Uploads an artifact to a remote location.
+     * 
+     * @param input
+     *            the input stream of the (local) artifact to upload.
+     * @param url
+     *            the URL of the (remote) artifact to upload to.
+     * @throws IOException
+     *             in case of I/O problems, or when the upload was refused by the remote.
+     */
+    private void uploadToRemote(InputStream input, URL url) throws IOException {
+        OutputStream output = null;
+
+        try {
+            URLConnection connection = m_connectionFactory.createConnection(url);
+            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);
+            }
+            connection.setDoOutput(true);
+
+            output = connection.getOutputStream();
+
+            byte[] buffer = new byte[BUFFER_SIZE];
+            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());
+                }
+            }
+        }
+        finally {
+            silentlyClose(output);
+        }
+    }
+
+    /**
+     * Determines whether the given URL points to an existing file.
+     * 
+     * @param url
+     *            the URL to test, cannot be <code>null</code>.
+     * @return <code>true</code> if the given URL points to an existing file, <code>false</code> otherwise.
+     */
+    private boolean urlPointsToExistingFile(URL url) {
+        boolean result = false;
+
+        if ("file".equals(url.getProtocol())) {
+            result = urlToFile(url).exists();
+        }
+        else {
+            try {
+                URLConnection connection = m_connectionFactory.createConnection(url);
+
+                if (connection instanceof HttpURLConnection) {
+                    HttpURLConnection hc = (HttpURLConnection) connection;
+
+                    // Perform a HEAD on the file, to see whether it exists...
+                    hc.setRequestMethod("HEAD");
+                    try {
+                        int responseCode = hc.getResponseCode();
+                        result = (responseCode == HttpURLConnection.HTTP_OK);
+                    }
+                    finally {
+                        hc.disconnect();
+                    }
+                }
+                else {
+                    // In all other scenario's: try to read a single byte from the input
+                    // stream, if this succeeds, we can assume the file exists...
+                    InputStream is = connection.getInputStream();
+                    try {
+                        is.read();
+                    }
+                    finally {
+                        silentlyClose(is);
+                    }
+                }
+            }
+            catch (IOException e) {
+                // Ignore; assume file does not exist...
+            }
+        }
+
+        return result;
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/VelocityArtifactPreprocessor.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,318 @@
+/*
+ * 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.helper.base;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.ace.client.repository.helper.PropertyResolver;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+
+/**
+ * This class can be used as a 'default' artifact preprocessor, using the Velocity template engine to preprocess
+ * the artifact.
+ */
+public class VelocityArtifactPreprocessor extends ArtifactPreprocessorBase {
+
+    private static Object m_initLock = new Object();
+    private static boolean m_velocityInitialized = false;
+
+    private final Map<String, Reference<byte[]>> m_cachedArtifacts;
+    private final Map<String, Reference<String>> m_cachedHashes;
+    private final MessageDigest m_md5;
+
+    /**
+     * Creates a new {@link VelocityArtifactPreprocessor} instance.
+     * @param connectionFactory 
+     */
+    public VelocityArtifactPreprocessor(ConnectionFactory connectionFactory) {
+        super(connectionFactory);
+
+        try {
+            m_md5 = MessageDigest.getInstance("MD5");
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Failed to create VelocityArtifactPreprocessor instance!", e);
+        }
+
+        m_cachedArtifacts = new ConcurrentHashMap<String, Reference<byte[]>>();
+        m_cachedHashes = new ConcurrentHashMap<String, Reference<String>>();
+    }
+
+    @Override
+    public boolean needsNewVersion(String url, PropertyResolver props, String targetID, String fromVersion) {
+        // get the template
+        byte[] input = null;
+        byte[] result = null;
+        
+        try {
+            init();
+            
+            input = getArtifactAsBytes(url);
+            result = process(input, props);
+        }
+        catch (IOException ioe) {
+            // problem initializing velocity, or we cannot retrieve the 
+            // original artifact, or process it; we can't say anything now.
+            return true;
+        }
+
+        // process the template
+        // first check: did we need any processing at all?
+        if (Arrays.equals(result, input)) {
+            return false;
+        }
+
+        // hash the processed template
+        String newHash = hash(result);
+
+        // find the hash for the previous version
+        String oldHash = getHashForVersion(url, targetID, fromVersion);
+
+        // Note: we do not cache any previously created processed templates, since the call that asks us to approve a new version
+        // may cross a pending needsNewVersion call.
+        return !newHash.equals(oldHash);
+    }
+
+    @Override
+    public String preprocess(String url, PropertyResolver props, String targetID, String version, URL obrBase) throws IOException {
+        init();
+
+        // first, get the original data.
+        byte[] input = getArtifactAsBytes(url);
+        // process the template
+        byte[] result = process(input, props);
+
+        // first check: did we need any processing at all?
+        if (Arrays.equals(result, input)) {
+            // template isn't modified; use direct URL instead...
+            return url;
+        }
+        
+        setHashForVersion(url, targetID, version, hash(result));
+        
+        String name = getFilename(url, targetID, version);
+        
+        uploadAsynchronously(name, obrBase, new ByteArrayInputStream(result));
+
+        return determineNewUrl(name, obrBase).toString();
+    }
+
+    /**
+     * Initializes this preprocessor by making sure {@link Velocity#init()} is called.
+     * <p>This method may be called multiple times.</p>
+     * 
+     * @throws IOException in case of problems initializing Velocity.
+     */
+    private void init() throws IOException {
+        if (m_velocityInitialized) {
+            return;
+        }
+        else {
+            synchronized (m_initLock) {
+                if (!m_velocityInitialized) {
+                    try {
+                        Velocity.init();
+                        m_velocityInitialized = true;
+                    }
+                    catch (Exception e) {
+                        // Something went seriously bad initializing velocity.
+                        throw new IOException("Error initializing Velocity: " + e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param url
+     * @param targetID
+     * @param version
+     * @return
+     */
+    private String getFilename(String url, String targetID, String version) {
+        int indexOfLastSlash = url.lastIndexOf('/');
+        if (indexOfLastSlash != -1) {
+            url = url.substring(indexOfLastSlash + 1);
+        }
+        return url + "-" + targetID + "-" + version;
+    }
+
+    /**
+     * @param url
+     * @param targetID
+     * @param version
+     * @return
+     */
+    private String getFullUrl(String url, String targetID, String version) {
+        return url + "-" + targetID + "-" + version;
+    }
+
+    /**
+     * @param url
+     * @param target
+     * @param version
+     * @return
+     */
+    private String getHashForVersion(String url, String target, String version) {
+        String key = createHashKey(url, target, version);
+
+        Reference<String> ref = m_cachedHashes.get(key);
+        String hash = (ref != null) ? ref.get() : null;
+        if (hash == null) {
+            try {
+                hash = hash(getBytesFromUrl(getFullUrl(url, target, version)));
+
+                m_cachedHashes.put(key, new WeakReference<String>(hash));
+            }
+            catch (IOException e) {
+                // we cannot retrieve the artifact, so we cannot say anything about it.
+            }
+        }
+
+        return hash;
+    }
+
+    /**
+     * @param url
+     * @param target
+     * @param version
+     * @param hash
+     */
+    private void setHashForVersion(String url, String target, String version, String hash) {
+        String key = createHashKey(url, target, version);
+
+        m_cachedHashes.put(key, new WeakReference<String>(hash));
+    }
+
+    /**
+     * Applies the template processor to the given byte array.
+     * 
+     * @param input the template (as byte array) to process;
+     * @param props the {@link PropertyResolver} to use.
+     * @return the processed template, never <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private byte[] process(byte[] input, PropertyResolver props) throws IOException {
+        VelocityContext context = new VelocityContext();
+        context.put("context", props);
+
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            Writer writer = new OutputStreamWriter(baos);
+            Velocity.evaluate(context, writer, "", new InputStreamReader(new ByteArrayInputStream(input)));
+            writer.flush();
+            return baos.toByteArray();
+        }
+        catch (IOException ioe) {
+            throw new IOException("Error processing the artifact: " + ioe.getMessage());
+        }
+    }
+
+    /**
+     * Reads all information from a given URL, and returns that as a byte array. The byte array is not to be changed, and could be potentially come from a cache.
+     * 
+     * @param url the URL to read the artifact from, cannot be <code>null</code>.
+     * @return the read (or cached) bytes, can be <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private byte[] getArtifactAsBytes(String url) throws IOException {
+        byte[] result = null;
+
+        Reference<byte[]> ref = m_cachedArtifacts.get(url);
+        if (ref == null || ((result = ref.get()) == null)) {
+            result = getBytesFromUrl(url);
+        }
+        return result;
+    }
+
+    /**
+     * Reads all bytes from the given URL and caches its result.
+     * 
+     * @param url the URL to read the bytes for, cannot be <code>null</code>.
+     * @return the read bytes from the given URL, can be <code>null</code> if the reading failed.
+     * @throws IOException in case of I/O problems.
+     */
+    private byte[] getBytesFromUrl(String url) throws IOException {
+        byte[] result = null;
+
+        // ACE-267
+        InputStream in = m_connectionFactory.createConnection(new URL(url)).getInputStream();
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            byte[] buf = new byte[BUFFER_SIZE];
+            for (int count = in.read(buf); count != -1; count = in.read(buf)) {
+                baos.write(buf, 0, count);
+            }
+            
+            result = baos.toByteArray();
+            
+            m_cachedArtifacts.put(url, new WeakReference<byte[]>(result));
+        }
+        finally {
+            silentlyClose(in);
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a key for storing/retrieving a hash.
+     * 
+     * @param url
+     * @param target
+     * @param version
+     * @return a hash key, never <code>null</code>.
+     */
+    private String createHashKey(String url, String target, String version) {
+        return new StringBuilder().append('[')
+            .append(url)
+            .append("][")
+            .append(target)
+            .append("][")
+            .append(version)
+            .append(']').toString();
+    }
+
+    /**
+     * Computes a hash for a given byte array.
+     * 
+     * @param input the byte array to compute the hash for.
+     * @return a hash for the given byte array, never <code>null</code>.
+     */
+    private String hash(byte[] input) {
+        return new String(m_md5.digest(input));
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/packageinfo?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/packageinfo (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/base/packageinfo Tue Apr  2 14:53:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

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

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/Activator.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/Activator.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/Activator.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,57 @@
+/*
+ * 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.helper.bundle.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.ace.client.repository.helper.ArtifactHelper;
+import org.apache.ace.client.repository.helper.ArtifactRecognizer;
+import org.apache.ace.client.repository.helper.bundle.BundleHelper;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Activator class for the Bundle ArtifactHelper.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        Dictionary<String, String> props = new Hashtable<String, String>();
+        props.put(ArtifactObject.KEY_MIMETYPE, BundleHelper.MIMETYPE);
+        BundleHelperImpl helperImpl = new BundleHelperImpl();
+        manager.add(createComponent()
+            .setInterface(ArtifactHelper.class.getName(), props)
+            .setImplementation(helperImpl));
+        manager.add(createComponent()
+            .setInterface(ArtifactRecognizer.class.getName(), null)
+            .setImplementation(helperImpl));
+        manager.add(createComponent()
+            .setInterface(BundleHelper.class.getName(), null)
+            .setImplementation(helperImpl));
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do
+    }
+}
\ No newline at end of file

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

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/VersionRange.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/VersionRange.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/VersionRange.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/impl/VersionRange.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,109 @@
+/* 
+ * 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.helper.bundle.impl;
+
+import org.osgi.framework.Version;
+
+class VersionRange
+{
+    private Version m_low = null;
+    private boolean m_isLowInclusive = false;
+    private Version m_high = null;
+    private boolean m_isHighInclusive = false;
+    private String m_toString = null;
+
+    public static final VersionRange infiniteRange = new VersionRange(
+        Version.emptyVersion, true, null, true);
+
+    public VersionRange(Version low, boolean isLowInclusive, Version high, boolean isHighInclusive) {
+        m_low = low;
+        m_isLowInclusive = isLowInclusive;
+        m_high = high;
+        m_isHighInclusive = isHighInclusive;
+    }
+
+    public Version getLow() {
+        return m_low;
+    }
+
+    public boolean isLowInclusive() {
+        return m_isLowInclusive;
+    }
+
+    public Version getHigh() {
+        return m_high;
+    }
+
+    public boolean isHighInclusive() {
+        return m_isHighInclusive;
+    }
+
+    public boolean isInRange(Version version) {
+        // We might not have an upper end to the range.
+        if (m_high == null) {
+            return (version.compareTo(m_low) >= 0);
+        }
+        else if (isLowInclusive() && isHighInclusive()) {
+            return (version.compareTo(m_low) >= 0) &&
+                (version.compareTo(m_high) <= 0);
+        }
+        else if (isHighInclusive()) {
+            return (version.compareTo(m_low) > 0) &&
+                (version.compareTo(m_high) <= 0);
+        }
+        else if (isLowInclusive()) {
+            return (version.compareTo(m_low) >= 0) &&
+                (version.compareTo(m_high) < 0);
+        }
+        return (version.compareTo(m_low) > 0) &&
+            (version.compareTo(m_high) < 0);
+    }
+
+    public static VersionRange parse(String range) {
+        // Check if the version is an interval.
+        if (range.indexOf(',') >= 0) {
+            String s = range.substring(1, range.length() - 1);
+            String vlo = s.substring(0, s.indexOf(',')).trim();
+            String vhi = s.substring(s.indexOf(',') + 1, s.length()).trim();
+            return new VersionRange(new Version(vlo), (range.charAt(0) == '['),
+                new Version(vhi), (range.charAt(range.length() - 1) == ']'));
+        }
+        else {
+            return new VersionRange(new Version(range), true, null, false);
+        }
+    }
+
+    public String toString() {
+        if (m_toString == null) {
+            if (m_high != null) {
+                StringBuffer sb = new StringBuffer();
+                sb.append(m_isLowInclusive ? '[' : '(');
+                sb.append(m_low.toString());
+                sb.append(',');
+                sb.append(m_high.toString());
+                sb.append(m_isHighInclusive ? ']' : ')');
+                m_toString = sb.toString();
+            }
+            else {
+                m_toString = m_low.toString();
+            }
+        }
+        return m_toString;
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/packageinfo?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/packageinfo (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/bundle/packageinfo Tue Apr  2 14:53:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/ConfigurationHelper.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/ConfigurationHelper.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/ConfigurationHelper.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/ConfigurationHelper.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,30 @@
+/*
+ * 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.helper.configuration;
+
+import org.apache.ace.client.repository.helper.ArtifactHelper;
+
+/**
+ * Definitions for ConfigurationHelper,used to treat an artifact as an AutoConf file.
+ */
+public interface ConfigurationHelper extends ArtifactHelper {
+    public static final String KEY_FILENAME = "filename";
+    public static final String MIMETYPE = "application/xml:osgi-autoconf";
+    public static final String PROCESSOR = "org.osgi.deployment.rp.autoconf";
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/Activator.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/Activator.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/Activator.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,57 @@
+/*
+ * 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.helper.configuration.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.ace.client.repository.helper.ArtifactHelper;
+import org.apache.ace.client.repository.helper.ArtifactRecognizer;
+import org.apache.ace.client.repository.helper.configuration.ConfigurationHelper;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Activator class for the Configuration ArtifactHelper.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        Dictionary<String, String> props = new Hashtable<String, String>();
+        props.put(ArtifactObject.KEY_MIMETYPE, ConfigurationHelper.MIMETYPE);
+
+        ConfigurationHelperImpl helperImpl = new ConfigurationHelperImpl();
+        manager.add(createComponent()
+            .setInterface(new String[] { ArtifactHelper.class.getName(), ArtifactRecognizer.class.getName() }, props)
+            .setImplementation(helperImpl)
+            .add(createServiceDependency()
+                .setService(ConnectionFactory.class)
+                .setRequired(true))
+            );
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/impl/ConfigurationHelperImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,189 @@
+/*
+ * 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.helper.configuration.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+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.base.VelocityArtifactPreprocessor;
+import org.apache.ace.client.repository.helper.configuration.ConfigurationHelper;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class ConfigurationHelperImpl implements ArtifactRecognizer, ConfigurationHelper {
+
+    // known valid metatype namespaces
+    private static final String NAMESPACE_1_0 = "http://www.osgi.org/xmlns/metatype/v1.0.0";
+    private static final String NAMESPACE_1_1 = "http://www.osgi.org/xmlns/metatype/v1.1.0";
+    private static final String NAMESPACE_1_2 = "http://www.osgi.org/xmlns/metatype/v1.2.0";
+
+    private final SAXParserFactory m_saxParserFactory;
+
+    // Injected by Dependency Manager
+    private volatile ConnectionFactory m_connectionFactory;
+    // Created in #start()
+    private volatile VelocityArtifactPreprocessor m_artifactPreprocessor;
+
+    public ConfigurationHelperImpl() {
+        m_saxParserFactory = SAXParserFactory.newInstance();
+        m_saxParserFactory.setNamespaceAware(false);
+        m_saxParserFactory.setValidating(false);
+    }
+    
+    public boolean canHandle(String mimetype) {
+        return MIMETYPE.equals(mimetype);
+    }
+
+    public Map<String, String> extractMetaData(ArtifactResource artifact) throws IllegalArgumentException {
+        Map<String, String> result = new HashMap<String, String>();
+        result.put(ArtifactObject.KEY_PROCESSOR_PID, PROCESSOR);
+        result.put(ArtifactObject.KEY_MIMETYPE, MIMETYPE);
+        String name = new File(artifact.getURL().getFile()).getName();
+        String key = KEY_FILENAME + "-";
+        int idx = name.indexOf(key);
+        if (idx > -1) {
+            int endIdx = name.indexOf("-", idx + key.length());
+            name = name.substring(idx + key.length(), (endIdx > -1) ? endIdx : (name.length() - getExtension(artifact).length()));
+        }
+        result.put(ArtifactObject.KEY_ARTIFACT_NAME, name);
+        result.put(KEY_FILENAME, name);
+        return result;
+    }
+
+    public String recognize(ArtifactResource artifact) {
+        MetaDataNamespaceCollector handler = new MetaDataNamespaceCollector();
+        InputStream input = null;
+        try {
+            input = artifact.openStream();
+            SAXParser parser = m_saxParserFactory.newSAXParser();
+            parser.parse(input, handler);
+        }
+        catch (Exception e) {
+            String namespace = handler.getMetaDataNamespace();
+            if (namespace != null
+                && (namespace.equals(NAMESPACE_1_0)
+                    || namespace.equals(NAMESPACE_1_1)
+                    || namespace.equals(NAMESPACE_1_2))) {
+                return MIMETYPE;
+            }
+        }
+        finally {
+            if (input != null) {
+                try {
+                    input.close();
+                }
+                catch (IOException e) {}
+            }
+        }
+        return null;
+    }
+
+    public boolean canUse(ArtifactObject object) {
+        return MIMETYPE.equals(object.getMimetype());
+    }
+
+    public Map<String, String> checkAttributes(Map<String, String> attributes) {
+        // All necessary checks will be done by the constructor using getMandatoryAttributes.
+        return attributes;
+    }
+
+    public <TYPE extends ArtifactObject> String getAssociationFilter(TYPE obj, Map<String, String> properties) {
+        return "(" + KEY_FILENAME + "=" + obj.getAttribute(KEY_FILENAME) + ")";
+    }
+
+    public <TYPE extends ArtifactObject> int getCardinality(TYPE obj, Map<String, String> properties) {
+        return Integer.MAX_VALUE;
+    }
+
+    public Comparator<ArtifactObject> getComparator() {
+        return null;
+    }
+
+    public String[] getDefiningKeys() {
+        return new String[] {KEY_FILENAME};
+    }
+
+    public String[] getMandatoryAttributes() {
+        return new String[] {KEY_FILENAME};
+    }
+
+    public ArtifactPreprocessor getPreprocessor() {
+        return m_artifactPreprocessor;
+    }
+    
+    public String getExtension(ArtifactResource artifact) {
+        return ".xml";
+    }
+
+    /**
+     * Called by dependency manager upon start of this component.
+     */
+    protected void start() {
+        m_artifactPreprocessor = new VelocityArtifactPreprocessor(m_connectionFactory);
+    }
+
+    /**
+     * Called by dependency manager upon stopping of this component.
+     */
+    protected void stop() {
+        m_artifactPreprocessor = null;
+        
+    }
+
+    static class MetaDataNamespaceCollector extends DefaultHandler {
+
+        private String m_metaDataNameSpace = "";
+
+        public String getMetaDataNamespace() {
+            return m_metaDataNameSpace;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+            throws SAXException {
+            if (qName.equals("MetaData") || qName.endsWith(":MetaData")) {
+                String nsAttributeQName = "xmlns";
+                if (qName.endsWith(":MetaData")) {
+                    nsAttributeQName = "xmlns" + ":" + qName.split(":")[0];
+                }
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    if (attributes.getQName(i).equals(nsAttributeQName)) {
+                        m_metaDataNameSpace = attributes.getValue(i);
+                    }
+                }
+            }
+            // first element is expected to have been the MetaData
+            // root so we can now terminate processing.
+            throw new SAXException("Done");
+        }
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/packageinfo?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/packageinfo (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/configuration/packageinfo Tue Apr  2 14:53:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/packageinfo?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/packageinfo (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/packageinfo Tue Apr  2 14:53:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/UserAdminHelper.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/UserAdminHelper.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/UserAdminHelper.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/UserAdminHelper.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,29 @@
+/*
+ * 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.helper.user;
+
+import org.apache.ace.client.repository.helper.ArtifactHelper;
+
+/**
+ * Definitions for the UserAdminHelper artifact helper.
+ */
+public interface UserAdminHelper extends ArtifactHelper {
+    public static final String MIMETYPE = "application/vnd.apache.ace.useradmin";
+    public static final String PROCESSOR = "org.apache.ace.resourceprocessor.useradmin";
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/Activator.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/Activator.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/Activator.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,58 @@
+/*
+ * 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.helper.user.impl;
+
+import java.util.Properties;
+
+import org.apache.ace.client.repository.helper.ArtifactHelper;
+import org.apache.ace.client.repository.helper.ArtifactRecognizer;
+import org.apache.ace.client.repository.helper.user.UserAdminHelper;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+/**
+ * Activator class for the UserAdmin ArtifactHelper.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_RANKING, 10);
+        props.put(ArtifactObject.KEY_MIMETYPE, UserAdminHelper.MIMETYPE);
+
+        UserHelperImpl helperImpl = new UserHelperImpl();
+        manager.add(createComponent()
+            .setInterface(new String[]{ ArtifactHelper.class.getName(), ArtifactRecognizer.class.getName() }, props)
+            .setImplementation(helperImpl)
+            .add(createServiceDependency()
+                .setService(ConnectionFactory.class)
+                .setRequired(true))
+            );
+    }
+
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/UserHelperImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/UserHelperImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/UserHelperImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/impl/UserHelperImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,139 @@
+/*
+ * 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.helper.user.impl;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+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.base.VelocityArtifactPreprocessor;
+import org.apache.ace.client.repository.helper.user.UserAdminHelper;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+public class UserHelperImpl implements ArtifactRecognizer, UserAdminHelper {
+
+    // Injected by Dependency Manager
+    private volatile ConnectionFactory m_connectionFactory;
+    // Created in #start()
+    private volatile VelocityArtifactPreprocessor m_artifactPreprocessor;
+
+    public boolean canHandle(String mimetype) {
+        return MIMETYPE.equals(mimetype);
+    }
+
+    public Map<String, String> extractMetaData(ArtifactResource artifact) throws IllegalArgumentException {
+        Map<String, String> result = new HashMap<String, String>();
+        result.put(ArtifactObject.KEY_PROCESSOR_PID, PROCESSOR);
+        result.put(ArtifactObject.KEY_MIMETYPE, MIMETYPE);
+        String name = new File(artifact.getURL().getFile()).getName();
+        String key = ArtifactObject.KEY_ARTIFACT_NAME + "-";
+        int idx = name.indexOf(key);
+        if (idx > -1) {
+            int endIdx = name.indexOf("-", idx + key.length());
+            name = name.substring(idx + key.length(), (endIdx > -1) ? endIdx : (name.length() - getExtension(artifact).length()));
+        }
+        result.put(ArtifactObject.KEY_ARTIFACT_NAME, name);
+        return result;
+    }
+
+    public String recognize(ArtifactResource artifact) {
+        try {
+            InputStream in = artifact.openStream();
+            
+            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+            Node root = doc.getFirstChild();
+            if (!root.getNodeName().equals("roles")) {
+                return null;
+            }
+            for (Node node = root.getFirstChild(); root != null; root = root.getNextSibling()) {
+                if (!node.getNodeName().equals("group") && !node.getNodeName().equals("user") && !node.getNodeName().equals("#text")) {
+                    return null;
+                }
+            }
+            return MIMETYPE;
+        }
+        catch (Exception e) {
+            // Does not matter.
+        }
+
+        return null;
+    }
+
+    public boolean canUse(ArtifactObject object) {
+        return MIMETYPE.equals(object.getMimetype());
+    }
+
+    public Map<String, String> checkAttributes(Map<String, String> attributes) {
+        // All necessary checks will be done by the constructor using getMandatoryAttributes.
+        return attributes;
+    }
+
+    public <TYPE extends ArtifactObject> String getAssociationFilter(TYPE obj, Map<String, String> properties) {
+        return "(" + ArtifactObject.KEY_ARTIFACT_NAME + "=" + obj.getAttribute(ArtifactObject.KEY_ARTIFACT_NAME) + ")";
+    }
+
+    public <TYPE extends ArtifactObject> int getCardinality(TYPE obj, Map<String, String> properties) {
+        return Integer.MAX_VALUE;
+    }
+
+    public Comparator<ArtifactObject> getComparator() {
+        return null;
+    }
+
+    public String[] getDefiningKeys() {
+        return new String[] {ArtifactObject.KEY_ARTIFACT_NAME};
+    }
+
+    public String[] getMandatoryAttributes() {
+        return new String[] {ArtifactObject.KEY_ARTIFACT_NAME};
+    }
+
+    public ArtifactPreprocessor getPreprocessor() {
+        return m_artifactPreprocessor;
+    }
+    
+    public String getExtension(ArtifactResource artifact) {
+        return ".xml";
+    }
+
+    /**
+     * Called by dependency manager upon start of this component.
+     */
+    protected void start() {
+        m_artifactPreprocessor = new VelocityArtifactPreprocessor(m_connectionFactory);
+    }
+
+    /**
+     * Called by dependency manager upon stopping of this component.
+     */
+    protected void stop() {
+        m_artifactPreprocessor = null;
+        
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/packageinfo?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/packageinfo (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/helper/user/packageinfo Tue Apr  2 14:53:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Activator.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Activator.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Activator.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,240 @@
+/*
+ * 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.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import org.apache.ace.client.repository.RepositoryAdmin;
+import org.apache.ace.client.repository.SessionFactory;
+import org.apache.ace.client.repository.helper.bundle.BundleHelper;
+import org.apache.ace.client.repository.object.Artifact2FeatureAssociation;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.client.repository.object.DeploymentVersionObject;
+import org.apache.ace.client.repository.object.Distribution2TargetAssociation;
+import org.apache.ace.client.repository.object.DistributionObject;
+import org.apache.ace.client.repository.object.Feature2DistributionAssociation;
+import org.apache.ace.client.repository.object.FeatureObject;
+import org.apache.ace.client.repository.object.TargetObject;
+import org.apache.ace.client.repository.repository.ArtifactRepository;
+import org.apache.ace.client.repository.repository.DeploymentVersionRepository;
+import org.apache.ace.client.repository.repository.TargetRepository;
+import org.apache.ace.client.repository.stateful.StatefulTargetRepository;
+import org.apache.ace.client.repository.stateful.impl.StatefulTargetRepositoryImpl;
+import org.apache.ace.server.log.store.LogStore;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.service.command.CommandProcessor;
+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;
+import org.osgi.service.prefs.PreferencesService;
+
+/**
+ * Activator for the RepositoryAdmin bundle. Creates the repository admin, which internally
+ * creates all required repositories.
+ */
+public class Activator extends DependencyActivatorBase implements SessionFactory {
+    
+    private final Map<String, SessionData> m_sessions = new HashMap<String, SessionData>();
+
+    private volatile DependencyManager m_dependencyManager;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void init(BundleContext context, DependencyManager manager) throws Exception {
+        m_dependencyManager = manager;
+        
+        Properties props = new Properties();
+        props.put(CommandProcessor.COMMAND_SCOPE, "clientrepo");
+        props.put(CommandProcessor.COMMAND_FUNCTION, new String[] { "sessions" });
+        
+        manager.add(createComponent()
+            .setInterface(SessionFactory.class.getName(), props)
+            .setImplementation(this)
+        );
+    }
+
+    /** Shell command to show the active sessions. */
+    public void sessions() {
+        synchronized (m_sessions) {
+        	if (m_sessions.isEmpty()) {
+        		System.out.println("No sessions.");
+        	}
+        	else {
+        		System.out.println("Sessions:");
+        		for (Entry<String, SessionData> session : m_sessions.entrySet()) {
+        			System.out.println(" * " + session.getKey());
+        		}
+        	}
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nop
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void createSession(String sessionID) {
+        SessionData sessionData = null;
+        synchronized (m_sessions) {
+            if (!m_sessions.containsKey(sessionID)) {
+                sessionData = new SessionData();
+                m_sessions.put(sessionID, sessionData);
+            }
+        }
+
+        // Allow session to be created outside the lock; to avoid potential deadlocks...
+        if (sessionData != null) {
+            createSessionServices(sessionData, sessionID);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroySession(String sessionID) {
+        SessionData sessionData = null;
+        synchronized (m_sessions) {
+            sessionData = m_sessions.remove(sessionID);
+        }
+
+        // Allow session to be destroyed outside the lock; to avoid potential deadlocks...
+        if ((sessionData != null) && !sessionData.isEmpty()) {
+            destroySessionServices(sessionData, sessionID);
+        }
+    }
+
+    /**
+     * Creates all necessary session-related service for the given session.
+     * 
+     * @param sd the session data to keep the session-related services;
+     * @param sessionID the session ID to use.
+     */
+    @SuppressWarnings("unchecked")
+    private void createSessionServices(SessionData sd, String sessionID) {
+        RepositoryAdminImpl rai = new RepositoryAdminImpl(sessionID);
+        Component comp1 = createComponent()
+            .setInterface(RepositoryAdmin.class.getName(), rai.getSessionProps())
+            .setImplementation(rai)
+            .setComposition("getInstances")
+            .add(createServiceDependency().setService(PreferencesService.class).setRequired(true))
+            .add(createServiceDependency().setService(EventAdmin.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false));
+
+        String sessionFilter = "(" + SessionFactory.SERVICE_SID + "=" + sessionID + ")";
+        String auditLogFilter = "(&(" + Constants.OBJECTCLASS + "=" + LogStore.class.getName() + ")(name=auditlog))";
+
+        Dictionary topic = new Hashtable();
+        topic.put(SessionFactory.SERVICE_SID, sessionID);
+        topic.put(EventConstants.EVENT_FILTER, sessionFilter);
+        topic.put(EventConstants.EVENT_TOPIC, new String[] {
+            ArtifactObject.TOPIC_ALL,
+            Artifact2FeatureAssociation.TOPIC_ALL,
+            FeatureObject.TOPIC_ALL,
+            Feature2DistributionAssociation.TOPIC_ALL,
+            DistributionObject.TOPIC_ALL,
+            Distribution2TargetAssociation.TOPIC_ALL,
+            TargetObject.TOPIC_ALL,
+            DeploymentVersionObject.TOPIC_ALL,
+            RepositoryAdmin.TOPIC_REFRESH, 
+            RepositoryAdmin.TOPIC_LOGIN 
+        });
+        
+        StatefulTargetRepositoryImpl statefulTargetRepositoryImpl = new StatefulTargetRepositoryImpl(sessionID);
+        Component comp2 = createComponent()
+            .setInterface(new String[] { StatefulTargetRepository.class.getName(), EventHandler.class.getName() }, topic)
+            .setImplementation(statefulTargetRepositoryImpl)
+            .add(createServiceDependency().setService(ArtifactRepository.class, sessionFilter).setRequired(true))
+            .add(createServiceDependency().setService(TargetRepository.class, sessionFilter).setRequired(true))
+            .add(createServiceDependency().setService(DeploymentVersionRepository.class, sessionFilter).setRequired(true))
+            .add(createServiceDependency().setService(LogStore.class, auditLogFilter).setRequired(false))
+            .add(createServiceDependency().setService(BundleHelper.class).setRequired(true))
+            .add(createServiceDependency().setService(EventAdmin.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false));
+
+        // Publish our components to our session data for later use...
+        sd.addComponents(m_dependencyManager, comp1, comp2);
+    }
+
+    /**
+     * Removes the session-related services from the session.
+     * 
+     * @param sd the session data that keeps the session-related services;
+     * @param sessionID the session ID to use.
+     */
+    private void destroySessionServices(SessionData sd, String sessionID) {
+        sd.removeAllComponents(m_dependencyManager);
+    }
+
+    /**
+     * Small container that keeps the session-related services for us.
+     */
+    private static final class SessionData {
+        private final List<Component> m_services = new ArrayList<Component>();
+
+        final void addComponents(DependencyManager manager, Component... comps) {
+            synchronized (m_services) {
+                for (Component c : comps) {
+                    m_services.add(c);
+                }
+            }
+
+            for (Component c : comps) {
+                manager.add(c);
+            }
+        }
+
+        final void removeAllComponents(DependencyManager manager) {
+            Component[] comps;
+            synchronized (m_services) {
+                comps = m_services.toArray(new Component[m_services.size()]);
+                m_services.clear();
+            }
+            
+            for (Component c : comps) {
+                manager.remove(c);
+            }
+        }
+        
+        final boolean isEmpty() {
+            synchronized (m_services) {
+                return m_services.isEmpty();
+            }
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationImpl.java?rev=1463576&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationImpl.java (added)
+++ ace/trunk/org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/Artifact2FeatureAssociationImpl.java Tue Apr  2 14:53:33 2013
@@ -0,0 +1,48 @@
+/*
+ * 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.osgi.framework.InvalidSyntaxException;
+
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+
+/**
+ * Implementation class for the Artifact2FeatureAssociation. For 'what it does', see Artifact2FeatureAssociation,
+ * for 'how it works', see AssociationImpl.
+ */
+public class Artifact2FeatureAssociationImpl extends AssociationImpl<ArtifactObject, FeatureObject, Artifact2FeatureAssociation> implements Artifact2FeatureAssociation {
+    private final static String XML_NODE = "artifact2feature";
+
+    public Artifact2FeatureAssociationImpl(Map<String, String> attributes, ChangeNotifier notifier, ArtifactRepositoryImpl artifactRepository, FeatureRepositoryImpl featureRepository) throws InvalidSyntaxException {
+        super(attributes, notifier, ArtifactObject.class, FeatureObject.class, artifactRepository, featureRepository, XML_NODE);
+    }
+
+    public Artifact2FeatureAssociationImpl(Map<String, String> attributes, Map<String, String> tags, ChangeNotifier notifier, ArtifactRepositoryImpl artifactRepository, FeatureRepositoryImpl featureRepository) throws InvalidSyntaxException {
+        super(attributes, tags, notifier, ArtifactObject.class, FeatureObject.class, artifactRepository, featureRepository, XML_NODE);
+    }
+
+    public Artifact2FeatureAssociationImpl(HierarchicalStreamReader reader, ChangeNotifier notifier, ArtifactRepositoryImpl artifactRepository, FeatureRepositoryImpl featureRepository) throws InvalidSyntaxException {
+        super(reader, notifier, ArtifactObject.class, FeatureObject.class, null, null, artifactRepository, featureRepository, XML_NODE);
+    }
+}



Mime
View raw message