ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject svn commit: r1635133 - in /ace/trunk: org.apache.ace.feedback.common/src/org/apache/ace/feedback/ org.apache.ace.log/src/org/apache/ace/log/ org.apache.ace.log/src/org/apache/ace/log/server/servlet/ org.apache.ace.log/src/org/apache/ace/log/server/stor...
Date Wed, 29 Oct 2014 13:53:40 GMT
Author: marrs
Date: Wed Oct 29 13:53:39 2014
New Revision: 1635133

URL: http://svn.apache.org/r1635133
Log:
ACE-490 ACE-491 Initial implementation and tests of setting the lowest ID to store in a log store. Also for synchronizing such settings.

Added:
    ace/trunk/org.apache.ace.feedback.common/src/org/apache/ace/feedback/LowestID.java
Modified:
    ace/trunk/org.apache.ace.log/src/org/apache/ace/log/LogSync.java
    ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/servlet/LogServlet.java
    ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/LogStore.java
    ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/impl/LogStoreImpl.java
    ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/mongo/MongoLogStore.java
    ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/Activator.java
    ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/LogSyncTask.java
    ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/servlet/LogServletTest.java
    ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/store/impl/ServerLogStoreTester.java
    ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/task/LogTaskTest.java

Added: ace/trunk/org.apache.ace.feedback.common/src/org/apache/ace/feedback/LowestID.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.feedback.common/src/org/apache/ace/feedback/LowestID.java?rev=1635133&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.feedback.common/src/org/apache/ace/feedback/LowestID.java (added)
+++ ace/trunk/org.apache.ace.feedback.common/src/org/apache/ace/feedback/LowestID.java Wed Oct 29 13:53:39 2014
@@ -0,0 +1,73 @@
+/*
+ * 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.feedback;
+
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+import org.apache.ace.feedback.util.Codec;
+
+/**
+ * Instances of this class represent a lowest ID for a specific target and store ID.
+ */
+public class LowestID {
+	private final String m_targetID;
+    private final long m_storeID;
+    private final long m_lowestID;
+
+	public LowestID(String targetID, long storeID, long lowestID) {
+		m_targetID = targetID;
+		m_storeID = storeID;
+		m_lowestID = lowestID;
+	}
+	
+	public LowestID(String representation) {
+        try {
+            StringTokenizer st = new StringTokenizer(representation, ",");
+            m_targetID = Codec.decode(st.nextToken());
+            m_storeID = Long.parseLong(st.nextToken());
+            m_lowestID = Long.parseLong(st.nextToken());
+        }
+        catch (NoSuchElementException e) {
+            throw new IllegalArgumentException("Could not create lowest ID object from: " + representation);
+        }
+	}
+
+	public String getTargetID() {
+		return m_targetID;
+	}
+
+	public long getStoreID() {
+		return m_storeID;
+	}
+
+	public long getLowestID() {
+		return m_lowestID;
+	}
+	
+    public String toRepresentation() {
+        StringBuffer result = new StringBuffer();
+        result.append(Codec.encode(m_targetID));
+        result.append(',');
+        result.append(m_storeID);
+        result.append(',');
+        result.append(m_lowestID);
+        return result.toString();
+    }
+}

Modified: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/LogSync.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/LogSync.java?rev=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/LogSync.java (original)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/LogSync.java Wed Oct 29 13:53:39 2014
@@ -53,6 +53,13 @@ public interface LogSync
      * <code>false</code> otherwise.
      */
     public boolean pushpull() throws IOException;
+    
+    /** Pushes lowest IDs to remote repository. */
+    public boolean pushIDs() throws IOException;
+    /** Pulls lowest IDs from remote repository. */
+    public boolean pullIDs() throws IOException;
+    /** Pushes and pulls lowest IDs to/from remote repository. */
+    public boolean pushpullIDs() throws IOException;
 
     /**
      * Returns the name of the log 'channel' this log sync task is assigned to.

Modified: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/servlet/LogServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/servlet/LogServlet.java?rev=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/servlet/LogServlet.java (original)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/servlet/LogServlet.java Wed Oct 29 13:53:39 2014
@@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletRes
 import org.apache.ace.authentication.api.AuthenticationService;
 import org.apache.ace.feedback.Descriptor;
 import org.apache.ace.feedback.Event;
+import org.apache.ace.feedback.LowestID;
 import org.apache.ace.log.server.store.LogStore;
 import org.apache.ace.range.SortedRangeSet;
 import org.osgi.service.log.LogService;
@@ -57,6 +58,10 @@ import org.osgi.service.useradmin.User;
  * http://host:port/auditlog/receive - Return all known events
  * http://host:port/auditlog/receive?tid=myid - Return all known events belonging to the specified target ID
  * http://host:port/auditlog/receive?tid=myid&logid=2374623874 - Return all known events belonging to the specified target ID
+ * 
+ * Similarly, you can also send/receive lowest IDs for the logs:
+ * http://host:port/auditlog/sendids
+ * http://host:port/auditlog/receiveids
  *
  * If the request is not correctly formatted or other problems arise error code <code>HttpServletResponse.SC_NOT_FOUND</code> will be sent in the response.
  */
@@ -71,6 +76,8 @@ public class LogServlet extends HttpServ
     private static final String QUERY = "/query";
     private static final String SEND = "/send";
     private static final String RECEIVE = "/receive";
