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:18:48 UTC

svn commit: r1465924 [3/3] - in /ace/trunk/org.apache.ace.log: ./ src/ src/org/ src/org/apache/ src/org/apache/ace/ src/org/apache/ace/log/ src/org/apache/ace/log/listener/ src/org/apache/ace/log/server/ src/org/apache/ace/log/server/servlet/ src/org/a...

Added: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/target/task/LogSyncTask.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/target/task/LogSyncTask.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/target/task/LogSyncTask.java (added)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/target/task/LogSyncTask.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,209 @@
+/*
+ * 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.log.target.task;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.ace.discovery.Discovery;
+import org.apache.ace.identification.Identification;
+import org.apache.ace.log.LogDescriptor;
+import org.apache.ace.log.LogEvent;
+import org.apache.ace.log.target.store.LogStore;
+import org.apache.ace.range.RangeIterator;
+import org.apache.ace.range.SortedRangeSet;
+import org.osgi.service.log.LogService;
+
+// TODO there are two versions of this class around, the other ohne being the server.LogSyncTask,
+// and both are fairly similar
+public class LogSyncTask implements Runnable {
+
+    private static final String COMMAND_QUERY = "query";
+    private static final String COMMAND_SEND = "send";
+    private static final String PARAMETER_TARGETID = "tid";
+    private static final String PARAMETER_LOGID = "logid";
+
+    // injected by dependencymanager
+    private volatile Discovery m_discovery;
+    private volatile Identification m_identification;
+    private volatile LogService m_log;
+    private volatile LogStore m_LogStore;
+    private volatile ConnectionFactory m_connectionFactory;
+
+    private final String m_endpoint;
+
+    public LogSyncTask(String endpoint) {
+        m_endpoint = endpoint;
+    }
+
+    /**
+     * Synchronize the log events available remote with the events available locally.
+     */
+    public void run() {
+        URL host = m_discovery.discover();
+
+        if (host == null) {
+            // expected if there's no discovered
+            // ps or relay server
+            m_log.log(LogService.LOG_WARNING, "Unable to synchronize log with remote (endpoint=" + m_endpoint + ") - none available");
+            return;
+        }
+
+        if ("file".equals(host.getProtocol())) {
+            // if the discovery URL is a file, we cannot sync, so we silently return here
+            return;
+        }
+
+        String targetId = m_identification.getID();
+        URLConnection sendConnection = null;
+        try {
+            sendConnection = m_connectionFactory.createConnection(new URL(host, m_endpoint + "/" + COMMAND_SEND));
+            sendConnection.setDoOutput(true);
+            if (sendConnection 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) sendConnection).setChunkedStreamingMode(8192);
+            }
+
+            long[] logIDs = m_LogStore.getLogIDs();
+            for (int i = 0; i < logIDs.length; i++) {
+                URL url = new URL(host, m_endpoint + "/" + COMMAND_QUERY + "?" + PARAMETER_TARGETID + "=" + targetId + "&" + PARAMETER_LOGID + "=" + logIDs[i]);
+
+                URLConnection queryConnection = m_connectionFactory.createConnection(url);
+                // TODO: make sure no actual call is made using sendConnection
+                // when there's nothing to sync
+                synchronizeLog(logIDs[i], queryConnection.getInputStream(), sendConnection);
+            }
+
+            // Make sure to send the actual POST request...
+            sendConnection.getContent();
+        }
+        catch (ConnectException e) {
+            m_log.log(LogService.LOG_WARNING, "Unable to connect to remote (endpoint=" + m_endpoint + ")");
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to (fully) synchronize log with remote (endpoint=" + m_endpoint + ")", e);
+        }
+        finally {
+            if (sendConnection instanceof HttpURLConnection) {
+                ((HttpURLConnection) sendConnection).disconnect();
+            }
+        }
+    }
+
+    /**
+     * Synchronizes a single log (there can be multiple log/logid's per target).
+     * 
+     * @param logID
+     *            ID of the log to synchronize.
+     * @param queryInput
+     *            Stream pointing to a query result for the events available remotely for this log id
+     * @param sendConnection
+     *            .getOutputStream() Stream to write the events to that are missing on the remote side.
+     * @throws java.io.IOException
+     *             If synchronization could not be completed due to an I/O failure.
+     */
+    protected void synchronizeLog(long logID, InputStream queryInput, URLConnection sendConnection) throws IOException {
+        long highestLocal = m_LogStore.getHighestID(logID);
+        if (highestLocal == 0) {
+            // No events, no need to synchronize
+            return;
+        }
+
+        SortedRangeSet localRange = new SortedRangeSet("1-" + highestLocal);
+        SortedRangeSet remoteRange = getDescriptor(queryInput).getRangeSet();
+        SortedRangeSet delta = remoteRange.diffDest(localRange);
+        RangeIterator rangeIterator = delta.iterator();
+
+        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(sendConnection.getOutputStream()));
+
+        if (rangeIterator.hasNext()) {
+            long lowest = rangeIterator.next();
+            long highest = delta.getHigh();
+            if (lowest <= highest) {
+                List events = m_LogStore.get(logID, lowest, highestLocal > highest ? highest : highestLocal);
+                Iterator iter = events.iterator();
+                while (iter.hasNext()) {
+                    LogEvent current = (LogEvent) iter.next();
+                    while ((current.getID() > lowest) && rangeIterator.hasNext()) {
+                        lowest = rangeIterator.next();
+                    }
+                    if (current.getID() == lowest) {
+                        // before we send the LogEvent to the other side, we fill out the
+                        // appropriate identification
+                        LogEvent event = new LogEvent(m_identification.getID(), current);
+                        writer.write(event.toRepresentation() + "\n");
+                    }
+                }
+            }
+        }
+
+        writer.flush();
+    }
+
+    /**
+     * Retrieves a LogDescriptor object from the specified stream.
+     * 
+     * @param queryInput
+     *            Stream containing a LogDescriptor object.
+     * @return LogDescriptor object reflecting the range contained in the stream.
+     * @throws java.io.IOException
+     *             If no range could be determined due to an I/O failure.
+     */
+    protected LogDescriptor getDescriptor(InputStream queryInput) throws IOException {
+        BufferedReader queryReader = null;
+        try {
+            queryReader = new BufferedReader(new InputStreamReader(queryInput));
+            String rangeString = queryReader.readLine();
+            if (rangeString != null) {
+                try {
+                    return new LogDescriptor(rangeString);
+                }
+                catch (IllegalArgumentException iae) {
+                    throw new IOException("Could not determine highest remote event id, received malformed event range (" + rangeString + ")");
+                }
+            }
+            else {
+                throw new IOException("Could not construct LogDescriptor from stream because stream is empty");
+            }
+        }
+        finally {
+            if (queryReader != null) {
+                try {
+                    queryReader.close();
+                }
+                catch (Exception ex) {
+                    // not much we can do
+                }
+            }
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/util/Codec.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/util/Codec.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/util/Codec.java (added)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/util/Codec.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,89 @@
+/*
+ * 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.log.util;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+
+public class Codec
+{
+
+    public static String decode(String source) throws IllegalArgumentException {
+        StringBuffer result = new StringBuffer();
+        StringCharacterIterator sci = new StringCharacterIterator(source);
+        for (char c = sci.current(); c != CharacterIterator.DONE; c = sci.next()) {
+            if (c == '$') {
+                c = sci.next();
+                if (c != CharacterIterator.DONE) {
+                    if (c == '$') {
+                        result.append('$');
+                    }
+                    else if (c == 'k') {
+                        result.append(',');
+                    }
+                    else if (c == 'n') {
+                        result.append('\n');
+                    }
+                    else if (c == 'r') {
+                        result.append('\r');
+                    }
+                    else if (c == 'e') {
+                        return null;
+                    }
+                    else {
+                        throw new IllegalArgumentException("Unknown escape character: " + c);
+                    }
+                }
+                else {
+                    throw new IllegalArgumentException("Unexpected end of input: " + source);
+                }
+            }
+            else {
+                result.append(c);
+            }
+        }
+        return result.toString();
+    }
+
+    public static String encode(String source) {
+        if (source == null) {
+            return "$e";
+        }
+        StringBuffer result = new StringBuffer();
+        StringCharacterIterator sci = new StringCharacterIterator(source);
+        for (char c = sci.current(); c != CharacterIterator.DONE; c = sci.next()) {
+            if (c == '$') {
+                result.append("$$");
+            }
+            else if (c == ',') {
+                result.append("$k");
+            }
+            else if (c == '\n') {
+                result.append("$n");
+            }
+            else if (c == '\r') {
+                result.append("$r");
+            }
+            else {
+                result.append(c);
+            }
+        }
+        return result.toString();
+    }
+}
\ No newline at end of file

Added: ace/trunk/org.apache.ace.log/target.bnd
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/target.bnd?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/target.bnd (added)
+++ ace/trunk/org.apache.ace.log/target.bnd Tue Apr  9 08:18:47 2013
@@ -0,0 +1,4 @@
+Private-Package: org.apache.ace.log.target,\
+	org.apache.ace.log.target.task
+Bundle-Activator: org.apache.ace.log.target.Activator
+Bundle-Version: 1.0.0

Added: ace/trunk/org.apache.ace.log/target.store.impl.bnd
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/target.store.impl.bnd?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/target.store.impl.bnd (added)
+++ ace/trunk/org.apache.ace.log/target.store.impl.bnd Tue Apr  9 08:18:47 2013
@@ -0,0 +1,4 @@
+Private-Package: org.apache.ace.log.target.store.impl
+Bundle-Activator: org.apache.ace.log.target.store.impl.Activator
+Export-Package: org.apache.ace.log.target.store
+Bundle-Version: 1.0.0

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogDescriptorTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogDescriptorTest.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogDescriptorTest.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogDescriptorTest.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,66 @@
+/*
+ * 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.log;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import org.apache.ace.range.SortedRangeSet;
+import org.testng.annotations.Test;
+
+public class LogDescriptorTest {
+
+    @Test(groups = { UNIT })
+    public void serializeDescriptor() {
+        LogDescriptor descriptor = new LogDescriptor("gwid", 1, new SortedRangeSet("2-3"));
+        assert descriptor.toRepresentation().equals("gwid,1,2-3") : "The representation of our descriptor is incorrect:" + descriptor.toRepresentation();
+    }
+
+    @Test(groups = { UNIT })
+    public void deserializeDescriptor() {
+        LogDescriptor descriptor = new LogDescriptor("gwid,1,2-3");
+        assert descriptor.getTargetID().equals("gwid") : "Target ID not correctly parsed.";
+        assert descriptor.getLogID() == 1 : "Log ID not correctly parsed.";
+        assert descriptor.getRangeSet().toRepresentation().equals("2-3") : "There should be nothing in the diff between the set in the descriptor and the check-set.";
+    }
+
+    @Test(groups = { UNIT })
+    public void deserializeMultiRangeDescriptor() {
+        LogDescriptor descriptor = new LogDescriptor("gwid,1,1-4$k6$k8$k10-20");
+        assert descriptor.getTargetID().equals("gwid") : "Target ID not correctly parsed.";
+        assert descriptor.getLogID() == 1 : "Log ID not correctly parsed.";
+        String representation = descriptor.getRangeSet().toRepresentation();
+        assert representation.equals("1-4,6,8,10-20") : "There should be nothing in the diff between the set in the descriptor and the check-set, but we parsed: " + representation;
+    }
+
+    @Test(groups = { UNIT })
+    public void deserializeMultiRangeDescriptorWithFunnyGWID() {
+        String line = "gw$$id,1,1-4$k6$k8$k10-20";
+        LogDescriptor descriptor = new LogDescriptor(line);
+        assert descriptor.getTargetID().equals("gw$id") : "Target ID not correctly parsed.";
+        assert descriptor.getLogID() == 1 : "Log ID not correctly parsed.";
+        assert line.equals(descriptor.toRepresentation()) : "Converting the line back to the representation failed.";
+        String representation = descriptor.getRangeSet().toRepresentation();
+        assert representation.equals("1-4,6,8,10-20") : "There should be nothing in the diff between the set in the descriptor and the check-set, but we parsed: " + representation;
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = IllegalArgumentException.class)
+    public void deserializeInvalidDescriptor() throws Exception {
+        new LogDescriptor("invalidStringRepresentation");
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogEventTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogEventTest.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogEventTest.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/LogEventTest.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,87 @@
+/*
+ * 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.log;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.util.Dictionary;
+import java.util.Properties;
+
+import org.testng.annotations.Test;
+
+public class LogEventTest {
+    @Test(groups = { UNIT })
+    public void serializeLogEvent() {
+        LogEvent e = new LogEvent("gwid", 1, 2, 3, AuditEvent.FRAMEWORK_STARTED, new Properties());
+        assert e.toRepresentation().equals("gwid,1,2,3," + AuditEvent.FRAMEWORK_STARTED);
+        Properties p = new Properties();
+        p.put(AuditEvent.KEY_ID, "my first value");
+        e = new LogEvent("gwid", 1, 2, 3, AuditEvent.BUNDLE_INSTALLED, p);
+        assert e.toRepresentation().equals("gwid,1,2,3," + AuditEvent.BUNDLE_INSTALLED + "," + AuditEvent.KEY_ID + ",my first value");
+        e = new LogEvent("gwid,gwid\n\r$", 1, 2, 3, AuditEvent.FRAMEWORK_STARTED, new Properties());
+        assert e.toRepresentation().equals("gwid$kgwid$n$r$$,1,2,3," + AuditEvent.FRAMEWORK_STARTED);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void deserializeLogEvent() {
+        LogEvent e = new LogEvent("gwid$kgwid$n$r$$,1,2,3," + AuditEvent.FRAMEWORK_STARTED + ",a,1,b,2,c,3");
+        assert e.getTargetID().equals("gwid,gwid\n\r$") : "Target ID is not correctly parsed";
+        assert e.getLogID() == 1 : "Log ID is not correctly parsed";
+        assert e.getID() == 2 : "ID is not correctly parsed";
+        assert e.getTime() == 3 : "Time is not correctly parsed";
+        assert e.getType() == AuditEvent.FRAMEWORK_STARTED : "Event type is wrong";
+        Dictionary p = e.getProperties();
+        assert p != null : "Properties are not correctly parsed";
+        assert p.get("a").equals("1") : "Property a should be 1";
+        assert p.get("b").equals("2") : "Property a should be 1";
+        assert p.get("c").equals("3") : "Property a should be 1";
+    }
+    @Test(groups = { UNIT })
+    public void deserializeIllegalLogEvent() {
+        try {
+            new LogEvent("garbage in, garbage out!");
+            assert false : "Parsing garbage should result in an exception";
+        }
+        catch (IllegalArgumentException e) {
+            // expected
+        }
+        try {
+            new LogEvent("g$z,1,2,3," + AuditEvent.BUNDLE_STOPPED);
+            assert false : "Parsing illegal token should result in an exception";
+        }
+        catch (IllegalArgumentException e) {
+            // expected
+        }
+        try {
+            new LogEvent("g$,1,2,3," + AuditEvent.BUNDLE_STOPPED);
+            assert false : "Parsing half of a token should result in an exception";
+        }
+        catch (IllegalArgumentException e) {
+            // expected
+        }
+        try {
+            new LogEvent("g$,1,2,3," + AuditEvent.BUNDLE_STOPPED + ",a");
+            assert false : "Parsing only a key should result in an exception";
+        }
+        catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/LogTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/LogTest.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/LogTest.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/LogTest.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,167 @@
+/*
+ * 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.log.listener;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.ace.log.AuditEvent;
+import org.apache.ace.log.Log;
+import org.apache.ace.log.listener.MockLog.LogEntry;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class LogTest {
+
+    private LogProxy m_logProxy;
+
+    private Log m_mockLog;
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws Exception {
+        m_logProxy = new LogProxy();
+        m_mockLog = new MockLog();
+    }
+
+    /**
+     * Test whether logging to the cache and setting a new Log causes the log entries to be flushed to this new Log.
+     */
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testLogCacheFlush() throws Exception {
+        assert ((MockLog) m_mockLog).getLogEntries().size() == 0 : "MockLog is not empty on start of test";
+
+        Dictionary props = new Properties();
+        String test = "test";
+        String value = "value";
+        props.put(test, value);
+        m_logProxy.log(1, props);
+
+        assert ((MockLog) m_mockLog).getLogEntries().size() == 0 : "MockLog is not empty, but should be as the log should be in the cache";
+
+        m_logProxy.setLog(m_mockLog);
+
+        assert ((MockLog) m_mockLog).getLogEntries().size() == 1 : "Log should contain 1 entry";
+        assert ((MockLog.LogEntry) ((MockLog) m_mockLog).getLogEntries().get(0)).getProperties().get(test).equals(value) : "The property should be 'test:value'";
+    }
+
+    /**
+     * Test whether after unsetting the Log, no new log entries are added, but that they are added to the cache instead
+     * (test the latter by flushing the cache).
+     */
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testUnsettingLog() throws Exception {
+        assert ((MockLog) m_mockLog).getLogEntries().size() == 0 : "MockLog is not empty on start of test";
+        m_logProxy.setLog(m_mockLog);
+
+        Dictionary props = new Properties();
+        props.put("test", "value");
+        m_logProxy.log(1, props);
+
+        assert ((MockLog) m_mockLog).getLogEntries().size() == 1 : "MockLog should have 1 log entry";
+
+        m_logProxy.setLog(null);
+
+        Dictionary props2 = new Properties();
+        props2.put("test2", "value2");
+        m_logProxy.log(2, props2);
+
+        assert ((MockLog) m_mockLog).getLogEntries().size() == 1 : "MockLog should still have 1 log entry";
+
+        m_logProxy.setLog(m_mockLog);
+
+        assert ((MockLog) m_mockLog).getLogEntries().size() == 2 : "MockLog should have 2 log entries";
+    }
+
+    /**
+     * Basic functionality of the ListenerImpl is covered, the rest of the situations will probably be covered by integration
+     * tests. Note: test the deployment event INSTALL only when a BundleContext is available
+     */
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testEventConverting() throws Exception {
+        ListenerImpl listeners = new ListenerImpl(null, m_logProxy);
+        listeners.startInternal();
+        m_logProxy.setLog(m_mockLog);
+
+        final String symbolicName = "org.apache.ace.auditlog.listener.testbundle.a";
+        final long bundleId = 123;
+        final String bundleVersion = "1.2.3";
+        final String bundleLocation = "/home/apache/ace/testbundlea.jar";
+
+        Bundle testBundleA = TestUtils.createMockObjectAdapter(Bundle.class, new Object() {
+            @SuppressWarnings("all")
+            public long getBundleId() {
+                return bundleId;
+            }
+
+            @SuppressWarnings("all")
+            public String getSymbolicName() {
+                return symbolicName;
+            }
+
+            @SuppressWarnings("all")
+            public Dictionary getHeaders() {
+                Dictionary dict = new Properties();
+                dict.put(Constants.BUNDLE_VERSION, bundleVersion);
+                return dict;
+            }
+
+            @SuppressWarnings("all")
+            public String getLocation() {
+                return bundleLocation;
+            }
+        });
+
+        BundleEvent bundleEvent = new BundleEvent(BundleEvent.INSTALLED, testBundleA);
+        FrameworkEvent frameworkEvent = new FrameworkEvent(FrameworkEvent.INFO, testBundleA, new IllegalStateException());
+
+        listeners.bundleChanged(bundleEvent);
+        listeners.frameworkEvent(frameworkEvent);
+        listeners.stopInternal();
+
+        List logEntries = ((MockLog) m_mockLog).getLogEntries();
+        assert logEntries.size() == 2 : "2 log entries should be logged";
+
+        LogEntry bundleEntry = (LogEntry) logEntries.get(0);
+        assert bundleEntry.getType() == AuditEvent.BUNDLE_INSTALLED : "state BUNDLE_INSTALLED (" + AuditEvent.BUNDLE_INSTALLED + ") should be in log but '" + bundleEntry.getType() + "' is in log instead";
+        Dictionary bundleProps = bundleEntry.getProperties();
+        assert bundleProps.size() == 4 : "4 properties should be stored, but found: " + bundleProps.size();
+        assert bundleProps.get(AuditEvent.KEY_ID).equals(Long.toString(bundleId)) : "id should be " + bundleId + " but is: " + bundleProps.get(AuditEvent.KEY_ID);
+        assert bundleProps.get(AuditEvent.KEY_NAME).equals(symbolicName) : "symbolicName should be " + symbolicName + " but is " + bundleProps.get(AuditEvent.KEY_NAME);
+        assert bundleProps.get(AuditEvent.KEY_VERSION).equals(bundleVersion) : "version should be " + bundleVersion + " but is " + bundleProps.get(AuditEvent.KEY_VERSION);
+        assert bundleProps.get(AuditEvent.KEY_LOCATION).equals(bundleLocation) : "location should be " + bundleLocation + " but is " + bundleProps.get(AuditEvent.KEY_LOCATION);
+
+        LogEntry frameworkEntry = (LogEntry) logEntries.get(1);
+        assert frameworkEntry.getType() == AuditEvent.FRAMEWORK_INFO : "state FRAMEWORK_INFO (" + AuditEvent.FRAMEWORK_INFO + ") should be in log but '" + frameworkEntry.getType() + "' is in log instead";
+        Dictionary frameworkProps = frameworkEntry.getProperties();
+        assert frameworkProps.size() == 2 : "2 properties should be stored, but found: " + frameworkProps.size();
+        assert frameworkProps.get(AuditEvent.KEY_ID).equals(Long.toString(bundleId)) : "id should be " + bundleId + " but is: " + frameworkProps.get(AuditEvent.KEY_ID);
+        assert frameworkProps.get(AuditEvent.KEY_TYPE).equals(IllegalStateException.class.getName()) : "exceptionType should be " + IllegalStateException.class.getName() + " but is: " + frameworkProps.get(AuditEvent.KEY_TYPE);
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/MockLog.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/MockLog.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/MockLog.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/listener/MockLog.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,71 @@
+/*
+ * 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.log.listener;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.apache.ace.log.Log;
+
+public class MockLog implements Log {
+
+    @SuppressWarnings("unchecked")
+    private List m_logEntries;
+
+    @SuppressWarnings("unchecked")
+    public MockLog() {
+        m_logEntries = Collections.synchronizedList(new ArrayList());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void log(int type, Dictionary properties) {
+        m_logEntries.add(new LogEntry(type, properties));
+    }
+
+    @SuppressWarnings("unchecked")
+    public List getLogEntries() {
+        return new ArrayList(m_logEntries);
+    }
+
+    public void clear() {
+        m_logEntries.clear();
+    }
+
+    public class LogEntry {
+        private int m_type;
+        @SuppressWarnings("unchecked")
+        private Dictionary m_properties;
+        @SuppressWarnings("unchecked")
+        public LogEntry(int type, Dictionary properties) {
+            m_type = type;
+            m_properties = properties;
+        }
+
+        public int getType() {
+            return m_type;
+        }
+
+        @SuppressWarnings("unchecked")
+        public Dictionary getProperties() {
+            return m_properties;
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/servlet/LogServletTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/servlet/LogServletTest.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/servlet/LogServletTest.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/servlet/LogServletTest.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,166 @@
+/*
+ * 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.log.server.servlet;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+
+import org.apache.ace.log.LogDescriptor;
+import org.apache.ace.log.LogEvent;
+import org.apache.ace.log.server.servlet.LogServlet;
+import org.apache.ace.log.server.store.LogStore;
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.service.log.LogService;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class LogServletTest {
+
+    private LogServlet m_logServlet;
+    private LogDescriptor m_range = new LogDescriptor("tID", 123, new SortedRangeSet("1-3"));
+    private LogEvent m_event1 = new LogEvent("tID", 123, 1, 888888, 1, new Properties());
+    private LogEvent m_event2 = new LogEvent("tID", 123, 2, 888888, 2, new Properties());
+    private MockLogStore m_mockStore;
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws Exception {
+        m_logServlet = new LogServlet("test", false /* useAuth */);
+        TestUtils.configureObject(m_logServlet, LogService.class);
+        m_mockStore = new MockLogStore();
+        TestUtils.configureObject(m_logServlet, LogStore.class, m_mockStore);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    protected void tearDown() throws Exception {
+    }
+
+    @Test(groups = { UNIT })
+    public void queryLog() throws Exception {
+        MockServletOutputStream output = new MockServletOutputStream();
+        boolean result = m_logServlet.handleQuery(m_range.getTargetID(), String.valueOf(m_range.getLogID()), null, output);
+        assert result;
+        assert m_range.toRepresentation().equals(output.m_text);
+        output.m_text = "";
+        result = m_logServlet.handleQuery(null, null, null, output);
+        assert result;
+        assert (m_range.toRepresentation() + "\n").equals(output.m_text);
+    }
+
+    @Test(groups = { UNIT })
+    public void receiveLog() throws Exception {
+        MockServletOutputStream output = new MockServletOutputStream();
+        boolean result = m_logServlet.handleReceive(m_range.getTargetID(), String.valueOf(m_range.getLogID()), "1", null, output);
+        assert result;
+        String expected = m_event1.toRepresentation() + "\n";
+        String actual = output.m_text;
+        assert expected.equals(actual) : "We expected '" + expected + "', but received '" + actual + "'";
+
+        output = new MockServletOutputStream();
+        result = m_logServlet.handleReceive(m_range.getTargetID(), String.valueOf(m_range.getLogID()), null , null, output);
+        assert result;
+        expected = m_event1.toRepresentation() + "\n" + m_event2.toRepresentation() + "\n";
+        actual = output.m_text;
+        assert expected.equals(actual) : "We expected '" + expected + "', but received '" + actual + "'";;
+    }
+
+    @Test(groups = { UNIT })
+    public void sendLog() throws Exception {
+        MockServletInputStream input = new MockServletInputStream();
+        String expected = m_event1.toRepresentation() + "\n" + m_event2.toRepresentation() + "\n";
+        input.setBytes(expected.getBytes());
+        m_logServlet.handleSend(input);
+
+        String actual = "";
+        for (Iterator<LogEvent> i = m_mockStore.m_events.iterator(); i.hasNext();) {
+            LogEvent event = i.next();
+            actual = actual + event.toRepresentation() + "\n";
+        }
+        assert expected.equals(actual);
+    }
+
+    private class MockLogStore implements LogStore {
+        public List<LogEvent> m_events = new ArrayList<LogEvent>();
+
+        public List<LogEvent> get(LogDescriptor range) {
+            List<LogEvent> events = new ArrayList<LogEvent>();
+            if (range.getRangeSet().contains(1)) {
+                events.add(m_event1);
+            }
+            if (range.getRangeSet().contains(2)) {
+                events.add(m_event2);
+            }
+            return events;
+        }
+        public List<LogDescriptor> getDescriptors(String targetID) {
+            return null;
+        }
+        public List<LogDescriptor> getDescriptors() {
+            List<LogDescriptor> ranges = new ArrayList<LogDescriptor>();
+            ranges.add(m_range);
+            return ranges;
+        }
+        public LogDescriptor getDescriptor(String targetID, long logID) throws IOException {
+            return m_range;
+        }
+        public void put(List<LogEvent> events) throws IOException {
+            m_events = events;
+        }
+    }
+
+    private class MockServletOutputStream extends ServletOutputStream {
+        public String m_text = "";
+
+        @Override
+        public void print(String s) throws IOException {
+            m_text = m_text.concat(s);
+        }
+        @Override
+        public void write(int arg0) throws IOException {
+        }
+    }
+
+    private class MockServletInputStream extends ServletInputStream {
+        private int i = 0;
+        private byte[] m_bytes;
+
+        @Override
+        public int read() throws IOException {
+            int result = -1;
+            if (i < m_bytes.length) {
+                result = m_bytes[i];
+                i++;
+            }
+            return result;
+        }
+
+        public void setBytes(byte[] bytes) {
+            m_bytes = bytes;
+        }
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/store/impl/ServerLogStoreTester.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/store/impl/ServerLogStoreTester.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/store/impl/ServerLogStoreTester.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/store/impl/ServerLogStoreTester.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,116 @@
+/*
+ * 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.log.server.store.impl;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.ace.log.AuditEvent;
+import org.apache.ace.log.LogDescriptor;
+import org.apache.ace.log.LogEvent;
+import org.apache.ace.log.server.store.impl.LogStoreImpl;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.service.event.EventAdmin;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ServerLogStoreTester {
+    private LogStoreImpl m_logStore;
+    private File m_dir;
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws Exception {
+        m_dir = File.createTempFile("logstore", "txt");
+        m_dir.delete();
+        m_dir.mkdirs();
+        m_logStore = new LogStoreImpl(m_dir, "log");
+        TestUtils.configureObject(m_logStore, EventAdmin.class);
+        m_logStore.start();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    protected void tearDown() throws IOException {
+        delete(m_dir);
+    }
+
+    @SuppressWarnings("serial")
+    @Test(groups = { UNIT })
+    public void testLog() throws IOException {
+        List<LogDescriptor> ranges = m_logStore.getDescriptors();
+        assert ranges.isEmpty() : "New store should have no ranges.";
+        List<LogEvent> events = new ArrayList<LogEvent>();
+        for (String target : new String[] { "g1", "g2", "g3" }) {
+            for (long log : new long[] { 1, 2, 3, 5 }) {
+                for (long id : new long[] { 1, 2, 3, 20 }) {
+                    events.add(new LogEvent(target, log, id, System.currentTimeMillis(), AuditEvent.FRAMEWORK_STARTED, new Properties() {
+                        {
+                            put("test", "bar");
+                        }
+                    }));
+                }
+            }
+        }
+        m_logStore.put(events);
+        assert m_logStore.getDescriptors().size() == 3 * 4 : "Incorrect amount of ranges returned from store";
+        List<LogEvent> stored = new ArrayList<LogEvent>();
+        for (LogDescriptor range : m_logStore.getDescriptors()) {
+            for (LogDescriptor range2 : m_logStore.getDescriptors(range.getTargetID())) {
+                stored.addAll(m_logStore.get(m_logStore.getDescriptor(range2.getTargetID(), range2.getLogID())));
+            }
+        }
+
+        Set<String> in = new HashSet<String>();
+        for (LogEvent event : events)  {
+            in.add(event.toRepresentation());
+        }
+        Set<String> out = new HashSet<String>();
+        for (LogEvent event : stored) {
+            out.add(event.toRepresentation());
+        }
+        assert in.equals(out) : "Stored events differ from the added.";
+    }
+
+    @Test( groups = { TestUtils.UNIT } )
+    public void testLogWithSpecialCharacters() throws IOException {
+        String targetID = "myta\0rget";
+        LogEvent event = new LogEvent(targetID, 1, 1, System.currentTimeMillis(), AuditEvent.FRAMEWORK_STARTED, new Properties());
+        List<LogEvent> events = new ArrayList<LogEvent>();
+        events.add(event);
+        m_logStore.put(events);
+        assert m_logStore.getDescriptors().size() == 1 : "Incorrect amount of ranges returned from store: expected 1, found " + m_logStore.getDescriptors().size();
+        assert m_logStore.getDescriptors(targetID).size() == 1 : "We expect to find a single event: expected 1, found " + m_logStore.getDescriptors(targetID).size();
+    }
+
+    private void delete(File root) {
+        if (root.isDirectory()) {
+            for (File child : root.listFiles()) {
+                delete(child);
+            }
+        }
+        root.delete();
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/task/LogTaskTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/task/LogTaskTest.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/task/LogTaskTest.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/task/LogTaskTest.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,107 @@
+/*
+ * 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.log.server.task;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ace.log.LogDescriptor;
+import org.apache.ace.log.server.task.LogSyncTask;
+import org.apache.ace.range.RangeIterator;
+import org.apache.ace.range.SortedRangeSet;
+import org.testng.annotations.Test;
+
+public class LogTaskTest {
+
+    private class MockLogSyncTask extends LogSyncTask {
+        public List<LogDescriptor> m_calledWith = new ArrayList<LogDescriptor>();
+
+        public MockLogSyncTask(String endpoint, String name) {
+            super(endpoint, name, LogSyncTask.Mode.PUSH);
+        }
+
+        @Override
+        protected void writeLogDescriptor(LogDescriptor descriptor, Writer writer) throws IOException {
+            m_calledWith.add(descriptor);
+        }
+
+    }
+
+    /**
+     * This test tests both delta computation and push behavior.
+     */
+    @Test(groups = { UNIT })
+    public void testDeltaComputation() throws IOException {
+        // TODO: Test the new LogDescriptor.
+        List<LogDescriptor> src = new ArrayList<LogDescriptor>();
+        List<LogDescriptor> dest = new ArrayList<LogDescriptor>();
+        MockLogSyncTask task = new MockLogSyncTask("mocklog", "mocklog");
+        // compare two empty lists
+        task.writeDelta(task.calculateDelta(src, dest), null);
+        assert task.m_calledWith.isEmpty() : "Delta of two empty lists should be empty";
+        // add something to the source
+        src.add(new LogDescriptor("gwid", 1, new SortedRangeSet("1-5")));
+        task.writeDelta(task.calculateDelta(src, dest), null);
+        assert task.m_calledWith.size() == 1 : "Delta should be 1 instead of: " + task.m_calledWith.size();
+        task.m_calledWith.clear();
+        // add an overlapping destination
+        dest.add(new LogDescriptor("gwid", 1, new SortedRangeSet("1-3")));
+        task.writeDelta(task.calculateDelta(src, dest), null);
+        assert task.m_calledWith.size() == 1 : "Delta should be 1 instead of: " + task.m_calledWith.size();
+        RangeIterator i = task.m_calledWith.get(0).getRangeSet().iterator();
+        assert i.next() == 4 : "Illegal value in SortedRangeSet";
+        assert i.next() == 5 : "Illegal value in SortedRangeSet";
+        assert !i.hasNext() : "Illegal value in SortedRangeSet";
+        task.m_calledWith.clear();
+        // add a non-overlapping destination
+        dest.add(new LogDescriptor("gwid", 2, new SortedRangeSet("50-100")));
+        task.writeDelta(task.calculateDelta(src, dest), null);
+        assert task.m_calledWith.size() == 1 : "Delta should be 1 instead of: " + task.m_calledWith.size();
+        i = task.m_calledWith.get(0).getRangeSet().iterator();
+        assert i.next() == 4 : "Illegal value in SortedRangeSet";
+        assert i.next() == 5 : "Illegal value in SortedRangeSet";
+        assert !i.hasNext() : "Illegal value in SortedRangeSet";
+        task.m_calledWith.clear();
+        // add non-overlapping source
+        src.add(new LogDescriptor("gwid", 2, new SortedRangeSet("1-49")));
+        task.writeDelta(task.calculateDelta(src, dest), null);
+        assert task.m_calledWith.size() == 2 : "Delta should be 2 instead of: " + task.m_calledWith.size();
+        task.m_calledWith.clear();
+        // add a source with gaps
+        src.add(new LogDescriptor("gwid", 3, new SortedRangeSet("1-10")));
+        dest.add(new LogDescriptor("gwid", 3, new SortedRangeSet("3,5-8")));
+        task.writeDelta(task.calculateDelta(src, dest), null);
+        assert task.m_calledWith.size() == 3 : "Delta should be 3 instead of: " + task.m_calledWith.size();
+        for (LogDescriptor l : task.m_calledWith) {
+            if (l.getLogID() == 3) {
+                i = l.getRangeSet().iterator();
+            }
+        }
+        assert i.next() == 1 : "Illegal value in SortedRangeSet";
+        assert i.next() == 2 : "Illegal value in SortedRangeSet";
+        assert i.next() == 4 : "Illegal value in SortedRangeSet";
+        assert i.next() == 9 : "Illegal value in SortedRangeSet";
+        assert i.next() == 10 : "Illegal value in SortedRangeSet";
+        assert !i.hasNext() : "Illegal value in SortedRangeSet";
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/store/impl/GatewayLogStoreTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/store/impl/GatewayLogStoreTest.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/store/impl/GatewayLogStoreTest.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/store/impl/GatewayLogStoreTest.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,104 @@
+/*
+ * 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.log.target.store.impl;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.ace.identification.Identification;
+import org.apache.ace.log.AuditEvent;
+import org.apache.ace.log.LogEvent;
+import org.apache.ace.log.target.store.LogStore;
+import org.apache.ace.log.target.store.impl.LogStoreImpl;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.service.log.LogService;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class GatewayLogStoreTest {
+    private LogStoreImpl m_logStore;
+    private File m_dir = null;
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws Exception {
+        m_dir  = File.createTempFile(LogStore.class.getName(), null);
+        m_dir.delete();
+        m_logStore = new LogStoreImpl(m_dir);
+        TestUtils.configureObject(m_logStore, LogService.class);
+        TestUtils.configureObject(m_logStore, Identification.class, TestUtils.createMockObjectAdapter(Identification.class, new Object() {
+            @SuppressWarnings("unused")
+            public String getID() {
+                return "test";
+            }
+        }));
+        m_logStore.start();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    protected void tearDown() throws IOException {
+        m_logStore.stop();
+        delete(m_dir);
+        m_logStore = null;
+    }
+
+    @SuppressWarnings({ "serial", "unchecked" })
+    @Test(groups = {UNIT})
+    public void testLog() throws IOException {
+        long[] ids = m_logStore.getLogIDs();
+        assert ids.length == 1 : "New store should have only one id";
+        List<String> events = new ArrayList<String>();
+        events.add(m_logStore.put(AuditEvent.FRAMEWORK_STARTED, new Properties() {{put("test", "test");}}).toRepresentation());
+        events.add(m_logStore.put(AuditEvent.BUNDLE_INSTALLED, new Properties() {{put("test", "test");}}).toRepresentation());
+        events.add(m_logStore.put(AuditEvent.DEPLOYMENTADMIN_COMPLETE, new Properties() {{put("test", "test");}}).toRepresentation());
+        ids = m_logStore.getLogIDs();
+        assert ids.length == 1 : "Error free store should have only one id";
+        long highest = m_logStore.getHighestID(ids[0]);
+        assert  highest == 3 : "Store with 3 entries should have 3 as highest id but was: " + highest;
+        List<String> result = new ArrayList<String>();
+        for (LogEvent event : (List<LogEvent>) m_logStore.get(ids[0])) {
+            result.add(event.toRepresentation());
+        }
+        assert result.equals(events) : "Events " + events + " should equal full log " + result;
+        result = new ArrayList<String>();
+        for (LogEvent event : (List<LogEvent>) m_logStore.get(ids[0], 1, highest)) {
+            result.add(event.toRepresentation());
+        }
+        assert result.equals(events) : "Events " + events + " should equal full log " + result;
+    }
+
+    @Test(groups = {UNIT}, expectedExceptions = {IOException.class})
+    public void testExceptionHandling() throws IOException {
+        m_logStore.handleException(m_logStore.getLog(4711), new IOException("test"));
+    }
+
+    private static void delete(File target) {
+        if (target.isDirectory()) {
+            for (File child : target.listFiles()) {
+                delete(child);
+            }
+        }
+        target.delete();
+    }
+}

Added: ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/task/LogSyncTaskTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/task/LogSyncTaskTest.java?rev=1465924&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/task/LogSyncTaskTest.java (added)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/target/task/LogSyncTaskTest.java Tue Apr  9 08:18:47 2013
@@ -0,0 +1,172 @@
+/*
+ * 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.log.target.task;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.ace.discovery.Discovery;
+import org.apache.ace.identification.Identification;
+import org.apache.ace.log.LogDescriptor;
+import org.apache.ace.log.LogEvent;
+import org.apache.ace.log.target.store.LogStore;
+import org.apache.ace.log.target.task.LogSyncTask;
+import org.apache.ace.range.SortedRangeSet;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.service.log.LogService;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class LogSyncTaskTest {
+
+    private static final String TARGET_ID = "gwID";
+    private LogSyncTask m_task;
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws Exception {
+        m_task = new LogSyncTask("testlog");
+        TestUtils.configureObject(m_task, LogService.class);
+        TestUtils.configureObject(m_task, Identification.class, new Identification() {
+            public String getID() {
+                return TARGET_ID;
+            }
+        });
+        TestUtils.configureObject(m_task, Discovery.class);
+        TestUtils.configureObject(m_task, LogStore.class);
+    }
+
+    @Test(groups = { UNIT })
+    public synchronized void getRange() throws Exception {
+        final LogDescriptor range = new LogDescriptor(TARGET_ID, 1, new SortedRangeSet("1-10"));
+        m_task.getDescriptor(new InputStream() {
+            int m_count = 0;
+            byte[] m_bytes = (range.toRepresentation() + "\n").getBytes();
+            @Override
+            public int read() throws IOException {
+                if (m_count < m_bytes.length) {
+                    byte b = m_bytes[m_count];
+                    m_count++;
+                    return b;
+                } else {
+                    return -1;
+                }
+            }
+        });
+    }
+
+    @Test(groups = { UNIT })
+    public synchronized void synchronizeLog() throws Exception {
+        final LogDescriptor range = new LogDescriptor(TARGET_ID, 1, new SortedRangeSet(new long[] {0}));
+        final LogEvent event = new LogEvent(TARGET_ID, 1, 1, 1, 1, new Properties());
+        final List<LogEvent> events = new ArrayList<LogEvent>();
+        events.add(event);
+
+        InputStream input = new InputStream() {
+            byte[] bytes = range.toRepresentation().getBytes();
+            int count = 0;
+            @Override
+            public int read() throws IOException {
+                if (count < bytes.length) {
+                    byte b = bytes[count];
+                    count++;
+                    return b;
+                } else {
+                    return -1;
+                }
+            }
+        };
+        TestUtils.configureObject(m_task, LogStore.class, new LogStore() {
+            public List<?> get(long logID, long from, long to) throws IOException {
+                return events;
+            }
+            public long getHighestID(long logID) throws IOException {
+                return event.getID();
+            }
+            public List<?> get(long logID) throws IOException { return null; }
+            public long[] getLogIDs() throws IOException { return null; }
+            @SuppressWarnings("unchecked")
+            public LogEvent put(int type, Dictionary props) throws IOException { return null; }
+        });
+        MockConnection connection = new MockConnection(new URL("http://mock"));
+        
+        m_task.synchronizeLog(1, input, connection);
+        String expectedString = event.toRepresentation() + "\n";
+        String actualString = connection.getString();
+
+        assert actualString.equals(expectedString) : "We expected " + expectedString + " but received " + actualString;
+    }
+
+    private class MockConnection extends HttpURLConnection {
+
+        private MockOutputStream m_output;
+
+        public MockConnection(URL url) throws IOException {
+            super(url);
+            m_output = new MockOutputStream();
+        }
+
+        public String getString() {
+            return m_output.getString();
+        }
+
+        @Override
+        public OutputStream getOutputStream() throws IOException {
+            return m_output;
+        }
+
+        @Override
+        public void disconnect() {
+            // Nop
+        }
+
+        @Override
+        public boolean usingProxy() {
+            return false;
+        }
+
+        @Override
+        public void connect() throws IOException {
+            // Nop
+        }
+    }
+
+    private class MockOutputStream extends OutputStream {
+        byte[] bytes = new byte[8*1024];
+        int count = 0;
+        @Override
+        public void write(int arg0) throws IOException {
+            bytes[count] = (byte) arg0;
+            count++;
+        }
+
+        public String getString() {
+            return new String(bytes, 0, count);
+        }
+    }
+
+}