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/09 10:12:36 UTC

svn commit: r1465922 [2/3] - in /ace/trunk: build/ org.apache.ace.authentication.itest/ org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/ org.apache.ace.client.repository.itest/ org.apache.ace.client.repository.itest/src/org/apa...

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,56 @@
+/*
+ * 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.repository;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ace.range.SortedRangeSet;
+
+/**
+ * The interface for replication of the data in a repository.
+ */
+public interface RepositoryReplication
+{
+    /**
+     * Determines the versions inside the repository.
+     * 
+     * @returns A <code>SortedRangeSet</code> representing all the versions currently inside the repository.
+     * @throws java.io.IOException If there is an error determining the current versions.
+     */
+    public SortedRangeSet getRange() throws IOException;
+
+    /**
+     * Gets the specified version.
+     * 
+     * @return A stream containing the specified version's data or <code>null</code> if the version does not exist.
+     * @throws java.io.IOException If there is an error reading the version.
+     * @throws IllegalArgumentException If the specified version is not greater than 0.
+     */
+    public InputStream get(long version) throws IOException, IllegalArgumentException;
+
+    /**
+     * Store the stream data as the specified version.
+     * 
+     * @return returns True if all went fine, false if the version already existed.
+     * @throws java.io.IOException If the stream data could not be stored successfully due to I/O problems.
+     * @throws IllegalArgumentException If the version number is not greater than 0.
+     */
+    public boolean put(InputStream data, long version) throws IOException, IllegalArgumentException;
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/BackupRepository.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/BackupRepository.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/BackupRepository.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/BackupRepository.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,72 @@
+/*
+ * 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.repository.ext;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Provides an interface for backing up objects. <code>write</code> and <code>read</code>
+ * allow writing and reading of the current version of the object. <code>backup</code>
+ * backs up the object, and <code>restore</code> restores it from a previously backed up
+ * version, if any. There is no way to directly use the backup.
+ */
+public interface BackupRepository
+{
+
+    /**
+     * Writes the input stream to the current object.
+     * @param data The data to be written. Remember to close this stream, if necessary.
+     * @throws java.io.IOException Will be thrown when (a) the input stream gets closed
+     * unexpectedly, or (b) there is an error writing the data.
+     */
+    public void write(InputStream data) throws IOException;
+
+    /**
+     * Reads the input stream from the current object. If there is no current version,
+     * an empty stream will be returned.
+     * @return An input stream, from which can be read. Remember to close it.
+     * @throws java.io.IOException Will be thrown when there is a problem storing the data.
+     */
+    public InputStream read() throws IOException;
+
+    /**
+     * Restores a previously backuped version of the object.
+     * @return True when there was a previously backup version which has
+     * now been restored, false otherwise.
+     * @throws java.io.IOException Thrown when the restore process goes bad.
+     */
+    public boolean restore() throws IOException;
+
+    /**
+     * Backs up the current version of the object, overwriting a previous
+     * backup, if any.
+     * @return True when there was a current version to be backed up, false
+     * otherwise.
+     * @throws java.io.IOException Thrown when the restore process goes bad.
+     */
+    public boolean backup() throws IOException;
+
+    /**
+     * Deletes the whole repository.
+     * 
+     * @throws IOException when the repository could not be deleted.
+     */
+    public void delete() throws IOException;
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/CachedRepository.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/CachedRepository.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/CachedRepository.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/CachedRepository.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,94 @@
+/*
+ * 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.repository.ext;
+
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.ace.repository.Repository;
+
+/**
+ * Provides a cached repository representation, allowing the storing of local changes, without
+ * committing them to the actual repository immediately.
+ */
+public interface CachedRepository extends Repository {
+
+    /**
+     * Checks our the most current version from the actual repository.
+     * @param fail Indicates that this method should throw an IOException when no data
+     * is available. Setting it to <code>false</code> will make it return an
+     * empty stream in that case.
+     * @return An input stream representing the checked out object.
+     * @throws java.io.IOException Is thrown when the actual repository's commit does.
+     */
+    public InputStream checkout(boolean fail) throws IOException;
+
+    /**
+     * Commits the most current version to the actual repository.
+     * @return true on success, false on failure (e.g. bad version number)
+     * @throws java.io.IOException Is thrown when the actual repository's commit does.
+     */
+    public boolean commit() throws IOException;
+
+    /**
+     * Gets the most recent version of the object. If no current version is available,
+     * and empty stream will be returned.
+     * @param fail Indicates that this method should throw an IOException when no data
+     * is available. Setting it to <code>false</code> will make it return an
+     * empty stream in that case.
+     * @return An input stream representing the most recently written object.
+     * @throws java.io.IOException Thrown when there is a problem retrieving the data.
+     */
+    public InputStream getLocal(boolean fail) throws IOException;
+
+    /**
+     * Writes the most recent version of the object.
+     * @throws java.io.IOException Thrown when there is a problem storing the data.
+     */
+    public void writeLocal(InputStream data) throws IOException;
+
+    /**
+     * Undoes all changes made using <code>writeLocal()</code> since the
+     * last <code>commit</code> or <code>checkout</code>.
+     * @throws java.io.IOException
+     */
+    public boolean revert() throws IOException;
+
+    /**
+     * Gets the most recent version of this repository, that is, the most recent version
+     * number that is either committed (successfully) or checked out.
+     * @return The most recent version of the underlying repository.
+     */
+    public long getMostRecentVersion();
+
+    /**
+     * Checks whether the version we have locally is current with respect to the version
+     * on the server.
+     * @return whether the version we have locally is current with respect to the version
+     * on the server.
+     * @throws java.io.IOException Thrown when an error occurs communicating with the server.
+     */
+    public boolean isCurrent() throws IOException;
+    
+    /**
+     * Deletes the local repository.
+     * 
+     * @throws IOException when the local repository could not be deleted
+     */
+    public void deleteLocal() throws IOException;
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,145 @@
+/*
+ * 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.repository.ext.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ace.range.RangeIterator;
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.ace.repository.Repository;
+import org.apache.ace.repository.ext.BackupRepository;
+import org.apache.ace.repository.ext.CachedRepository;
+
+/**
+ * Provides a CachedRepository, which uses either a <code>Repository</code> and a <code>BackupRepository</code>
+ * as remote and local storage, or a URL location and two files, from which it will create a <code>Repository</code>
+ *  and a <code>FileBasedBackupRepository</code>. Note that this class is not thread-safe, and should be synchronized
+ *  by the caller.
+ */
+public class CachedRepositoryImpl implements CachedRepository {
+    public static final long UNCOMMITTED_VERSION = -1;
+
+    private volatile long m_mostRecentVersion;
+
+    private final BackupRepository m_local;
+    private final Repository m_remote;
+
+    /**
+     * Creates a cached repository using.
+     * 
+     * @param remote A repository which holds committed versions.
+     * @param backup A backup repository for local changes.
+     * @param mostRecentVersion The version from which <code>backup</code> was checked out or committed.
+     * If no version has been committed yet, use <code>UNCOMMITTED_VERSION</code>.
+     */
+    public CachedRepositoryImpl(Repository remote, BackupRepository backup, long mostRecentVersion) {
+        m_remote = remote;
+        m_local = backup;
+        m_mostRecentVersion = mostRecentVersion;
+    }
+
+    public InputStream checkout(boolean fail) throws IOException, IllegalArgumentException {
+        m_mostRecentVersion = highestRemoteVersion();
+        if (m_mostRecentVersion == 0) {
+            // If there is no remote version, then simply return an empty stream.
+            if (fail) {
+                throw new IOException("No version has yet been checked in to the repository.");
+            }
+            else {
+                return new ByteArrayInputStream(new byte[0]);
+            }
+        }
+        return checkout(m_mostRecentVersion);
+    }
+
+    public InputStream checkout(long version) throws IOException, IllegalArgumentException {
+        m_local.write(m_remote.checkout(version));
+        m_local.backup();
+
+        m_mostRecentVersion = version;
+
+        return m_local.read();
+    }
+
+    public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException {
+        m_local.write(data);
+
+        return commit(fromVersion);
+    }
+
+    public boolean commit() throws IOException {
+        if (m_mostRecentVersion < 0) {
+            throw new IllegalStateException("A commit should be preceded by a checkout.");
+        }
+        return commit(m_mostRecentVersion++);
+    }
+
+    public boolean commit(long fromVersion) throws IOException, IllegalArgumentException {
+        boolean success = m_remote.commit(m_local.read(), fromVersion);
+        if (success) {
+            m_local.backup();
+        }
+
+        return success;
+    }
+
+    public SortedRangeSet getRange() throws IOException {
+        return m_remote.getRange();
+    }
+
+    public InputStream getLocal(boolean fail) throws IllegalArgumentException, IOException {
+        // ACE-240: only fail in case there is no local version available; when mostRecentVersion 
+        // equals to 0, it means that nothing has been committed locally...
+        if ((m_mostRecentVersion <= 0) && fail) {
+            throw new IOException("No local version available of " + m_local + ", remote " + m_remote);
+        }
+        return m_local.read();
+    }
+
+    public boolean revert() throws IOException {
+         return m_local.restore();
+    }
+
+    public void writeLocal(InputStream data) throws IllegalArgumentException, IOException {
+        m_local.write(data);
+    }
+
+    public long getMostRecentVersion() {
+        return m_mostRecentVersion;
+    }
+
+    public boolean isCurrent() throws IOException {
+        return highestRemoteVersion() == m_mostRecentVersion;
+    }
+    
+    public void deleteLocal() throws IOException {
+    	m_local.delete();
+    }
+
+    private long highestRemoteVersion() throws IOException {
+        long result = 0;
+        RangeIterator ri = getRange().iterator();
+        while (ri.hasNext()) {
+            result = ri.next();
+        }
+        return result;
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/FilebasedBackupRepository.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/FilebasedBackupRepository.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/FilebasedBackupRepository.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/FilebasedBackupRepository.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,163 @@
+/*
+ * 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.repository.ext.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.ace.repository.ext.BackupRepository;
+
+/**
+ * A file-based implementation of the Backup Repository, using two files to store the current
+ * and backup version.
+ */
+public class FilebasedBackupRepository implements BackupRepository {
+    private static final int COPY_BUFFER_SIZE = 4096;
+
+    private final File m_current;
+    private final File m_backup;
+
+    /**
+     * Creates a FilebasedBackupRepository. The file objects should point to a correct file,
+     * but the files will be created when necessary.
+     * @param current A file to store the current revision in.
+     * @param backup A file to store a backup version in.
+     */
+    public FilebasedBackupRepository(File current, File backup) {
+        m_current = current;
+        m_backup = backup;
+    }
+
+    public InputStream read() throws IOException {
+        if (!m_current.exists()) {
+            return new ByteArrayInputStream(new byte[0]);
+        }
+
+        try {
+            return new FileInputStream(m_current);
+        }
+        catch (FileNotFoundException e) {
+            throw new IOException("Unable to open file: " + e.getMessage(), e);
+        }
+    }
+
+    public void write(InputStream data) throws IOException {
+        try {
+            if (!m_current.exists()) {
+                m_current.createNewFile();
+            }
+        }
+        catch (IOException e) {
+            throw new IOException("Unable to create file: " + e.getMessage(), e);
+        }
+
+        try {
+            FileOutputStream out = new FileOutputStream(m_current);
+            copy(data, out);
+            out.close();
+        }
+        catch (FileNotFoundException e) {
+            throw new IOException("Unable to open file: " + e.getMessage(), e);
+        }
+    }
+
+    public boolean backup() throws IOException {
+        if (!m_current.exists()) {
+            return false;
+        }
+        copy(m_current, m_backup);
+        return true;
+    }
+
+    public boolean restore() throws IOException {
+        if (!m_backup.exists()) {
+            return false;
+        }
+        copy(m_backup, m_current);
+        return true;
+    }
+
+    public void delete() throws IOException {
+        boolean deletedCurrent = true, deletedBackup = true;
+        if (m_current.exists()) {
+            deletedCurrent = m_current.delete();
+        }
+        if (m_backup.exists()) {
+            deletedBackup = m_backup.delete();
+        }
+        if (!(deletedCurrent && deletedBackup)) {
+            throw new IOException("Could not delete: " + (deletedCurrent ? "" : "current ") + (deletedBackup ? "" : "backup"));
+        }
+    }
+
+    /**
+     * Helper function that writes the contents of one file to another.
+     * @param source The source file.
+     * @param destination The destination file.
+     * @throws java.io.IOException Thrown when file IO goes wrong.
+     */
+    private static void copy(File source, File destination) throws IOException {
+        if (destination.exists()) {
+            destination.delete();
+        }
+        destination.createNewFile();
+
+        FileOutputStream out = new FileOutputStream(destination);
+        FileInputStream in = new FileInputStream(source);
+
+        try {
+            copy(in, out);
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Copies the contents of an input stream to an output stream.
+     * @param in The input stream.
+     * @param out The output stream.
+     * @throws java.io.IOException Thrown when the output stream is closed unexpectedly.
+     */
+    private static void copy(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[COPY_BUFFER_SIZE];
+        int bytes = in.read(buffer);
+        while (bytes != -1) {
+            out.write(buffer, 0, bytes);
+            out.flush();
+            bytes = in.read(buffer);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "FilebasedBackupRepository[" + m_current + "," + m_backup + "]";
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,211 @@
+/*
+ * 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.repository.ext.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.ace.repository.Repository;
+
+/**
+ * This class works as a local interface for a remote repository by handling the network
+ * communication.
+ */
+public class RemoteRepository implements Repository {
+    private static final String COMMAND_QUERY = "/query";
+    private static final String COMMAND_CHECKOUT = "/checkout";
+    private static final String COMMAND_COMMIT = "/commit";
+    
+    private static final String MIME_APPLICATION_OCTET_STREAM = "application/octet-stream";
+    
+    private static final int COPY_BUFFER_SIZE = 64 * 1024;
+
+    private final URL m_url;
+    private final String m_customer;
+    private final String m_name;
+
+    private volatile ConnectionFactory m_connectionFactory;
+
+    /**
+     * Creates a remote repository that connects to a given location with a given customer- and repository name.
+     * 
+     * @param url The location of the repository.
+     * @param customer The customer name to use.
+     * @param name The repository name to use.
+     */
+    public RemoteRepository(URL url, String customer, String name) {
+        if (url == null || customer == null || name == null) {
+            throw new IllegalArgumentException("None of the parameters can be null!");
+        }
+
+        m_url = url;
+        m_customer = customer;
+        m_name = name;
+    }
+
+    public InputStream checkout(long version) throws IOException, IllegalArgumentException {
+        if (version <= 0) {
+            throw new IllegalArgumentException("Version must be greater than 0.");
+        }
+
+        URL url = buildCommand(m_url, COMMAND_CHECKOUT, version);
+        HttpURLConnection connection = (HttpURLConnection) m_connectionFactory.createConnection(url);
+
+        if (connection.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) {
+        	connection.disconnect();
+    		throw new IllegalArgumentException("Requested version not found in remote repository. (" + connection.getResponseMessage() + ")");
+        }
+        if (connection.getResponseCode() != HttpServletResponse.SC_OK) {
+        	connection.disconnect();
+            throw new IOException("Connection error: " + connection.getResponseMessage());
+        }
+
+        return connection.getInputStream();
+        
+    }
+
+    public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException {
+        URL url = buildCommand(m_url, COMMAND_COMMIT, fromVersion);
+        HttpURLConnection connection = (HttpURLConnection) m_connectionFactory.createConnection(url);
+        
+        // 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...
+        connection.setChunkedStreamingMode(8192);
+        connection.setRequestProperty("Content-Type", MIME_APPLICATION_OCTET_STREAM);
+        connection.setDoOutput(true);
+
+        OutputStream out = connection.getOutputStream();
+        try {
+        	copy(data, out);
+        } finally {
+        	out.flush();
+        	out.close();
+        }
+        
+        try {
+			return connection.getResponseCode() == HttpServletResponse.SC_OK;
+		} finally {
+			connection.disconnect();
+		}
+    }
+
+    public SortedRangeSet getRange() throws IOException {
+        URL url = buildCommand(m_url, COMMAND_QUERY, 0);
+        
+        HttpURLConnection connection = (HttpURLConnection) m_connectionFactory.createConnection(url);
+        
+        try {
+	        if (connection.getResponseCode() == HttpServletResponse.SC_OK) {
+	            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+	            try {
+		            String line = reader.readLine();
+		            if (line == null) {
+		                throw new IOException("Repository not found: customer=" + m_customer + ", name=" + m_name);
+		            }
+
+		            String representation = line.substring(line.lastIndexOf(','));
+		            return new SortedRangeSet(representation);
+	            } finally {
+	            	reader.close();
+	            }
+	        }
+	
+	        throw new IOException("Connection error: " + connection.getResponseMessage());
+        } finally {
+        	connection.disconnect();
+        }
+    }
+
+    /**
+     * Helper method which copies the contents of an input stream to an output stream.
+     * @param in The input stream.
+     * @param out The output stream.
+     * @throws java.io.IOException Thrown when one of the streams is closed unexpectedly.
+     */
+    private static void copy(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[COPY_BUFFER_SIZE];
+        int bytes = in.read(buffer);
+        while (bytes != -1) {
+            out.write(buffer, 0, bytes);
+            bytes = in.read(buffer);
+        }
+    }
+
+    /**
+     * Builds a command string to use in the request to the server, based on the parameters
+     * this object was created with. The version is only mandatory for <code>CHECKOUT</code>
+     * and <code>COMMIT</code>.
+     * 
+     * @param command A command string, use the <code>COMMAND_</code> constants in this file.
+     * @param version A version statement.
+     * @return The command string.
+     */
+    private URL buildCommand(URL url, String command, long version) {
+        StringBuilder params = new StringBuilder();
+
+        if (m_customer != null) {
+            if (params.length() != 0) {
+                params.append("&");
+            }
+            params.append("customer=").append(m_customer);
+        }
+        if (m_name != null) {
+            if (params.length() != 0) {
+                params.append("&");
+            }
+            params.append("name=").append(m_name);
+        }
+        if (command != COMMAND_QUERY) {
+            if (params.length() != 0) {
+                params.append("&");
+            }
+            params.append("version=").append(version);
+        }
+        
+        StringBuilder newURL = new StringBuilder();
+        newURL.append(url.toExternalForm());
+        newURL.append(command);
+        if (params.length() > 0) {
+        	newURL.append("?").append(params);
+        }
+
+        try {
+            return new URL(newURL.toString());
+        }
+        catch (MalformedURLException e) {
+            throw new IllegalArgumentException("Could not create URL: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "RemoteRepository[" + m_url + "," + m_customer + "," + m_name + "]";
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/packageinfo?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/packageinfo (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/packageinfo Tue Apr  9 08:12:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/packageinfo?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/packageinfo (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/packageinfo Tue Apr  9 08:12:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/Activator.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/Activator.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/Activator.java Tue Apr  9 08:12: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.repository.impl;
+
+import java.util.Properties;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.service.prefs.PreferencesService;
+
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, "org.apache.ace.server.repository.factory");
+        manager.add(createComponent()
+            .setInterface(ManagedServiceFactory.class.getName(), props)
+            .setImplementation(new RepositoryFactory(manager))
+            .add(createServiceDependency().setService(PreferencesService.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do here
+    }
+}

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,181 @@
+/*
+ * 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.repository.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ace.repository.Repository;
+import org.apache.ace.repository.RepositoryReplication;
+import org.apache.ace.repository.impl.constants.RepositoryConstants;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.osgi.service.prefs.PreferencesService;
+
+/**
+ * A <code>ManagedServiceFactory</code> responsible for creating a (<code>Replication</code>)<code>Repository</code>
+ * instance for each valid configuration that is received from the <code>ConfigurationAdmin</code>.
+ */
+public class RepositoryFactory implements ManagedServiceFactory {
+
+    private volatile LogService m_log;                     /* injected by dependency manager */
+    private volatile BundleContext m_context;              /* injected by dependency manager */
+    private volatile PreferencesService m_prefsService;    /* injected by dependency manager */
+
+    private File m_tempDir;
+    private File m_baseDir;
+    private Preferences m_prefs;
+    private final DependencyManager m_manager;
+    private final Map<String, Component> m_instances = new HashMap<String, Component>();
+
+    public RepositoryFactory(DependencyManager manager) {
+        m_manager = manager;
+    }
+
+    public synchronized void deleted(String pid) {
+        // pull service
+        Component service = m_instances.remove(pid);
+        if (service != null) {
+            m_manager.remove(service);
+        }
+
+        // remove persisted data
+        File dir = new File(m_baseDir, pid);
+        if (dir.isDirectory()) {
+            File[] files = dir.listFiles();
+            for (int i = 0; (files != null) && (i < files.length); i++) {
+                files[i].delete();
+            }
+            if (!dir.delete()) {
+                m_log.log(LogService.LOG_WARNING, "Unable to clean up files ( in " + dir.getAbsolutePath() + ") after removing repository");
+            }
+        }
+        try {
+            m_prefs.node(pid).removeNode();
+            m_prefs.sync();
+        }
+        catch (BackingStoreException e) {
+            // Not much we can do
+        }
+    }
+
+    public String getName() {
+        return "RepositoryFactory";
+    }
+
+    public synchronized void init() {
+        m_tempDir = m_context.getDataFile("tmp");
+        if ((m_tempDir != null) && !m_tempDir.isDirectory() && !m_tempDir.mkdirs()) {
+            throw new IllegalArgumentException("Unable to create temp directory (" + m_tempDir.getAbsolutePath() + ")");
+        }
+        m_baseDir = m_context.getDataFile("repos");
+        if ((m_baseDir != null) && !m_baseDir.isDirectory() && !m_baseDir.mkdirs()) {
+            throw new IllegalArgumentException("Unable to create base directory (" + m_baseDir.getAbsolutePath() + ")");
+        }
+    }
+
+    /**
+     * Creates a new instance if the supplied dictionary contains a valid configuration. A configuration is valid if
+     * <code>RepositoryConstants.REPOSITORY_NAME</code> and <code>RepositoryConstants.REPOSITORY_CUSTOMER</code> properties
+     * are present, not empty and the combination of the two is unique in respect to other previously created instances.
+     * Finally a property <code>RepositoryConstants.REPOSITORY_MASTER</code> should be present and be either <code>true</code>
+     * or <code>false</code>.
+     *
+     * @param pid A unique identifier for the instance, generated by <code>ConfigurationAdmin</code> normally.
+     * @param dict The configuration properties for the instance, see description above.
+     * @throws ConfigurationException If any of the above explanation fails <b>or</b>when there is an internal error creating the repository.
+     */
+    @SuppressWarnings("unchecked")
+    public synchronized void updated(String pid, Dictionary dict) throws ConfigurationException {
+        String name = (String) dict.get(RepositoryConstants.REPOSITORY_NAME);
+        if ((name == null) || "".equals(name)) {
+            throw new ConfigurationException(RepositoryConstants.REPOSITORY_NAME, "Repository name has to be specified.");
+        }
+
+        String customer = (String) dict.get(RepositoryConstants.REPOSITORY_CUSTOMER);
+        if ((customer == null) || "".equals(customer)) {
+            throw new ConfigurationException(RepositoryConstants.REPOSITORY_CUSTOMER, "Repository customer has to be specified.");
+        }
+
+        String master = (String) dict.get(RepositoryConstants.REPOSITORY_MASTER);
+        if (!("false".equalsIgnoreCase(master.trim()) || "true".equalsIgnoreCase(master.trim()))) {
+            throw new ConfigurationException(RepositoryConstants.REPOSITORY_MASTER, "Have to specify whether the repository is the master or a slave.");
+        }
+        boolean isMaster = Boolean.parseBoolean(master);
+
+        String initialContents = (String) dict.get(RepositoryConstants.REPOSITORY_INITIAL_CONTENT);
+
+        if (m_prefs == null) {
+            m_prefs = m_prefsService.getSystemPreferences();
+        }
+
+        String[] nodes;
+        try {
+            nodes = m_prefs.childrenNames();
+        }
+        catch (BackingStoreException e) {
+            throw new ConfigurationException("none", "Internal error while validating configuration.");
+        }
+        for (int i = 0; i < nodes.length; i++) {
+            Preferences node = m_prefs.node(nodes[i]);
+            if (name.equalsIgnoreCase(node.get(RepositoryConstants.REPOSITORY_NAME, "")) && name.equalsIgnoreCase(node.get(RepositoryConstants.REPOSITORY_CUSTOMER, ""))) {
+                throw new ConfigurationException("name and customer", "Name and customer combination already exists");
+            }
+        }
+
+        Preferences node = m_prefs.node(pid);
+        node.put(RepositoryConstants.REPOSITORY_NAME, name);
+        node.put(RepositoryConstants.REPOSITORY_CUSTOMER, customer);
+
+        Component service = m_instances.get(pid);
+        if (service == null) {
+            // new instance
+            File dir = new File(m_baseDir, pid);
+            RepositoryImpl store = new RepositoryImpl(dir, m_tempDir, isMaster);
+            if ((initialContents != null) && isMaster) {
+                try {
+                    store.commit(new ByteArrayInputStream(initialContents.getBytes()), 0);
+                }
+                catch (IOException e) {
+                    m_log.log(LogService.LOG_ERROR, "Unable to set initial contents of the repository.", e);
+                }
+            }
+            service = m_manager.createComponent()
+                .setInterface(new String[] {RepositoryReplication.class.getName(), Repository.class.getName()}, dict)
+                .setImplementation(store)
+                .add(m_manager.createServiceDependency().setService(LogService.class).setRequired(false));
+            m_manager.add(service);
+            m_instances.put(pid, service);
+        } else {
+            // update existing instance
+            RepositoryImpl store = (RepositoryImpl) service.getService();
+            store.updated(isMaster);
+        }
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,296 @@
+/*
+ * 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.repository.impl;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.ace.repository.Repository;
+import org.apache.ace.repository.RepositoryReplication;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Implementation of an object repository. The object repository holds (big) chunks of data identified by
+ * a version. To interact with the repository two interfaces are implemented:
+ * <ul>
+ *   <li><code>Repository</code> - a read-write interface to the repository, you can commit and checkout versions</li>
+ *   <li><code>RepositoryReplication</code> - interface used only for replication of the repository, you can get and put versions</li>
+ * </ul>
+ * A repository can be either a master or a slave repository. Committing a new version is only possible on a master repository.
+ */
+public class RepositoryImpl implements RepositoryReplication, Repository {
+
+    private volatile LogService m_log; /* will be injected by dependency manager */
+    private volatile boolean m_isMaster;
+
+    private final File m_tempDir;
+    private final File m_dir;
+
+    /**
+     * Creates a new repository.
+     *
+     * @param dir Directory to be used for storage of the repository data, will be created if needed.
+     * @param temp Directory to be used as temp directory, will be created if needed.
+     * @param isMaster True if this repository is a master repository, false otherwise.
+     * @throws IllegalArgumentException If <code>dir</code> and/or <code>temp</code> could not be created or is not a directory.
+     */
+    public RepositoryImpl(File dir, File temp, boolean isMaster) {
+        m_isMaster = isMaster;
+        if (!dir.isDirectory() && !dir.mkdirs()) {
+            throw new IllegalArgumentException("Repository location is not a valid directory (" + dir.getAbsolutePath() + ")");
+        }
+        if (!temp.isDirectory() && !temp.mkdirs()) {
+            throw new IllegalArgumentException("Temp location is not a valid directory (" + temp.getAbsolutePath() + ")");
+        }
+        m_tempDir = temp;
+        m_dir = dir;
+    }
+
+    public InputStream get(long version) throws IOException, IllegalArgumentException {
+        return checkout(version);
+    }
+
+    public boolean put(InputStream data, long version) throws IOException, IllegalArgumentException {
+        if (version <= 0) {
+            throw new IllegalArgumentException("Version must be greater than 0.");
+        }
+        File file = new File(m_dir, Long.toString(version));
+        if (file.exists()) {
+            return false;
+        }
+
+        // store stream in temp file
+        File tempFile = File.createTempFile("repository", null, m_tempDir);
+        OutputStream fileStream = null;
+        try {
+            fileStream = new FileOutputStream(tempFile);
+            
+            byte[] buffer = new byte[1024];
+            int bytes;
+            while ((bytes = data.read(buffer)) >= 0) {
+                fileStream.write(buffer, 0, bytes);
+            }
+        }
+        catch (IOException e) {
+            String deleteMsg = "";
+            if (!tempFile.delete()) {
+                deleteMsg = " and was unable to remove temp file " + tempFile.getAbsolutePath();
+            }
+            m_log.log(LogService.LOG_WARNING, "Error occurred while storing new version in repository" + deleteMsg, e);
+            throw e;
+        }
+        finally {
+            if (fileStream != null) {
+                try {
+                    fileStream.close();
+                }
+                catch (IOException ioe) {
+                    // Not much we can do
+                }
+            }
+        }
+
+        // move temp file to final location
+        renameFile(tempFile, file);
+
+        return true;
+    }
+
+    public InputStream checkout(long version) throws IOException, IllegalArgumentException {
+        if (version <= 0) {
+            throw new IllegalArgumentException("Version must be greater than 0.");
+        }
+        File file = new File(m_dir, String.valueOf(version));
+        return (file.isFile()) ? new FileInputStream(file) : null;
+    }
+
+    public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException {
+        if (!m_isMaster) {
+            throw new IllegalStateException("Commit is only permitted on master repositories");
+        }
+        if (fromVersion < 0) {
+            throw new IllegalArgumentException("Version must be greater than or equal to 0.");
+        }
+
+        long[] versions = getVersions();
+
+        if (versions.length == 0) {
+            if (fromVersion == 0) {
+                put(data, 1);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        long lastVersion = versions[versions.length - 1];
+        if (lastVersion == fromVersion) {
+            put(data, fromVersion + 1);
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    public SortedRangeSet getRange() throws IOException {
+        return new SortedRangeSet(getVersions());
+    }
+
+    /* returns list of all versions present in ascending order */
+    private long[] getVersions() throws IOException {
+        File[] versions = m_dir.listFiles();
+        if (versions == null) {
+            throw new IOException("Unable to list version in the store (failed to get the filelist for directory '" + m_dir.getAbsolutePath() + "'");
+        }
+        long[] results = new long[versions.length];
+        for (int i = 0; i < versions.length; i++) {
+            String name = versions[i].getName();
+			try {
+                results[i] = Long.parseLong(name);
+            }
+            catch (NumberFormatException nfe) {
+                m_log.log(LogService.LOG_WARNING, "Unable to determine version number for '" + name + "', skipping it.");
+            }
+        }
+        Arrays.sort(results);
+        return results;
+    }
+
+    /**
+     * Updates the repository configuration.
+     *
+     * @param isMaster True if the repository is a master repository, false otherwise.
+     *
+     * @throws ConfigurationException If it was impossible to use the new configuration.
+     */
+    public void updated(boolean isMaster) throws ConfigurationException {
+        m_isMaster = isMaster;
+    }
+
+    /**
+     * Renames a given source file to a new destination file.
+     * <p>
+     * This avoids the problem mentioned in ACE-155.<br/>
+     * The moveFile method from Commons-IO is not used, as it would mean that
+     * we need to include this JAR in several placed for only a few lines of
+     * code.
+     * </p>
+     * 
+     * @param source the file to rename;
+     * @param dest the file to rename to.
+     */
+    private void renameFile(File source, File dest) throws IOException {
+        boolean renameOK = false;
+        int attempts = 0;
+        while (!renameOK && (attempts++ < 10)) {
+            try {
+                renameOK = source.renameTo(dest);
+                if (!renameOK) {
+                    renameOK = moveFile(source, dest);
+                }
+            }
+            catch (IOException e) {
+                // In all other cases, we assume the source file is still locked and cannot be removed;
+            }
+        }
+
+        if (!renameOK) {
+            if (m_log != null) {
+                m_log.log(LogService.LOG_ERROR, "Unable to move new repository file to it's final location.");
+            }
+            throw new IOException("Could not move temporary file (" + source.getAbsolutePath() + ") to it's final location (" + dest.getAbsolutePath() + ")");
+        }
+    }
+
+    /**
+     * Moves a given source file to a destination location, effectively resulting in a rename.
+     * 
+     * @param source the source file to move;
+     * @param dest the destination file to move the file to.
+     * @return <code>true</code> if the move succeeded.
+     * @throws IOException in case of I/O problems.
+     */
+    private boolean moveFile(File source, File dest) throws IOException {
+        final int bufferSize = 1024 * 1024; // 1MB
+
+        FileInputStream fis = null;
+        FileOutputStream fos = null;
+        FileChannel input = null;
+        FileChannel output = null;
+        
+        try {
+            fis = new FileInputStream(source);
+            input = fis.getChannel();
+
+            fos = new FileOutputStream(dest);
+            output = fos.getChannel();
+
+            long size = input.size();
+            long pos = 0;
+            while (pos < size) {
+                pos += output.transferFrom(input, pos, Math.min(size - pos, bufferSize));
+            }
+        }
+        finally {
+            closeQuietly(fos);
+            closeQuietly(fis);
+            closeQuietly(output);
+            closeQuietly(input);
+        }
+
+        if (source.length() != dest.length()) {
+            throw new IOException("Failed to move file! Not all contents from '" + source + "' copied to '" + dest + "'!");
+        }
+
+        dest.setLastModified(source.lastModified());
+
+        if (!source.delete()) {
+            dest.delete();
+            throw new IOException("Failed to move file! Source file (" + source + ") locked?");
+        }
+
+        return true;
+    }
+
+    /**
+     * Safely closes a given resource, ignoring any I/O exceptions that might occur by this.
+     * 
+     * @param resource the resource to close, can be <code>null</code>.
+     */
+    private void closeQuietly(Closeable resource) {
+        try {
+            if (resource != null) {
+                resource.close();
+            }
+        }
+        catch (IOException e) {
+            // Ignored...
+        }
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,27 @@
+/*
+ * 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.repository.impl.constants;
+
+public interface RepositoryConstants
+{
+    public static final String REPOSITORY_NAME = "name";
+    public static final String REPOSITORY_CUSTOMER = "customer";
+    public static final String REPOSITORY_MASTER = "master";
+    public static final String REPOSITORY_INITIAL_CONTENT = "initial";
+}
\ No newline at end of file

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

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/Activator.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/Activator.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/Activator.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,59 @@
+/*
+ * 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.repository.servlet;
+
+import javax.servlet.Servlet;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+    public static final String REPOSITORY_PID = "org.apache.ace.repository.servlet.RepositoryServlet";
+    public static final String REPOSITORY_REPLICATION_PID = "org.apache.ace.repository.servlet.RepositoryReplicationServlet";
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createComponent()
+            .setInterface(Servlet.class.getName(), null)
+            .setImplementation(RepositoryServlet.class)
+            .add(createConfigurationDependency()
+                .setPropagate(true)
+                .setPid(REPOSITORY_PID))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
+
+        manager.add(createComponent()
+            .setInterface(Servlet.class.getName(), null)
+            .setImplementation(RepositoryReplicationServlet.class)
+            .add(createConfigurationDependency()
+                .setPropagate(true)
+                .setPid(REPOSITORY_REPLICATION_PID))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // do nothing
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryReplicationServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryReplicationServlet.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryReplicationServlet.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryReplicationServlet.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,82 @@
+/*
+ * 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.repository.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.ace.repository.RepositoryReplication;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationException;
+
+public class RepositoryReplicationServlet extends RepositoryServletBase {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    protected ServiceReference[] getRepositories(String filter) throws InvalidSyntaxException {
+        return m_context.getServiceReferences(RepositoryReplication.class.getName(), filter);
+    }
+
+    @Override
+    protected SortedRangeSet getRange(ServiceReference ref) throws IOException {
+        RepositoryReplication repository = (RepositoryReplication) m_context.getService(ref);
+        SortedRangeSet range = repository.getRange();
+        m_context.ungetService(ref);
+        return range;
+    }
+
+    @Override
+    protected boolean doCommit(ServiceReference ref, long version, InputStream data) throws IllegalArgumentException, IOException {
+        RepositoryReplication r = (RepositoryReplication) m_context.getService(ref);
+        boolean result = r.put(data, version);
+        m_context.ungetService(ref);
+        return result;
+    }
+
+    @Override
+    protected InputStream doCheckout(ServiceReference ref, long version) throws IllegalArgumentException, IOException {
+        RepositoryReplication r = (RepositoryReplication) m_context.getService(ref);
+        InputStream result = r.get(version);
+        m_context.ungetService(ref);
+        return result;
+    }
+
+    @Override
+    public String getServletInfo() {
+        return "Apache ACE Repository Replication Servlet";
+    }
+
+    @Override
+    public void updated(Dictionary settings) throws ConfigurationException {
+        super.updated(settings);
+    }
+
+    @Override
+    protected String getCheckoutCommand() {
+        return "/get";
+    }
+
+    @Override
+    protected String getCommitCommand() {
+        return "/put";
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServlet.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServlet.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServlet.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,82 @@
+/*
+ * 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.repository.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.ace.repository.Repository;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationException;
+
+public class RepositoryServlet extends RepositoryServletBase {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    protected ServiceReference[] getRepositories(String filter) throws InvalidSyntaxException {
+        return m_context.getServiceReferences(Repository.class.getName(), filter);
+    }
+
+    @Override
+    protected SortedRangeSet getRange(ServiceReference ref) throws IOException {
+        Repository repository = (Repository) m_context.getService(ref);
+        SortedRangeSet range = repository.getRange();
+        m_context.ungetService(ref);
+        return range;
+    }
+
+    @Override
+    protected boolean doCommit(ServiceReference ref, long version, InputStream data) throws IllegalArgumentException, IOException {
+        Repository r = (Repository) m_context.getService(ref);
+        boolean result = r.commit(data, version);
+        m_context.ungetService(ref);
+        return result;
+    }
+
+    @Override
+    protected InputStream doCheckout(ServiceReference ref, long version) throws IllegalArgumentException, IOException {
+        Repository r = (Repository) m_context.getService(ref);
+        InputStream result = r.checkout(version);
+        m_context.ungetService(ref);
+        return result;
+    }
+
+    @Override
+    public String getServletInfo() {
+        return "Apache ACE Repository Servlet";
+    }
+
+    @Override
+    public void updated(Dictionary settings) throws ConfigurationException {
+        super.updated(settings);
+    }
+
+    @Override
+    protected String getCheckoutCommand() {
+        return "/checkout";
+    }
+
+    @Override
+    protected String getCommitCommand() {
+        return "/commit";
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,348 @@
+/*
+ * 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.repository.servlet;
+
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Dictionary;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.ace.authentication.api.AuthenticationService;
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Base class for the repository servlets. Both the repository and the repository replication
+ * servlets work in a similar way, so the specifics were factored out of this base class and
+ * put in two subclasses.
+ */
+public abstract class RepositoryServletBase extends HttpServlet implements ManagedService {
+
+    /** A boolean denoting whether or not authentication is enabled. */
+    private static final String KEY_USE_AUTHENTICATION = "authentication.enabled";
+
+    private static final int COPY_BUFFER_SIZE = 1024;
+    
+    private static final String QUERY = "/query";
+    
+    protected static final String TEXT_MIMETYPE = "text/plain";
+    protected static final String BINARY_MIMETYPE = "application/octet-stream";
+
+    // injected by Dependency Manager
+    private volatile DependencyManager m_dm; 
+    private volatile AuthenticationService m_authService;
+
+    private volatile boolean m_useAuth = false;
+    
+    protected volatile BundleContext m_context;
+    protected volatile LogService m_log;
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        String path = request.getPathInfo();
+        String customer = request.getParameter("customer");
+        String name = request.getParameter("name");
+        String filter = request.getParameter("filter");
+        String version = request.getParameter("version");
+
+        if (QUERY.equals(path)) {
+            // both repositories have a query method
+            if (filter != null) {
+                if ((name == null) && (customer == null)) {
+                    handleQuery(filter, response);
+                }
+                else {
+                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Specify either a filter or customer and/or name, but not both.");
+                }
+            }
+            else {
+                if ((name != null) && (customer != null)) {
+                    handleQuery("(&(customer=" + customer + ")(name=" + name + "))", response);
+                }
+                else if (name != null) {
+                    handleQuery("(name=" + name + ")", response);
+                }
+                else if (customer != null) {
+                    handleQuery("(customer=" + customer + ")", response);
+                }
+                else {
+                    handleQuery(null, response);
+                }
+            }
+        }
+        else if (getCheckoutCommand().equals(path)) {
+            // and both have a checkout, only it's named differently
+            if ((name != null) && (customer != null) && (version != null)) {
+                handleCheckout(customer, name, Long.parseLong(version), response);
+            }
+        }
+        else {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+
+    /**
+     * Returns the name of the "checkout" command.
+     */
+    protected abstract String getCheckoutCommand();
+
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        String path = request.getPathInfo();
+        String customer = request.getParameter("customer");
+        String name = request.getParameter("name");
+        String version = request.getParameter("version");
+
+        if (getCommitCommand().equals(path)) {
+            // and finally, both have a commit, only it's named differently
+            if ((name != null) && (customer != null) && (version != null)) {
+                handleCommit(customer, name, Long.parseLong(version), request.getInputStream(), response);
+            }
+            else {
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Name, customer and version should all be specified.");
+            }
+        }
+        else {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+
+    /**
+     * Returns the name of the "commit" command.
+     */
+    protected abstract String getCommitCommand();
+
+    /**
+     * Handles a query command and sends back the response.
+     */
+    private void handleQuery(String filter, HttpServletResponse response) throws IOException {
+        try {
+            ServiceReference[] refs = getRepositories(filter);
+            StringBuffer result = new StringBuffer();
+            if (refs != null) {
+                for (ServiceReference ref : refs) {
+                    result.append((String) ref.getProperty("customer"));
+                    result.append(',');
+                    result.append((String) ref.getProperty("name"));
+                    result.append(',');
+                    result.append(getRange(ref).toRepresentation());
+                    result.append('\n');
+                }
+            }
+            response.setContentType(TEXT_MIMETYPE);
+            response.getWriter().print(result.toString());
+        }
+        catch (IOException e) {
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not retrieve version range for repository: " + e.getMessage());
+        }
+        catch (InvalidSyntaxException e) {
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid filter syntax: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Implement this by asking the right repository for a range of available versions.
+     *
+     * @param ref reference to the repository service you need to dereference
+     * @return a sorted range set
+     * @throws java.io.IOException if the range cannot be obtained
+     */
+    protected abstract SortedRangeSet getRange(ServiceReference ref) throws IOException;
+
+    /**
+     * Returns a list of repositories that match the specified filter condition.
+     *
+     * @param filter the filter condition
+     * @return an array of service references
+     * @throws InvalidSyntaxException if the filter condition is invalid
+     */
+    protected abstract ServiceReference[] getRepositories(String filter) throws InvalidSyntaxException;
+
+    /**
+     * Called by Dependency Manager upon initialization of this component.
+     * 
+     * @param comp the component to initialize, cannot be <code>null</code>.
+     */
+    protected void init(Component comp) {
+        comp.add(m_dm.createServiceDependency()
+            .setService(AuthenticationService.class)
+            .setRequired(m_useAuth)
+            .setInstanceBound(true)
+            );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        if (!authenticate(req)) {
+            // Authentication failed; don't proceed with the original request...
+            resp.sendError(SC_UNAUTHORIZED);
+        } else {
+            // Authentication successful, proceed with original request...
+            super.service(req, resp);
+        }
+    }
+
+    /**
+     * Authenticates, if needed the user with the information from the given request.
+     * 
+     * @param request the request to obtain the credentials from, cannot be <code>null</code>.
+     * @return <code>true</code> if the authentication was successful, <code>false</code> otherwise.
+     */
+    private boolean authenticate(HttpServletRequest request) {
+        if (m_useAuth) {
+            User user = m_authService.authenticate(request);
+            if (user == null) {
+                m_log.log(LogService.LOG_INFO, "Authentication failure!");
+            }
+            return (user != null);
+        }
+        return true;
+    }
+
+    /**
+     * Handles a commit command and sends back the response.
+     */
+    private void handleCommit(String customer, String name, long version, InputStream data, HttpServletResponse response) throws IOException {
+        try {
+            ServiceReference[] refs = getRepositories("(&(customer=" + customer + ")(name=" + name + "))");
+            if ((refs != null) && (refs.length == 1)) {
+                ServiceReference ref = refs[0];
+                try {
+                    if (!doCommit(ref, version, data)) {
+                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not commit");
+                    } else {
+                        response.sendError(HttpServletResponse.SC_OK);
+                    }
+                }
+                catch (IllegalArgumentException e) {
+                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid version");
+                }
+                catch (IllegalStateException e) {
+                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Cannot commit, not the master repository");
+                }
+            }
+        }
+        catch (IOException e) {
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "I/O exception: " + e.getMessage());
+        }
+        catch (InvalidSyntaxException e) {
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid filter syntax: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Commit or put the data into the repository.
+     *
+     * @param ref reference to the repository service
+     * @param version the version
+     * @param data the data
+     * @return <code>true</code> if successful
+     * @throws IllegalArgumentException
+     * @throws java.io.IOException
+     */
+    protected abstract boolean doCommit(ServiceReference ref, long version, InputStream data) throws IllegalArgumentException, IOException;
+
+    /**
+     * Handles a checkout command and returns the response.
+     */
+    private void handleCheckout(String customer, String name, long version, HttpServletResponse response) throws IOException {
+        try {
+            ServiceReference[] refs = getRepositories("(&(customer=" + customer + ")(name=" + name + "))");
+            if ((refs != null) && (refs.length == 1)) {
+                ServiceReference ref = refs[0];
+                response.setContentType(BINARY_MIMETYPE);
+                InputStream data = doCheckout(ref, version);
+                if (data == null) {
+                    response.sendError(HttpServletResponse.SC_NOT_FOUND, "Requested version does not exist: " + version);
+                } else {
+                    copy(data, response.getOutputStream());
+                }
+            }
+            else {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND, ((refs == null) ? "Could not find repository " : "Multiple repositories found ") + " for customer " + customer + ", name " + name);
+            }
+        }
+        catch (IOException e) {
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "I/O exception: " + e.getMessage());
+        }
+        catch (InvalidSyntaxException e) {
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid filter syntax: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Checkout or get data from the repository.
+     *
+     * @param ref reference to the repository service
+     * @param version the version
+     * @return the data
+     * @throws IllegalArgumentException
+     * @throws java.io.IOException
+     */
+    protected abstract InputStream doCheckout(ServiceReference ref, long version) throws IllegalArgumentException, IOException;
+
+    /**
+     * Copies data from an input stream to an output stream.
+     * @param in the input
+     * @param out the output
+     * @throws java.io.IOException if copying fails
+     */
+    private void copy(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[COPY_BUFFER_SIZE];
+        int bytes = in.read(buffer);
+        while (bytes != -1) {
+            out.write(buffer, 0, bytes);
+            bytes = in.read(buffer);
+        }
+    }
+
+    public void updated(Dictionary settings) throws ConfigurationException {
+        if (settings != null) {
+            String useAuthString = (String) settings.get(KEY_USE_AUTHENTICATION);
+            if (useAuthString == null
+                || !("true".equalsIgnoreCase(useAuthString) || "false".equalsIgnoreCase(useAuthString))) {
+                throw new ConfigurationException(KEY_USE_AUTHENTICATION, "Missing or invalid value!");
+            }
+            boolean useAuth = Boolean.parseBoolean(useAuthString);
+
+            m_useAuth = useAuth;
+        }
+        else {
+            m_useAuth = false;
+        }
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/packageinfo
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/packageinfo?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/packageinfo (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/packageinfo Tue Apr  9 08:12:33 2013
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file

Added: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java?rev=1465922&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java (added)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java Tue Apr  9 08:12:33 2013
@@ -0,0 +1,51 @@
+/*
+ * 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.repository.task;
+
+import java.util.Properties;
+
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.ace.discovery.Discovery;
+import org.apache.ace.scheduler.constants.SchedulerConstants;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put(SchedulerConstants.SCHEDULER_NAME_KEY, RepositoryReplicationTask.class.getName());
+        props.put(SchedulerConstants.SCHEDULER_DESCRIPTION_KEY, "Synchronizes repositories.");
+
+        manager.add(createComponent()
+            .setInterface(Runnable.class.getName(), props)
+            .setImplementation(RepositoryReplicationTask.class)
+            .add(createServiceDependency().setService(Discovery.class).setRequired(true))
+            .add(createServiceDependency().setService(ConnectionFactory.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false))
+            );
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+    }
+}
\ No newline at end of file