+    private static final String SEND_IDS = "/sendids";
+    private static final String RECEIVE_IDS = "/receiveids";
 
     // url parameter keys
     private static final String TARGETID_KEY = "tid";
@@ -100,16 +107,17 @@ public class LogServlet extends HttpServ
             if (SEND.equals(path) && !handleSend(request.getInputStream())) {
                 sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Could not construct a log event for all events received");
             }
+            else if (SEND_IDS.equals(path) && !handleSendIDs(request.getInputStream())) {
+                sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Could not set lowest IDs for all logs received");
+            }
         }
         catch (IOException e) {
-            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error processing received log events");
+            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error processing post request");
         }
     }
 
     @Override
     protected void doGet(HttpServletRequest request, HttpServletResponse response) {
-        // 'query' and 'receive' calls are GET calls
-
         String path = request.getPathInfo();
         String targetID = request.getParameter(TARGETID_KEY);
         String logID = request.getParameter(LOGID_KEY);
@@ -126,11 +134,14 @@ public class LogServlet extends HttpServ
                 sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Unable to interpret query");
             }
             else if (RECEIVE.equals(path) && !handleReceive(targetID, logID, range, filter, output)) {
-                sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Unable to interpret receive query");
+                sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Unable to interpret receive request");
+            }
+            else if (RECEIVE_IDS.equals(path) && !handleReceiveIDs(targetID, logID, filter, output)) {
+                sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Unable to interpret receiveids request");
             }
         }
         catch (IOException e) {
-            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to process query");
+            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to process request", e);
         }
         finally {
             try {
@@ -144,9 +155,6 @@ public class LogServlet extends HttpServ
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         if (!authenticate(req)) {
@@ -269,6 +277,73 @@ public class LogServlet extends HttpServ
         m_store.put(events);
         return success;
     }
+    
+    // Handle a call to the send IDs 'command'
+    protected boolean handleSendIDs(ServletInputStream input) throws IOException {
+        boolean success = true;
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(input));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                try {
+                	LowestID lid = new LowestID(line);
+                	m_log.log(LogService.LOG_DEBUG, "Lowest ID event received: '" + line +"'");
+                	m_store.setLowestID(lid.getTargetID(), lid.getStoreID(), lid.getLowestID());
+                }
+                catch (IllegalArgumentException iae) {
+                    success = false;
+                    m_log.log(LogService.LOG_WARNING, "Could not construct lowest ID from string: '" + line + "'");
+                }
+            }
+        }
+        finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                }
+                catch (Exception ex) {
+                    // not much we can do
+                }
+            }
+        }
+        return success;
+    }
+    
+    // Handle a call to the receive 'command'
+    protected boolean handleReceiveIDs(String targetID, String logID, String filter, ServletOutputStream output) throws IOException {
+        if ((targetID != null) && (logID != null)) {
+            // target and log id are specified, return only the lowest ID that matches these id's
+    		long logid = Long.parseLong(logID);
+        	outputLowestID(targetID, logid, output);
+            return true;
+        }
+        else if ((targetID != null) && (logID == null)) {
+            // target id is specified, log id is not, return all events that belong to the specified target id
+            List<Descriptor> descriptors = m_store.getDescriptors(targetID);
+            for (Descriptor descriptor : descriptors) {
+                outputLowestID(targetID, descriptor.getStoreID(), output);
+            }
+            return true;
+        }
+        else if ((targetID == null) && (logID == null)) {
+            // no target or log id has been specified, return all events
+            List<Descriptor> descriptors = m_store.getDescriptors();
+            for (Descriptor descriptor : descriptors) {
+                outputLowestID(descriptor.getTargetID(), descriptor.getStoreID(), output);
+            }
+            return true;
+        }
+        return false;
+    }
+
+	private void outputLowestID(String targetID, long logID, ServletOutputStream output) throws IOException {
+		long lowestID = m_store.getLowestID(targetID, logID);
+		if (lowestID > 0) {
+			LowestID lid = new LowestID(targetID, logID, lowestID);
+			output.print(lid.toRepresentation() + "\n");
+		}
+	}
 
     // print string representations of all events in the specified range to the specified output
     private void outputRange(ServletOutputStream output, Descriptor range) throws IOException {
@@ -280,7 +355,16 @@ public class LogServlet extends HttpServ
 
     // send an error response
     private void sendError(HttpServletResponse response, int statusCode, String description) {
-        m_log.log(LogService.LOG_WARNING, "Log request failed: " + description);
+    	sendError(response, statusCode, description, null);
+    }
+    
+    private void sendError(HttpServletResponse response, int statusCode, String description, Throwable t) {
+    	if (t == null) {
+    		m_log.log(LogService.LOG_WARNING, "Log request failed: " + description);
+    	}
+    	else {
+    		m_log.log(LogService.LOG_WARNING, "Log request failed: " + description, t);
+    	}
         try {
             response.sendError(statusCode, description);
         }

Modified: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/LogStore.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/LogStore.java?rev=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/LogStore.java (original)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/LogStore.java Wed Oct 29 13:53:39 2014
@@ -113,4 +113,7 @@ public interface LogStore
      * @throws java.io.IOException in case of any IO error.
      */
     public Event put(String targetID, int type, Dictionary props) throws IOException;
+    
+    public void setLowestID(String targetID, long logID, long lowestID) throws IOException;
+    public long getLowestID(String targetID, long logID) throws IOException;
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/impl/LogStoreImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/impl/LogStoreImpl.java?rev=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/impl/LogStoreImpl.java (original)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/impl/LogStoreImpl.java Wed Oct 29 13:53:39 2014
@@ -22,6 +22,7 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
+import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
@@ -62,7 +63,8 @@ public class LogStoreImpl implements Log
     private int m_maxEvents = 0;
 
     private final ConcurrentMap<String, Set<Long>> m_locks = new ConcurrentHashMap<String, Set<Long>>();
-    private final Map<String, Long> m_fileToID = new HashMap<String, Long>();
+    private final Map<String, Long> m_fileToHighestID = new HashMap<String, Long>();
+    private final Map<String, Long> m_fileToLowestID = new HashMap<String, Long>();
 
     public LogStoreImpl(File baseDir, String name) {
         m_name = name;
@@ -103,7 +105,7 @@ public class LogStoreImpl implements Log
         final SortedRangeSet set = descriptor.getRangeSet();
         BufferedReader in = null;
         try {
-            File log = new File(new File(m_dir, targetIDToFilename(descriptor.getTargetID())), String.valueOf(descriptor.getStoreID()));
+            File log = getLogFile(descriptor.getTargetID(), descriptor.getStoreID());
             if (!log.isFile()) {
                 return result;
             }
@@ -119,15 +121,15 @@ public class LogStoreImpl implements Log
                 else {
                     counter = -1;
                 }
-                if (set.contains(id)) {
+                if (set.contains(id) && id >= getLowestIDInternal(descriptor.getTargetID(), descriptor.getStoreID())) {
                     result.add(event);
                 }
             }
             if (counter < 1) {
-                m_fileToID.remove(file);
+                m_fileToHighestID.remove(file);
             }
             else {
-                m_fileToID.put(file, counter);
+                m_fileToHighestID.put(file, counter);
             }
         }
         finally {
@@ -152,7 +154,7 @@ public class LogStoreImpl implements Log
     }
     
     private Descriptor getDescriptorInternal(String targetID, long logID, boolean lock) throws IOException {
-        Long high = m_fileToID.get(new File(new File(m_dir, targetIDToFilename(targetID)), String.valueOf(logID)).getAbsolutePath());
+        Long high = m_fileToHighestID.get(getLogFile(targetID, logID).getAbsolutePath());
         if (high != null) {
             Range r = new Range(1, high);
             return new Descriptor(targetID, logID, new SortedRangeSet(r.toRepresentation()));
@@ -169,20 +171,20 @@ public class LogStoreImpl implements Log
     }
 
     public List<Descriptor> getDescriptors(String targetID) throws IOException {
-        File dir = new File(m_dir, targetIDToFilename(targetID));
+        File dir = getTargetDirectory(targetID);
         List<Descriptor> result = new ArrayList<Descriptor>();
         if (!dir.isDirectory()) {
             return result;
         }
 
-        for (String name : notNull(dir.list())) {
+        for (String name : notNull(dir.list(LOGID_FILENAME_FILTER))) {
             result.add(getDescriptor(targetID, Long.parseLong(name)));
         }
 
         return result;
     }
 
-    public List<Descriptor> getDescriptors() throws IOException {
+	public List<Descriptor> getDescriptors() throws IOException {
         List<Descriptor> result = new ArrayList<Descriptor>();
         for (String name : notNull(m_dir.list())) {
             result.addAll(getDescriptors(filenameToTargetID(name)));
@@ -227,8 +229,8 @@ public class LogStoreImpl implements Log
         // 1. we can append events at the end of the existing file
         // 2. we need to insert events in the existing file (meaning we have to
         // rewrite basically the whole file)
-        String file = new File(new File(m_dir, targetIDToFilename(targetID)), String.valueOf(logID)).getAbsolutePath();
-        Long highest = m_fileToID.get(file);
+        String file = getLogFile(targetID, logID).getAbsolutePath();
+        Long highest = m_fileToHighestID.get(file);
         boolean cached = false;
         if (highest != null) {
             if (highest.longValue() + 1 == list.get(0).getID()) {
@@ -255,7 +257,7 @@ public class LogStoreImpl implements Log
 
         PrintWriter out = null;
         try {
-            File dir = new File(m_dir, targetIDToFilename(targetID));
+            File dir = getTargetDirectory(targetID);
             if (!dir.isDirectory() && !dir.mkdirs()) {
                 throw new IOException("Unable to create backup store.");
             }
@@ -291,10 +293,10 @@ public class LogStoreImpl implements Log
                 m_eventAdmin.postEvent(new org.osgi.service.event.Event(LogStore.EVENT_TOPIC, props));
             }
             if ((cached) && (high < Long.MAX_VALUE)) {
-                m_fileToID.put(file, new Long(high));
+                m_fileToHighestID.put(file, new Long(high));
             }
             else {
-                m_fileToID.remove(file);
+                m_fileToHighestID.remove(file);
             }
         }
         finally {
@@ -307,6 +309,27 @@ public class LogStoreImpl implements Log
         }
     }
 
+	private void createTargetDirectory(String targetID) throws IOException {
+		File directory = getTargetDirectory(targetID);
+		if (!directory.isDirectory()) {
+			if (!directory.mkdirs()) {
+				throw new IOException("Could not create directory: " + directory.getAbsolutePath());
+			}
+		}
+	}
+
+	private File getTargetDirectory(String targetID) {
+		return new File(m_dir, targetIDToFilename(targetID));
+	}
+
+	private File getLogFile(String targetID, Long logID) {
+		return new File(getTargetDirectory(targetID), String.valueOf(logID));
+	}
+
+	private File getLogFileIndex(String targetID, Long logID) {
+		return new File(getTargetDirectory(targetID), String.valueOf(logID) + ".index");
+	}
+
     /**
      * Sort the given list of events into a map of maps according to the targetID and the logID of each event.
      * 
@@ -528,4 +551,87 @@ public class LogStoreImpl implements Log
         	releaseLock(targetID, storeID);
         }
     }
+    
+    @Override
+    public void setLowestID(String targetID, long logID, long lowestID) throws IOException {
+        obtainLock(targetID, logID);
+        try {
+	        long currentID = getLowestIDInternal(targetID, logID);
+	        if (currentID < lowestID) {
+		        FileWriter fw = null;
+		        try {
+		        	createTargetDirectory(targetID);
+		        	File index = getLogFileIndex(targetID, logID);
+					fw = new FileWriter(index);
+		        	fw.write(Long.toString(lowestID));
+		        	m_fileToLowestID.put(index.getAbsolutePath(), lowestID);
+		        }
+		        finally {
+	    			if (fw != null) {
+	    				try {
+	    					fw.close();
+	    				}
+	    				catch (IOException ioe) {}
+	    			}
+		        }
+	        }
+        }
+        finally {
+            releaseLock(targetID, logID);
+		}
+    }
+    
+    public long getLowestID(String targetID, long logID) throws IOException {
+        obtainLock(targetID, logID);
+        try {
+        	return getLowestIDInternal(targetID, logID);
+        }
+        finally {
+            releaseLock(targetID, logID);
+        }
+    }
+    
+    private long getLowestIDInternal(String targetID, long logID) {
+    	File index = getLogFileIndex(targetID, logID);
+    	Long result = m_fileToLowestID.get(index.getAbsolutePath());
+    	if (result == null) {
+    		BufferedReader br = null;
+    		try {
+				br = new BufferedReader(new FileReader(index));
+    			String line = br.readLine();
+    			br.close();
+    			result = Long.parseLong(line);
+    			m_fileToLowestID.put(index.getAbsolutePath(), result);
+    		}
+    		catch (Exception nfe) {
+    			// if the file somehow got corrupted, or does not exist,
+    			// we simply assume 0 as the default
+    			m_fileToLowestID.put(index.getAbsolutePath(), 0L);
+    			return 0L;
+    		}
+    		finally {
+    			if (br != null) {
+    				try {
+						br.close();
+					}
+    				catch (IOException e) {}
+    			}
+    		}
+    	}
+    	return result;
+    }
+
+    private static FilenameFilter LOGID_FILENAME_FILTER = new LogIDFilenameFilter();
+    private static class LogIDFilenameFilter implements FilenameFilter {
+		@Override
+		public boolean accept(File dir, String name) {
+			try {
+				Long.parseLong(name);
+				return true;
+			}
+			catch (NumberFormatException nfe) {
+				return false;
+			}
+		}
+    }
 }

Modified: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/mongo/MongoLogStore.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/mongo/MongoLogStore.java?rev=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/mongo/MongoLogStore.java (original)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/store/mongo/MongoLogStore.java Wed Oct 29 13:53:39 2014
@@ -193,4 +193,16 @@ public class MongoLogStore implements Lo
     	// TODO add an event to the appropriate store
     	return null;
     }
+
+	@Override
+	public void setLowestID(String targetID, long logID, long lowestID) throws IOException {
+		// TODO Auto-generated method stub
+		
+	}
+	
+	@Override
+	public long getLowestID(String targetID, long logID) throws IOException {
+		// TODO Auto-generated method stub
+		return 0;
+	}
 }

Modified: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/Activator.java?rev=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/Activator.java (original)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/Activator.java Wed Oct 29 13:53:39 2014
@@ -40,6 +40,7 @@ import org.osgi.service.log.LogService;
 public class Activator extends DependencyActivatorBase implements ManagedServiceFactory {
     private static final String KEY_LOG_NAME = "name";
     private static final String KEY_MODE = "mode";
+    private static final String KEY_MODE_LOWEST_IDS = "mode-lowest-ids";
     private static final String KEY_TARGETID = "tid";
     
     private final Map<String, Component> m_instances = new HashMap<String, Component>();
@@ -69,13 +70,27 @@ public class Activator extends Dependenc
         if ((name == null) || "".equals(name)) {
             throw new ConfigurationException(KEY_LOG_NAME, "Log name has to be specified.");
         }
-        Mode mode = Mode.PUSH;
+        Mode dataTransferMode = Mode.PUSH;
         String modeValue = (String) dict.get(KEY_MODE);
         if ("pull".equals(modeValue)) {
-        	mode = Mode.PULL;
+        	dataTransferMode = Mode.PULL;
         }
         else if ("pushpull".equals(modeValue)) {
-        	mode = Mode.PUSHPULL;
+        	dataTransferMode = Mode.PUSHPULL;
+        }
+        else if ("none".equals(modeValue)) {
+        	dataTransferMode = Mode.NONE;
+        }
+        Mode lowestIDsMode = Mode.NONE;
+        modeValue = (String) dict.get(KEY_MODE_LOWEST_IDS);
+        if ("pull".equals(modeValue)) {
+        	lowestIDsMode = Mode.PULL;
+        }
+        else if ("pushpull".equals(modeValue)) {
+        	lowestIDsMode = Mode.PUSHPULL;
+        }
+        else if ("push".equals(modeValue)) {
+        	lowestIDsMode = Mode.PUSH;
         }
         String targetID = (String) dict.get(KEY_TARGETID);
 
@@ -84,9 +99,9 @@ public class Activator extends Dependenc
         Properties props = new Properties();
         props.put(KEY_LOG_NAME, name);
         props.put("taskName", LogSyncTask.class.getName());
-        props.put("description", "Syncs log (name=" + name + ", mode=" + mode.toString() + (targetID == null ? "" : ", targetID=" + targetID) + ") with a server.");
+        props.put("description", "Syncs log (name=" + name + ", mode=" + dataTransferMode.toString() + (targetID == null ? "" : ", targetID=" + targetID) + ") with a server.");
         String filter = "(&(" + Constants.OBJECTCLASS + "=" + LogStore.class.getName() + ")(name=" + name + "))";
-        LogSyncTask service = new LogSyncTask(name, name, mode, targetID);
+        LogSyncTask service = new LogSyncTask(name, name, dataTransferMode, lowestIDsMode, targetID);
         newComponent = m_manager.createComponent()
     		.setInterface(new String[] { Runnable.class.getName(), LogSync.class.getName() }, props)
     		.setImplementation(service)

Modified: ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/LogSyncTask.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/LogSyncTask.java?rev=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/LogSyncTask.java (original)
+++ ace/trunk/org.apache.ace.log/src/org/apache/ace/log/server/task/LogSyncTask.java Wed Oct 29 13:53:39 2014
@@ -41,8 +41,8 @@ import org.apache.ace.connectionfactory.
 import org.apache.ace.discovery.Discovery;
 import org.apache.ace.feedback.Descriptor;
 import org.apache.ace.feedback.Event;
+import org.apache.ace.feedback.LowestID;
 import org.apache.ace.log.LogSync;
-import org.apache.ace.log.server.servlet.LogServlet;
 import org.apache.ace.log.server.store.LogStore;
 import org.apache.ace.range.SortedRangeSet;
 import org.osgi.service.log.LogService;
@@ -50,13 +50,14 @@ import org.osgi.service.log.LogService;
 public class LogSyncTask implements Runnable, LogSync {
 
     public static enum Mode {
-        PUSH, PULL, PUSHPULL
+        NONE, PUSH, PULL, PUSHPULL
     }
 
     private static final String COMMAND_QUERY = "query";
     private static final String COMMAND_SEND = "send";
-
+    private static final String COMMAND_SEND_IDS = "sendids";
     private static final String COMMAND_RECEIVE = "receive";
+    private static final String COMMAND_RECEIVE_IDS = "receiveids";
     private static final String TARGETID_KEY = "tid";
     @SuppressWarnings("unused")
     private static final String FILTER_KEY = "filter";
@@ -72,16 +73,18 @@ public class LogSyncTask implements Runn
     private final String m_endpoint;
     private final String m_name;
     private final String m_targetID;
-    private final Mode m_mode;
+    private final Mode m_dataTransferMode;
+    private final Mode m_lowestIDMode;
 
-    public LogSyncTask(String endpoint, String name, Mode mode) {
-    	this(endpoint, name, mode, null);
+    public LogSyncTask(String endpoint, String name, Mode dataTransferMode, Mode lowestIDMode) {
+    	this(endpoint, name, dataTransferMode, lowestIDMode, null);
     }
 
-    public LogSyncTask(String endpoint, String name, Mode mode, String targetID) {
+    public LogSyncTask(String endpoint, String name, Mode dataTransferMode, Mode lowestIDMode, String targetID) {
         m_endpoint = endpoint;
         m_name = name;
-        m_mode = mode;
+        m_dataTransferMode = dataTransferMode;
+        m_lowestIDMode = lowestIDMode;
         m_targetID = targetID;
     }
 
@@ -100,10 +103,49 @@ public class LogSyncTask implements Runn
     public boolean pushpull() throws IOException {
         return synchronize(true /* push */, true /* pull */);
     }
+    
+    public boolean pullIDs() throws IOException {
+    	return synchronizeLowestIDs(false, true);
+    }
+
+    public boolean pushIDs() throws IOException {
+    	return synchronizeLowestIDs(true, false);
+    }
+
+    public boolean pushpullIDs() throws IOException {
+    	return synchronizeLowestIDs(true, true);
+    }
 
     public void run() {
         try {
-            switch (m_mode) {
+            switch (m_lowestIDMode) {
+	            case NONE:
+	            	break;
+                case PULL:
+                    pullIDs();
+                    break;
+                case PUSH:
+                    pushIDs();
+                    break;
+                case PUSHPULL:
+                    pushpullIDs();
+                    break;
+            }
+        }
+        catch (MalformedURLException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to (" + m_lowestIDMode + ") synchronize IDs (name=" + m_name + ") with remote (malformed URL, incorrect configuration?)", e);
+        }
+        catch (ConnectException e) {
+            m_log.log(LogService.LOG_WARNING, "Unable to (" + m_lowestIDMode + ") synchronize IDs (name=" + m_name + ") with remote (connection refused, remote not up?)", e);
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to (" + m_lowestIDMode + ") synchronize IDs (name=" + m_name + ") with remote", e);
+        }
+
+        try {
+            switch (m_dataTransferMode) {
+	            case NONE:
+	            	break;
                 case PULL:
                     pull();
                     break;
@@ -116,13 +158,13 @@ public class LogSyncTask implements Runn
             }
         }
         catch (MalformedURLException e) {
-            m_log.log(LogService.LOG_ERROR, "Unable to (" + m_mode.toString() + ") synchronize log (name=" + m_name + ") with remote (malformed URL, incorrect configuration?)");
+            m_log.log(LogService.LOG_ERROR, "Unable to (" + m_dataTransferMode + ") synchronize log (name=" + m_name + ") with remote (malformed URL, incorrect configuration?)", e);
         }
         catch (ConnectException e) {
-            m_log.log(LogService.LOG_WARNING, "Unable to (" + m_mode.toString() + ") synchronize log (name=" + m_name + ") with remote (connection refused, remote not up?)");
+            m_log.log(LogService.LOG_WARNING, "Unable to (" + m_dataTransferMode + ") synchronize log (name=" + m_name + ") with remote (connection refused, remote not up?)", e);
         }
         catch (IOException e) {
-            m_log.log(LogService.LOG_ERROR, "Unable to (" + m_mode.toString() + ") synchronize log (name=" + m_name + ") with remote", e);
+            m_log.log(LogService.LOG_ERROR, "Unable to (" + m_dataTransferMode + ") synchronize log (name=" + m_name + ") with remote", e);
         }
     }
 
@@ -402,4 +444,119 @@ public class LogSyncTask implements Runn
 
         return result;
     }
+    
+    private boolean synchronizeLowestIDs(boolean push, boolean pull) throws IOException {
+        URL host = m_discovery.discover();
+    	
+        boolean result = false;
+        if (push) {
+            result |= doPushLowestIDs(host);
+        }
+        if (pull) {
+            result |= doPullLowestIDs(host);
+        }
+
+        return result;
+    }
+    
+    protected boolean doPushLowestIDs(URL host) {
+    	boolean result = false;
+        OutputStream sendOutput = null;
+        HttpURLConnection sendConnection = null;
+
+        try {
+            sendConnection = createConnection(createURL(host, COMMAND_SEND_IDS));
+            // 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...
+            sendConnection.setChunkedStreamingMode(8192);
+            sendConnection.setDoOutput(true);
+            sendOutput = sendConnection.getOutputStream();
+            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(sendOutput));
+            try {
+            	for (Descriptor d : (m_targetID == null ? m_logStore.getDescriptors() : m_logStore.getDescriptors(m_targetID))) {
+            		long lowestID = m_logStore.getLowestID(d.getTargetID(), d.getStoreID());
+            		if (lowestID > 0) {
+            			LowestID lid = new LowestID(d.getTargetID(), d.getStoreID(), lowestID);
+            			writer.write(lid.toRepresentation() + "\n");
+            		}
+            	}
+            }
+            finally {
+                writer.close();
+            }
+
+            // Will cause a flush and reads the response from the server...
+            int rc = sendConnection.getResponseCode();
+            result = (rc == HttpServletResponse.SC_OK);
+
+            if (!result) {
+                String msg = sendConnection.getResponseMessage();
+                m_log.log(LogService.LOG_WARNING, String.format("Could not push lowest IDs '%s'. Server response: %s (%d)", m_name, msg, rc));
+            }
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to (fully) push lowest IDs with remote", e);
+        }
+        finally {
+            closeSilently(sendOutput);
+            closeSilently(sendConnection);
+        }
+        if (result) {
+            m_log.log(LogService.LOG_DEBUG, "Pushed lowest IDs (" + m_name + ") successfully to remote...");
+        }
+        return result;
+    }
+    
+    protected boolean doPullLowestIDs(URL host) {
+        boolean result = false;
+        InputStream receiveInput = null;
+        HttpURLConnection receiveConnection = null;
+        try {
+            URL url = createURL(host, COMMAND_RECEIVE_IDS);
+
+            receiveConnection = createConnection(url);
+            receiveInput = receiveConnection.getInputStream();
+
+            BufferedReader reader = new BufferedReader(new InputStreamReader(receiveInput));
+            try {
+            	String line;
+                while ((line = reader.readLine()) != null) {
+                    try {
+                    	LowestID lid = new LowestID(line);
+                		m_logStore.setLowestID(lid.getTargetID(), lid.getStoreID(), lid.getLowestID());
+                    }
+                    catch (IllegalArgumentException e) {
+                        // Just skip this one.
+                    	m_log.log(LogService.LOG_WARNING, "Could not parse incoming line: " + line + " because: " + e.getMessage(), e);
+                    }
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_DEBUG, "Error reading line from reader", e);
+            }
+            finally {
+                reader.close();
+            }
+
+            int rc = receiveConnection.getResponseCode();
+            result = (rc == HttpServletResponse.SC_OK);
+
+            if (!result) {
+                String msg = receiveConnection.getResponseMessage();
+                m_log.log(LogService.LOG_WARNING, String.format("Could not receive lowest IDs '%s'. Server response: %s (%d)", m_name, msg, rc));
+            }
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to connect to receive lowest IDs.", e);
+        }
+        finally {
+            closeSilently(receiveInput);
+            closeSilently(receiveConnection);
+        }
+        if (result) {
+            m_log.log(LogService.LOG_DEBUG, "Pulled lowest IDs (" + m_name + ") successfully from remote...");
+        }
+        return result;
+    }
 }

Modified: 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=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/servlet/LogServletTest.java (original)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/servlet/LogServletTest.java Wed Oct 29 13:53:39 2014
@@ -22,10 +22,11 @@ import static org.apache.ace.test.utils.
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Dictionary;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import javax.servlet.ServletInputStream;
 import javax.servlet.ServletOutputStream;
@@ -83,6 +84,36 @@ public class LogServletTest {
         assert result;
         assert (m_range.toRepresentation() + "\n").equals(output.m_text);
     }
+    
+    @Test(groups = { UNIT })
+    public void receiveLowestID() throws Exception {
+    	// no lowest ID set
+        MockServletOutputStream output = new MockServletOutputStream();
+        boolean result = m_logServlet.handleReceiveIDs(m_range.getTargetID(), String.valueOf(m_range.getStoreID()), null, output);
+        assert result;
+        String expected = "";
+        String actual = output.m_text;
+        assert expected.equals(actual) : "We expected '" + expected + "', but received '" + actual + "'";
+        
+        // set lowest ID
+        m_mockStore.setLowestID(m_range.getTargetID(), m_range.getStoreID(), 5);
+        output = new MockServletOutputStream();
+        result = m_logServlet.handleReceiveIDs(m_range.getTargetID(), String.valueOf(m_range.getStoreID()), null, output);
+        assert result;
+        expected = m_range.getTargetID() + "," + m_range.getStoreID() + ",5\n";
+        actual = output.m_text;
+        assert expected.equals(actual) : "We expected '" + expected + "', but received '" + actual + "'";
+    }
+
+    @Test(groups = { UNIT })
+    public void sendLowestID() throws Exception {
+        MockServletInputStream input = new MockServletInputStream();
+        String expected = m_range.getTargetID() + "," + m_range.getStoreID() + ",9\n";
+        input.setBytes(expected.getBytes());
+        m_logServlet.handleSendIDs(input);
+        long lowestID = m_mockStore.getLowestID(m_range.getTargetID(), m_range.getStoreID());
+		assert 9 == lowestID : "Expected lowest ID to be 9, but got: " + lowestID;
+    }
 
     @Test(groups = { UNIT })
     public void receiveLog() throws Exception {
@@ -116,6 +147,46 @@ public class LogServletTest {
         assert expected.equals(actual);
     }
 
+    private static class Tuple {
+    	private final String m_targetID;
+    	private final long m_logID;
+    	public Tuple(String targetID, long logID) {
+    		if (targetID == null) {
+    			throw new IllegalArgumentException("TargetID cannot be null");
+    		}
+			m_targetID = targetID;
+			m_logID = logID;
+		}
+		public String getTargetID() {
+			return m_targetID;
+		}
+		public long getLogID() {
+			return m_logID;
+		}
+		@Override
+		public int hashCode() {
+			final int prime = 31;
+			int result = 1;
+			result = prime * result + (int) (m_logID ^ (m_logID >>> 32));
+			result = prime * result + m_targetID.hashCode();
+			return result;
+		}
+		@Override
+		public boolean equals(Object obj) {
+			if (this == obj) {
+				return true;
+			}
+			if (obj == null) {
+				return false;
+			}
+			if (getClass() != obj.getClass()) {
+				return false;
+			}
+			Tuple other = (Tuple) obj;
+			return (m_logID == other.getLogID() && m_targetID.equals(other.getTargetID()));
+		}
+    }
+
     private class MockLogStore implements LogStore {
         public List<Event> m_events = new ArrayList<Event>();
 
@@ -152,6 +223,17 @@ public class LogServletTest {
         public Event put(String targetID, int type, Dictionary props) throws IOException {
             throw new UnsupportedOperationException("not implemented");
         }
+        private Map<Tuple, Long> m_lowestIDs = new HashMap<>();
+        
+		@Override
+		public void setLowestID(String targetID, long logID, long lowestID) throws IOException {
+			m_lowestIDs.put(new Tuple(targetID, logID), lowestID);
+		}
+		@Override
+		public long getLowestID(String targetID, long logID) throws IOException {
+			Long result  = m_lowestIDs.get(new Tuple(targetID, logID));
+			return result == null ? 0 : result.longValue();
+		}
     }
 
     private class MockServletOutputStream extends ServletOutputStream {

Modified: 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=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/store/impl/ServerLogStoreTester.java (original)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/store/impl/ServerLogStoreTester.java Wed Oct 29 13:53:39 2014
@@ -82,12 +82,7 @@ public class ServerLogStoreTester {
         }
         m_logStore.put(events);
         assert m_logStore.getDescriptors().size() == 3 * 4 : "Incorrect amount of ranges returned from store";
-        List<Event> stored = new ArrayList<Event>();
-        for (Descriptor range : m_logStore.getDescriptors()) {
-            for (Descriptor range2 : m_logStore.getDescriptors(range.getTargetID())) {
-                stored.addAll(m_logStore.get(m_logStore.getDescriptor(range2.getTargetID(), range2.getStoreID())));
-            }
-        }
+        List<Event> stored = getStoredEvents();
 
         Set<String> in = new HashSet<String>();
         for (Event event : events) {
@@ -100,6 +95,52 @@ public class ServerLogStoreTester {
         assert in.equals(out) : "Stored events differ from the added.";
     }
 
+    
+    @SuppressWarnings("serial")
+    @Test(groups = { UNIT })
+    public void testLogLowestID() throws IOException {
+        Map<String, String> props = new HashMap<String, String>();
+        props.put("test", "bar");
+
+        List<Descriptor> ranges = m_logStore.getDescriptors();
+        assert ranges.isEmpty() : "New store should have no ranges.";
+        List<Event> events = new ArrayList<Event>();
+
+        assert 0 == m_logStore.getLowestID("target", 1) : "Lowest ID should be 0 by default, not: " + m_logStore.getLowestID("target", 1);
+        m_logStore.setLowestID("target", 1, 10);
+        assert 10 == m_logStore.getLowestID("target", 1) : "Lowest ID should be 10, not: " + m_logStore.getLowestID("target", 1);
+        assert 0 == m_logStore.getLowestID("target", 0) : "Lowest ID should be 0 by default, not: " + m_logStore.getLowestID("target", 1);
+        assert 0 == m_logStore.getLowestID("target2", 1) : "Lowest ID should be 0 by default, not: " + m_logStore.getLowestID("target", 1);
+
+        for (long id = 0; id < 20; id++) {
+            events.add(new Event("target", 1, id, System.currentTimeMillis(), AuditEvent.FRAMEWORK_STARTED, props));
+        }
+        m_logStore.put(events);
+        assert m_logStore.getDescriptors().size() == 1 : "Incorrect amount of ranges returned from store";
+        List<Event> stored = getStoredEvents();
+        assert stored.size() == 10 : "Exactly 10 events should have been stored";
+        m_logStore.setLowestID("target", 1, 19);
+        stored = getStoredEvents();
+        assert stored.size() == 1 : "Exactly 1 event should have been stored";
+        m_logStore.setLowestID("target", 1, 20);
+        stored = getStoredEvents();
+        assert stored.size() == 0 : "No events should have been stored";
+        m_logStore.setLowestID("target", 1, 100);
+        stored = getStoredEvents();
+        assert stored.size() == 0 : "No events should have been stored";
+    }
+
+	private List<Event> getStoredEvents() throws IOException {
+		List<Event> stored = new ArrayList<Event>();
+        for (Descriptor range : m_logStore.getDescriptors()) {
+            for (Descriptor range2 : m_logStore.getDescriptors(range.getTargetID())) {
+                stored.addAll(m_logStore.get(m_logStore.getDescriptor(range2.getTargetID(), range2.getStoreID())));
+            }
+        }
+		return stored;
+	}
+    
+    
     @Test(groups = { UNIT })
     public void testCreateLogMessagesConcurrently() throws Exception {
         final Properties props = new Properties();
@@ -123,12 +164,7 @@ public class ServerLogStoreTester {
         exec.shutdown();
         exec.awaitTermination(10, TimeUnit.SECONDS);
         assert m_logStore.getDescriptors().size() == 10 : "Incorrect amount of ranges returned from store: " + m_logStore.getDescriptors().size();
-        List<Event> stored = new ArrayList<Event>();
-        for (Descriptor range : m_logStore.getDescriptors()) {
-            for (Descriptor range2 : m_logStore.getDescriptors(range.getTargetID())) {
-                stored.addAll(m_logStore.get(m_logStore.getDescriptor(range2.getTargetID(), range2.getStoreID())));
-            }
-        }
+        List<Event> stored = getStoredEvents();
         assert stored.size() == 10000 : "Incorrect number of events got stored: " + stored.size();
     }
     
@@ -270,12 +306,7 @@ public class ServerLogStoreTester {
         es.awaitTermination(60, TimeUnit.SECONDS);
         int size = m_logStore.getDescriptors().size();
         assert size == 3 * 4 : "Incorrect amount of ranges returned from store: " + size;
-        List<Event> stored = new ArrayList<Event>();
-        for (Descriptor range : m_logStore.getDescriptors()) {
-            for (Descriptor range2 : m_logStore.getDescriptors(range.getTargetID())) {
-                stored.addAll(m_logStore.get(m_logStore.getDescriptor(range2.getTargetID(), range2.getStoreID())));
-            }
-        }
+        List<Event> stored = getStoredEvents();
 
         Set<String> out = new HashSet<String>();
         for (Event event : stored) {

Modified: 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=1635133&r1=1635132&r2=1635133&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/task/LogTaskTest.java (original)
+++ ace/trunk/org.apache.ace.log/test/org/apache/ace/log/server/task/LogTaskTest.java Wed Oct 29 13:53:39 2014
@@ -39,7 +39,7 @@ public class LogTaskTest {
         public List<Descriptor> m_calledWith = new ArrayList<Descriptor>();
 
         public MockLogSyncTask(String endpoint, String name) {
-            super(endpoint, name, LogSyncTask.Mode.PUSH);
+            super(endpoint, name, LogSyncTask.Mode.PUSH, LogSyncTask.Mode.NONE);
         }
 
         public void clear() {



Mime
View raw message