ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j...@apache.org
Subject svn commit: r1522863 [1/2] - in /ace/trunk: org.apache.ace.agent.itest/src/org/apache/ace/agent/itest/ org.apache.ace.agent/src/org/apache/ace/agent/ org.apache.ace.agent/src/org/apache/ace/agent/impl/ org.apache.ace.agent/test/org/apache/ace/agent/impl/
Date Fri, 13 Sep 2013 10:54:34 GMT
Author: jawi
Date: Fri Sep 13 10:54:34 2013
New Revision: 1522863

URL: http://svn.apache.org/r1522863
Log:
ACE-323 - smarter handling of installation failures:

- refactored the installation logic to be the same for installing
  deployment packages as well as agent updates;
- introduced a generic interface for handling updates, this way
  we can align the installation of agent updates and DPs;
- no longer leaking OSGi-specific exceptions upon failed
  installations;
- refactored/simplified the download handling.


Added:
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/InstallationFailedException.java   (with props)
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/UpdateHandler.java   (with props)
Modified:
    ace/trunk/org.apache.ace.agent.itest/src/org/apache/ace/agent/itest/AgentDeploymentTest.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/AgentUpdateHandler.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DeploymentHandler.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandle.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandler.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadResult.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/Activator.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/AgentUpdateHandlerImpl.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DefaultController.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DeploymentHandlerImpl.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadCallableImpl.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadHandleImpl.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadHandlerImpl.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadResultImpl.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/InternalConstants.java
    ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/UpdateHandlerBase.java
    ace/trunk/org.apache.ace.agent/test/org/apache/ace/agent/impl/CustomControllerTest.java
    ace/trunk/org.apache.ace.agent/test/org/apache/ace/agent/impl/DeploymentHandlerImplTest.java
    ace/trunk/org.apache.ace.agent/test/org/apache/ace/agent/impl/DownloadHandlerTest.java

Modified: ace/trunk/org.apache.ace.agent.itest/src/org/apache/ace/agent/itest/AgentDeploymentTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.itest/src/org/apache/ace/agent/itest/AgentDeploymentTest.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent.itest/src/org/apache/ace/agent/itest/AgentDeploymentTest.java (original)
+++ ace/trunk/org.apache.ace.agent.itest/src/org/apache/ace/agent/itest/AgentDeploymentTest.java Fri Sep 13 10:54:34 2013
@@ -116,15 +116,15 @@ public class AgentDeploymentTest extends
             m_packages.put(testPackage.getVersion().toString(), testPackage);
         }
 
-        public synchronized void setFailure(Failure failure) {
-            m_failure = failure;
-        }
-
         public synchronized void reset() {
             m_failure = null;
             m_packages.clear();
         }
 
+        public synchronized void setFailure(Failure failure) {
+            m_failure = failure;
+        }
+
         @Override
         protected synchronized void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
             String pathinfoTail = req.getPathInfo().replaceFirst("/" + m_agentId + "/versions/?", "");
@@ -199,16 +199,25 @@ public class AgentDeploymentTest extends
     }
 
     private static class TestEventListener implements EventListener {
-        private final Map<String, List<Map<String, String>>> m_topics = new HashMap<String, List<Map<String, String>>>();
+        private static boolean matches(Map<String, String> source, Map<String, String> target) {
+            for (Map.Entry<String, String> sourceEntry : source.entrySet()) {
+                String sourceKey = sourceEntry.getKey();
+                String sourceValue = sourceEntry.getValue();
 
-        public Map<String, List<Map<String, String>>> getTopics() {
-            Map<String, List<Map<String, String>>> result;
-            synchronized (m_topics) {
-                result = new HashMap<String, List<Map<String, String>>>(m_topics);
+                if (!target.containsKey(sourceKey)) {
+                    return false;
+                }
+                String targetValue = target.get(sourceKey);
+                if (!sourceValue.equals(targetValue)) {
+                    return false;
+                }
             }
-            return result;
+
+            return true;
         }
 
+        private final Map<String, List<Map<String, String>>> m_topics = new HashMap<String, List<Map<String, String>>>();
+
         public boolean containsTopic(String topic) {
             synchronized (m_topics) {
                 return m_topics.containsKey(topic);
@@ -230,6 +239,14 @@ public class AgentDeploymentTest extends
             }
         }
 
+        public Map<String, List<Map<String, String>>> getTopics() {
+            Map<String, List<Map<String, String>>> result;
+            synchronized (m_topics) {
+                result = new HashMap<String, List<Map<String, String>>>(m_topics);
+            }
+            return result;
+        }
+
         @Override
         public void handle(String topic, Map<String, String> payload) {
             if (LOGLEVEL == Levels.DEBUG) {
@@ -245,23 +262,6 @@ public class AgentDeploymentTest extends
                 payloads.add(payload);
             }
         }
-
-        private static boolean matches(Map<String, String> source, Map<String, String> target) {
-            for (Map.Entry<String, String> sourceEntry : source.entrySet()) {
-                String sourceKey = sourceEntry.getKey();
-                String sourceValue = sourceEntry.getValue();
-
-                if (!target.containsKey(sourceKey)) {
-                    return false;
-                }
-                String targetValue = target.get(sourceKey);
-                if (!sourceValue.equals(targetValue)) {
-                    return false;
-                }
-            }
-
-            return true;
-        }
     }
 
     private static class TestPackage {
@@ -298,8 +298,8 @@ public class AgentDeploymentTest extends
         }
     }
 
-    private static final String AGENT_DEPLOYMENT_COMPLETE = "agent/deployment/COMPLETE";
-    private static final String AGENT_DEPLOYMENT_INSTALL = "agent/deployment/INSTALL";
+    private static final String AGENT_INSTALLATION_START = "agent/installation/START";
+    private static final String AGENT_INSTALLATION_COMPLETE = "agent/installation/COMPLETE";
 
     private static final String AGENT_ID = "007";
     private static final String TEST_BUNDLE_NAME_PREFIX = "test.bundle";
@@ -345,11 +345,11 @@ public class AgentDeploymentTest extends
         // Check our event log, should contain all handled events...
         Map<String, List<Map<String, String>>> topics = m_listener.getTopics();
 
-        List<Map<String, String>> events = topics.get(AGENT_DEPLOYMENT_INSTALL);
+        List<Map<String, String>> events = topics.get(AGENT_INSTALLATION_START);
         // should contain exactly three different elements...
         assertEquals(events.toString(), 3, events.size());
 
-        events = topics.get(AGENT_DEPLOYMENT_COMPLETE);
+        events = topics.get(AGENT_INSTALLATION_COMPLETE);
         // should contain exactly three different elements...
         assertEquals(events.toString(), 3, events.size());
     }
@@ -476,6 +476,29 @@ public class AgentDeploymentTest extends
         expectSuccessfulDeployment(m_package1, Failure.VERSIONS_RETRY_AFTER);
     }
 
+    /**
+     * Tests the deployment of "streamed" deployment packages simulating an "unstable" connection.
+     */
+    public void testStreamingDeploymentWithUnstableConnection() throws Exception {
+        setupAgentForStreamingDeployment();
+
+        expectSuccessfulDeployment(m_package1, null);
+        
+        expectFailedDeployment(m_package6, Failure.EMPTY_STREAM);
+        waitForInstalledVersion(V1_0_0);
+
+        expectFailedDeployment(m_package6, Failure.CORRUPT_STREAM);
+        waitForInstalledVersion(V1_0_0);
+
+        expectFailedDeployment(m_package6, Failure.ABORT_STREAM);
+        waitForInstalledVersion(V1_0_0);
+
+        expectFailedDeployment(m_package6, Failure.EMPTY_STREAM);
+        waitForInstalledVersion(V1_0_0);
+
+        expectFailedDeployment(m_package6, null);
+    }
+
     @Override
     protected void configureAdditionalServices() throws Exception {
         TestBundle bundle1v1 = new TestBundle(TEST_BUNDLE_NAME_PREFIX.concat("1"), V1_0_0);
@@ -542,27 +565,27 @@ public class AgentDeploymentTest extends
         return props;
     }
 
-    private void expectSuccessfulDeployment(TestPackage dpackage, Failure failure) throws Exception {
-        deployPackage(dpackage, failure);
-
-        waitForEventReceived(AGENT_DEPLOYMENT_INSTALL);
-        waitForEventReceived(AGENT_DEPLOYMENT_COMPLETE, "successful", "true");
-
-        waitForInstalledVersion(dpackage.getVersion());
+    private void deployPackage(TestPackage dpackage, Failure failure) {
+        synchronized (m_servlet) {
+            m_servlet.setFailure(failure);
+            m_servlet.addPackage(dpackage);
+        }
     }
 
     private void expectFailedDeployment(TestPackage dpackage, Failure failure) throws Exception {
         deployPackage(dpackage, failure);
 
-        waitForEventReceived(AGENT_DEPLOYMENT_INSTALL);
-        waitForEventReceived(AGENT_DEPLOYMENT_COMPLETE, "successful", "false");
+        waitForEventReceived(AGENT_INSTALLATION_START);
+        waitForEventReceived(AGENT_INSTALLATION_COMPLETE, "successful", "false");
     }
 
-    private void deployPackage(TestPackage dpackage, Failure failure) {
-        synchronized (m_servlet) {
-            m_servlet.setFailure(failure);
-            m_servlet.addPackage(dpackage);
-        }
+    private void expectSuccessfulDeployment(TestPackage dpackage, Failure failure) throws Exception {
+        deployPackage(dpackage, failure);
+
+        waitForEventReceived(AGENT_INSTALLATION_START);
+        waitForEventReceived(AGENT_INSTALLATION_COMPLETE, "successful", "true");
+
+        waitForInstalledVersion(dpackage.getVersion());
     }
 
     private void setupAgentForNonStreamingDeployment() throws Exception {

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/AgentUpdateHandler.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/AgentUpdateHandler.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/AgentUpdateHandler.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/AgentUpdateHandler.java Fri Sep 13 10:54:34 2013
@@ -18,32 +18,9 @@
  */
 package org.apache.ace.agent;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.SortedSet;
-
-import org.osgi.framework.Version;
-
 /**
  * Agent context delegate interface that is responsible for managing agent updates.
  */
-public interface AgentUpdateHandler {
-
-    /** Returns the locally installed version of the agent. */
-    Version getInstalledVersion();
-
-    /** Returns the versions available on the server. */
-    SortedSet<Version> getAvailableVersions() throws RetryAfterException, IOException;
-
-    /** Returns an input stream for the update of the agent. */
-    InputStream getInputStream(Version version) throws RetryAfterException, IOException;
-
-    /** Returns a download handle to download the update of the agent. */
-    DownloadHandle getDownloadHandle(Version version) throws RetryAfterException, IOException;
-
-    /** Returns the size of the update of the agent. */
-    long getSize(Version version) throws RetryAfterException, IOException;
-
-    /** Installs the update of the agent. */
-    void install(InputStream stream) throws IOException;
+public interface AgentUpdateHandler extends UpdateHandler {
+    // No additional methods
 }

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DeploymentHandler.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DeploymentHandler.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DeploymentHandler.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DeploymentHandler.java Fri Sep 13 10:54:34 2013
@@ -18,71 +18,9 @@
  */
 package org.apache.ace.agent;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.SortedSet;
-
-import org.osgi.framework.Version;
-import org.osgi.service.deploymentadmin.DeploymentException;
-
 /**
  * Agent context delegate interface that provides the deployment functions.
  */
-public interface DeploymentHandler {
-
-    /**
-     * Return the installed deployment package version for this agent.
-     * 
-     * @return The installed version, {@link Version.emptyVersion} if no packages have been installed
-     */
-    Version getInstalledVersion();
-
-    /**
-     * Return the sorted set of available deployment package versions as reported by the server.
-     * 
-     * @return The sorted set of versions, may be empty
-     * @throws RetryAfterException If the server indicates it is too busy with a Retry-After header
-     * @throws IOException If the connection to the server fails
-     */
-    SortedSet<Version> getAvailableVersions() throws RetryAfterException, IOException;
-
-    /**
-     * Return the estimated size for a deployment package as reported by the server.
-     * 
-     * @param version The version of the package
-     * @param fixPackage Request the server for a fix-package
-     * @return The estimated size in bytes, <code>-1</code> indicates the size is unknown
-     * @throws RetryAfterException If the server indicates it is too busy with a Retry-After header
-     * @throws IOException If the connection to the server fails
-     */
-    long getPackageSize(Version version, boolean fixPackage) throws RetryAfterException, IOException;
-
-    /**
-     * Returns the {@link InputStream} for a deployment package.
-     * 
-     * @param version The version of the deployment package
-     * @param fixPackage Request the server for a fix-package
-     * @return The input-stream for the deployment package
-     * @throws RetryAfterException If the server indicates it is too busy with a Retry-After header
-     * @throws IOException If the connection to the server fails
-     */
-    InputStream getInputStream(Version version, boolean fixPackage) throws RetryAfterException, IOException;
-
-    /**
-     * Return the {@link DownloadHandle} for a deployment package.
-     * 
-     * @param version The version of the deployment package
-     * @param fixPackage Request the server for a fix-package
-     * @return The download handle
-     */
-    DownloadHandle getDownloadHandle(Version version, boolean fixPackage) throws RetryAfterException, IOException;
-
-    /**
-     * Install a deployment package from an input stream.
-     * 
-     * @param inputStream The inputStream, not <code>null</code>
-     * @throws IOException If reading the input stream fails.
-     */
-    // TODO should we expose the foreign exception?
-    void deployPackage(InputStream inputStream) throws DeploymentException, IOException;
+public interface DeploymentHandler extends UpdateHandler {
+    // No additional methods
 }

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandle.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandle.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandle.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandle.java Fri Sep 13 10:54:34 2013
@@ -18,6 +18,8 @@
  */
 package org.apache.ace.agent;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * A {@link DownloadHandle} provides control over an asynchronous download and access to the resulting file when the it
  * is completed. <br/>
@@ -30,76 +32,50 @@ package org.apache.ace.agent;
  * <ul>
  */
 public interface DownloadHandle {
-
-    /**
-     * Size of the buffer used while downloading the content stream.
-     */
-    int DEFAULT_READBUFFER_SIZE = 1024;
-
     /**
-     * Callback interface; when registered the progress method will be invoked while downloading the content stream for
-     * every {@link READBUFFER_SIZE} bytes.
+     * Callback interface; when registered the completed method will be invoked when the download terminates for any
+     * reason.
      */
-    interface ProgressListener {
+    interface DownloadProgressListener {
         /**
          * Called while downloading the content stream.
          * 
-         * @param contentLength The total length of the content or -1 if unknown.
-         * @param progress The number of bytes that has been received so far.
+         * @param bytesRead
+         *            The number of bytes that has been received so far;
+         * @param totalBytes
+         *            The total length of the content or -1 if unknown.
          */
-        void progress(long contentLength, long progress);
-    }
+        void progress(long bytesRead, long totalBytes);
 
-    /**
-     * Callback interface; when registered the completed method will be invoked when the download terminates for any
-     * reason.
-     * 
-     */
-    interface ResultListener {
         /**
          * Called when a download terminates.
          * 
-         * @param result The result of the download.
+         * @param result
+         *            The result of the download.
          */
         void completed(DownloadResult result);
     }
 
     /**
-     * Registers the progress listener.
-     * 
-     * @param listener The progress listener.
-     * @return this
-     */
-    DownloadHandle setProgressListener(ProgressListener listener);
-
-    /**
-     * Registers the completion listener.
-     * 
-     * @param listener The completion listener.
-     * @return this
+     * Starts the download, reporting the result and progress to the supplied listeners.
      */
-    DownloadHandle setCompletionListener(ResultListener listener);
+    void start(DownloadProgressListener listener);
 
     /**
-     * Starts the download.
+     * Convenience method to start the download and block until it is finished.
      * 
-     * @return this
+     * @param timeout
+     *            the timeout to wait for a result;
+     * @param unit
+     *            the unit of the timeout to wait for a result.
+     * @return the download result, never <code>null</code>.
      */
-    DownloadHandle start();
+    DownloadResult startAndAwaitResult(long timeout, TimeUnit unit) throws InterruptedException;
 
     /**
      * Pauses the download.
-     * 
-     * @return this
-     */
-    DownloadHandle stop();
-
-    /**
-     * Retrieves the download result. Will wait for completion before returning.
-     * 
-     * @return The result of the download
      */
-    DownloadResult result();
+    void stop();
 
     /**
      * Releases any resources that may be held by the handle.

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandler.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandler.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandler.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadHandler.java Fri Sep 13 10:54:34 2013
@@ -34,5 +34,6 @@ public interface DownloadHandler {
     DownloadHandle getHandle(URL url);
 
     // TODO named handlers (resume over urls)
+    @Deprecated
     DownloadHandle getHandle(URL url, int readBufferSize);
 }

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadResult.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadResult.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadResult.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/DownloadResult.java Fri Sep 13 10:54:34 2013
@@ -20,8 +20,6 @@ package org.apache.ace.agent;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.List;
-import java.util.Map;
 
 /**
  * Represents the result of a download task.
@@ -44,12 +42,10 @@ public interface DownloadResult {
     InputStream getInputStream() throws IOException;
 
     /**
-     * @return
+     * @return the result code
      */
     int getCode();
 
-    Map<String, List<String>> getHeaders();
-
     /**
      * Return the cause of an unsuccessful download.
      * 

Added: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/InstallationFailedException.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/InstallationFailedException.java?rev=1522863&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/InstallationFailedException.java (added)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/InstallationFailedException.java Fri Sep 13 10:54:34 2013
@@ -0,0 +1,42 @@
+/*
+ * 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.agent;
+
+/**
+ * Generic exception that is thrown when an installation of an update failed.
+ * 
+ * @see UpdateHandler#install(java.io.InputStream)
+ */
+public class InstallationFailedException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates a new {@link InstallationFailedException} instance.
+     */
+    public InstallationFailedException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * Creates a new {@link InstallationFailedException} instance.
+     */
+    public InstallationFailedException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+}

Propchange: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/InstallationFailedException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/UpdateHandler.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/UpdateHandler.java?rev=1522863&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/UpdateHandler.java (added)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/UpdateHandler.java Fri Sep 13 10:54:34 2013
@@ -0,0 +1,125 @@
+/*
+ * 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.agent;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.SortedSet;
+
+import org.osgi.framework.Version;
+
+/**
+ * Generic interface for installing updates.
+ */
+public interface UpdateHandler {
+    /**
+     * @return a short descriptive name for this update handler, for example, "agent updater".
+     */
+    String getName();
+
+    /**
+     * Return the sorted set of available update-versions as reported by the server.
+     * 
+     * @return a sorted set of versions, may be empty, but never be <code>null</code>.
+     * @throws RetryAfterException
+     *             if the server indicates it is too busy, and this call should be retried on a later moment;
+     * @throws IOException
+     *             in case the connection to the server failed.
+     */
+    SortedSet<Version> getAvailableVersions() throws RetryAfterException, IOException;
+
+    /**
+     * Return the {@link DownloadHandle} for an update.
+     * 
+     * @param version
+     *            the version of the update to get a download handle for, cannot be <code>null</code>;
+     * @param fixPackage
+     *            <code>true</code> if a download handler for a fix-package should be requested, <code>false</code>
+     *            otherwise.
+     * @return a download handle for the requested update, never <code>null</code>.
+     * @throws RetryAfterException
+     *             if the server indicates it is too busy, and this call should be retried on a later moment.
+     */
+    DownloadHandle getDownloadHandle(Version version, boolean fixPackage) throws RetryAfterException;
+
+    /**
+     * Returns the highest available update-version as reported by the server.
+     * 
+     * @return the highest available version, never <code>null</code>, can be {@link Version#emptyVersion} in case no
+     *         version is available.
+     * @throws RetryAfterException
+     *             if the server indicates it is too busy, and this call should be retried on a later moment;
+     * @throws IOException
+     *             in case the connection to the server failed.
+     */
+    Version getHighestAvailableVersion() throws RetryAfterException, IOException;
+
+    /**
+     * Returns the {@link InputStream} for an update.
+     * 
+     * @param version
+     *            the version of the update to get an input-stream for, cannot be <code>null</code>;
+     * @param fixPackage
+     *            <code>true</code> if an input-stream for a fix-package should be requested, <code>false</code>
+     *            otherwise.
+     * @return the input-stream for the update, never <code>null</code>.
+     * @throws RetryAfterException
+     *             if the server indicates it is too busy, and this call should be retried on a later moment;
+     * @throws IOException
+     *             in case the connection to the server failed.
+     */
+    InputStream getInputStream(Version version, boolean fixPackage) throws RetryAfterException, IOException;
+
+    /**
+     * Return version of the current installed update for this agent.
+     * 
+     * @return the installed version, {@link Version.emptyVersion} if no packages have been installed, never
+     *         <code>null</code>.
+     */
+    Version getInstalledVersion();
+
+    /**
+     * Return the estimated size for an update as reported by the server.
+     * 
+     * @param version
+     *            the version of the update to get a size estimation for, cannot be <code>null</code>;
+     * @param fixPackage
+     *            <code>true</code> if a size estimation for a fix-package should be requested, <code>false</code>
+     *            otherwise.
+     * @return the estimated size in bytes, <code>-1</code> indicates the size is unknown.
+     * @throws RetryAfterException
+     *             if the server indicates it is too busy, and this call should be retried on a later moment;
+     * @throws IOException
+     *             in case the connection to the server failed.
+     */
+    long getSize(Version version, boolean fixPackage) throws RetryAfterException, IOException;
+
+    /**
+     * Install an update from an input stream.
+     * 
+     * @param inputStream
+     *            the inputStream, can not be <code>null</code>.
+     * @throws InstallationFailedException
+     *             in case the installation failed;
+     * @throws IOException
+     *             if reading from the given input stream fails.
+     */
+    void install(InputStream inputStream) throws InstallationFailedException, IOException;
+
+}

Propchange: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/UpdateHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/Activator.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/Activator.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/Activator.java Fri Sep 13 10:54:34 2013
@@ -18,6 +18,7 @@
  */
 package org.apache.ace.agent.impl;
 
+import java.io.File;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadFactory;
@@ -105,13 +106,15 @@ public class Activator implements Bundle
      * Called by our {@link DependencyTrackerImpl} when all dependencies are satisfied.
      */
     public void componentStarted(BundleContext context) throws Exception {
-        m_agentContext = new AgentContextImpl(context.getDataFile(""));
+        final File bundleDataArea = context.getDataFile("");
+
+        m_agentContext = new AgentContextImpl(bundleDataArea);
 
         m_agentContext.setHandler(LoggingHandler.class, new LoggingHandlerImpl());
         m_agentContext.setHandler(ConfigurationHandler.class, new ConfigurationHandlerImpl());
         m_agentContext.setHandler(EventsHandler.class, new EventsHandlerImpl(context));
         m_agentContext.setHandler(ScheduledExecutorService.class, m_executorService);
-        m_agentContext.setHandler(DownloadHandler.class, new DownloadHandlerImpl());
+        m_agentContext.setHandler(DownloadHandler.class, new DownloadHandlerImpl(bundleDataArea));
         m_agentContext.setHandler(DeploymentHandler.class, new DeploymentHandlerImpl(context, m_packageAdmin));
         m_agentContext.setHandler(AgentUpdateHandler.class, new AgentUpdateHandlerImpl(context));
         m_agentContext.setHandler(FeedbackHandler.class, new FeedbackHandlerImpl());

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/AgentUpdateHandlerImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/AgentUpdateHandlerImpl.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/AgentUpdateHandlerImpl.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/AgentUpdateHandlerImpl.java Fri Sep 13 10:54:34 2013
@@ -18,7 +18,9 @@
  */
 package org.apache.ace.agent.impl;
 
-import static org.apache.ace.agent.impl.ConnectionUtil.*;
+import static org.apache.ace.agent.impl.ConnectionUtil.closeSilently;
+import static org.apache.ace.agent.impl.ConnectionUtil.copy;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -66,29 +68,34 @@ public class AgentUpdateHandlerImpl exte
     }
 
     @Override
-    public DownloadHandle getDownloadHandle(Version version) throws RetryAfterException, IOException {
+    public DownloadHandle getDownloadHandle(Version version, boolean fixPackage) throws RetryAfterException {
         return getDownloadHandle(getEndpoint(getServerURL(), getIdentification(), version));
     }
 
     @Override
-    public InputStream getInputStream(Version version) throws RetryAfterException, IOException {
+    public InputStream getInputStream(Version version, boolean fixPackage) throws RetryAfterException, IOException {
         return getInputStream(getEndpoint(getServerURL(), getIdentification(), version));
     }
-
+    
     @Override
     public Version getInstalledVersion() {
         return m_bundleContext.getBundle().getVersion();
     }
 
     @Override
-    public long getSize(Version version) throws RetryAfterException, IOException {
+    public String getName() {
+        return "agent";
+    }
+
+    @Override
+    public long getSize(Version version, boolean fixPackage) throws RetryAfterException, IOException {
         return getPackageSize(getEndpoint(getServerURL(), getIdentification(), version));
     }
 
     @Override
     public void install(InputStream stream) throws IOException {
         try {
-            InputStream currentBundleVersion = getInputStream(m_bundleContext.getBundle().getVersion());
+            InputStream currentBundleVersion = getInputStream(m_bundleContext.getBundle().getVersion(), false /* fixPackage */);
             Bundle bundle = m_bundleContext.installBundle("agent-updater", generateBundle());
             bundle.start();
             ServiceTracker st = new ServiceTracker(m_bundleContext, m_bundleContext.createFilter("(" + Constants.OBJECTCLASS + "=org.apache.ace.agent.updater.Activator)"), null);
@@ -136,15 +143,17 @@ public class AgentUpdateHandlerImpl exte
 
     /** Generates an input stream that contains a complete bundle containing our update code for the agent. */
     private InputStream generateBundle() throws IOException {
+        final String activatorClass = "org/apache/ace/agent/updater/Activator.class";
+
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
         InputStream is = null;
         JarOutputStream os = null;
         try {
-            is = getClass().getResourceAsStream("/org/apache/ace/agent/updater/Activator.class");
+            is = getClass().getResourceAsStream("/".concat(activatorClass));
 
             os = new JarOutputStream(baos, createBundleManifest());
-            os.putNextEntry(new JarEntry("org/apache/ace/agent/updater/Activator.class"));
+            os.putNextEntry(new JarEntry(activatorClass));
 
             try {
                 copy(is, os);

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DefaultController.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DefaultController.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DefaultController.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DefaultController.java Fri Sep 13 10:54:34 2013
@@ -25,9 +25,7 @@ import static org.apache.ace.agent.Agent
 import static org.apache.ace.agent.AgentConstants.CONFIG_CONTROLLER_SYNCDELAY;
 import static org.apache.ace.agent.AgentConstants.CONFIG_CONTROLLER_SYNCINTERVAL;
 import static org.apache.ace.agent.impl.ConnectionUtil.closeSilently;
-import static org.apache.ace.agent.impl.InternalConstants.AGENT_CONFIG_CHANGED;
-import static org.apache.ace.agent.impl.InternalConstants.AGENT_DEPLOYMENT_COMPLETE;
-import static org.apache.ace.agent.impl.InternalConstants.AGENT_DEPLOYMENT_INSTALL;
+import static org.apache.ace.agent.impl.InternalConstants.*;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -35,141 +33,126 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.ace.agent.AgentUpdateHandler;
-import org.apache.ace.agent.DeploymentHandler;
 import org.apache.ace.agent.DownloadHandle;
+import org.apache.ace.agent.DownloadHandle.DownloadProgressListener;
 import org.apache.ace.agent.DownloadResult;
 import org.apache.ace.agent.DownloadState;
 import org.apache.ace.agent.EventListener;
 import org.apache.ace.agent.FeedbackChannel;
+import org.apache.ace.agent.InstallationFailedException;
 import org.apache.ace.agent.RetryAfterException;
+import org.apache.ace.agent.UpdateHandler;
 import org.osgi.framework.Version;
-import org.osgi.service.deploymentadmin.DeploymentException;
 
 /**
  * Default configurable controller
  */
 public class DefaultController extends ComponentBase implements Runnable, EventListener {
-
     /**
      * UpdateInstaller that provides download deployment package install. The install is non-blocking. Upon download
      * completion this installer will reschedule the controller.
      */
-    static class DownloadUpdateInstaller extends UpdateInstaller implements DownloadHandle.ProgressListener, DownloadHandle.ResultListener {
-        // active download state
+    static class DownloadUpdateInstaller extends UpdateInstaller {
         private volatile DownloadHandle m_downloadHandle;
-        private volatile DownloadResult m_downloadResult = null;
-        private volatile Version m_downloadVersion;
-        private volatile long m_downloadLength = 0;
-        private volatile long m_downloadProgress = 0;
+        private volatile UpdateInfo m_updateInfo;
 
         public DownloadUpdateInstaller(DefaultController controller) {
             super(controller);
         }
 
         @Override
-        public void completed(DownloadResult result) {
-            int delay = 1; // seconds
-            m_downloadResult = result;
-            getController().logInfo("Deployment package download completed for version %s. Rescheduling the controller to run in %d seconds", m_downloadVersion, delay);
-            getController().scheduleRun(delay);
-        }
-
-        @Override
-        public void doInstallUpdate(Version fromVersion, Version toVersion, boolean fixPackage) throws RetryAfterException, DeploymentException, IOException {
-            DeploymentHandler deploymentHandler = getController().getDeploymentHandler();
+        public void doInstallUpdate(final UpdateHandler delegate, final UpdateInfo updateInfo) throws RetryAfterException {
+            DefaultController controller = getController();
 
-            if (m_downloadHandle != null && !m_downloadVersion.equals(toVersion)) {
-                getController().logInfo("Cancelling deployment package download for %s because a newer version is available...", m_downloadVersion);
+            if (m_downloadHandle != null && m_updateInfo != null && !m_updateInfo.m_to.equals(updateInfo.m_to)) {
+                controller.logInfo("Cancelling download of %s update for %s because a newer version is available...", m_updateInfo.m_type, m_updateInfo.m_to);
                 m_downloadHandle.discard();
                 m_downloadHandle = null;
             }
 
             if (m_downloadHandle == null) {
-                getController().logInfo("Starting deployment package download %s => %s...", fromVersion, toVersion);
-
-                m_downloadVersion = toVersion;
-
-                m_downloadHandle = deploymentHandler.getDownloadHandle(toVersion, fixPackage);
-                m_downloadHandle.setProgressListener(this).setCompletionListener(this).start();
-                return;
-            }
-
-            if (m_downloadResult == null) {
-                getController().logInfo("Deployment package download for %s is in progress %d / %d", toVersion, m_downloadProgress, m_downloadLength);
-                return;
-            }
-
-            DownloadState state = m_downloadResult.getState();
-            if (DownloadState.FAILED == state) {
-                getController().logWarning("Deployment package download for %s is FAILED. Restarting download...");
-
-                m_downloadHandle.discard();
-                m_downloadHandle = null;
-
-                throw new IOException("Download failed for deployment update " + fromVersion + " => " + toVersion);
-            }
-            else if (DownloadState.STOPPED == state) {
-                getController().logInfo("Deployment package download for %s is STOPPED. Trying to resume download...");
-
-                m_downloadResult = null;
-                m_downloadHandle.start();
-            }
-            else if (DownloadState.SUCCESSFUL == state) {
-                getController().logInfo("Installing downloaded deployment update %s => %s...", fromVersion, toVersion);
-
-                getController().sendDeploymentInstallEvent(fromVersion, toVersion, fixPackage);
-                
-                InputStream inputStream = null;
-                boolean success = false;
+                controller.logInfo("Starting download of %s update, %s => %s...", updateInfo.m_type, updateInfo.m_from, updateInfo.m_to);
 
-                try {
-                    inputStream = m_downloadResult.getInputStream();
-
-                    deploymentHandler.deployPackage(inputStream);
-
-                    success = true;
-                }
-                finally {
-                    m_downloadHandle.discard();
-                    m_downloadHandle = null;
+                m_updateInfo = updateInfo;
 
-                    closeSilently(inputStream);
+                m_downloadHandle = delegate.getDownloadHandle(updateInfo.m_to, updateInfo.m_fixPackage);
+                m_downloadHandle.start(new DownloadProgressListener() {
+                    @Override
+                    public void completed(DownloadResult result) {
+                        DefaultController controller = getController();
+                        DownloadState state = result.getState();
+
+                        String type = updateInfo.m_type;
+                        Version fromVersion = updateInfo.m_from;
+                        Version toVersion = updateInfo.m_to;
+
+                        if (DownloadState.FAILED == state) {
+                            controller.logInfo("Download of %s update is FAILED. Restarting download...", type);
+
+                            m_downloadHandle.discard();
+                            m_downloadHandle = null;
+                        }
+                        else if (DownloadState.STOPPED == state) {
+                            controller.logInfo("Download of %s update is STOPPED. Resuming download...", type);
+
+                            m_downloadHandle.start(this);
+                        }
+                        else if (DownloadState.SUCCESSFUL == state) {
+                            controller.logInfo("Installing %s update %s => %s...", type, fromVersion, toVersion);
+
+                            boolean success = false;
+                            Exception cause = null;
+
+                            try {
+                                startInstallation(updateInfo);
+
+                                delegate.install(result.getInputStream());
+
+                                success = true;
+                            }
+                            catch (Exception exception) {
+                                cause = exception;
+                                controller.logWarning("Installation of %s update failed!", exception, type);
+                            }
+                            finally {
+                                m_downloadHandle.discard();
+                                m_downloadHandle = null;
+
+                                endInstallation(updateInfo, success, cause);
+                            }
+                        }
+                    }
 
-                    getController().sendDeploymentCompletedEvent(fromVersion, toVersion, fixPackage, success);
-                }
+                    @Override
+                    public void progress(long contentLength, long progress) {
+                        if (updateInfo != null) {
+                            getController().logInfo("Progress of %s update download: %d of %d bytes...", updateInfo.m_type, progress, contentLength);
+                        }
+                    }
+                });
             }
         }
 
         @Override
         public void doReset() {
             if (m_downloadHandle != null) {
-                getController().logInfo("Cancelling deployment package download for version %s because of reset...", m_downloadVersion);
+                getController().logInfo("Cancelling deployment package download for version %s because of reset...", m_updateInfo.m_to);
                 m_downloadHandle.discard();
             }
             clearDownloadState();
         }
 
-        @Override
-        public void progress(long contentLength, long progress) {
-            m_downloadLength = contentLength;
-            m_downloadProgress = progress;
-        }
-
         private void clearDownloadState() {
             if (m_downloadHandle != null) {
                 m_downloadHandle.discard();
             }
             m_downloadHandle = null;
-            m_downloadResult = null;
-            m_downloadVersion = null;
+            m_updateInfo = null;
         }
     }
 
@@ -182,27 +165,34 @@ public class DefaultController extends C
         }
 
         @Override
-        public void doInstallUpdate(Version from, Version to, boolean fix) throws RetryAfterException, DeploymentException, IOException {
-            getController().logInfo("Installing streaming deployment update %s => %s", from, to);
+        public void doInstallUpdate(UpdateHandler delegate, UpdateInfo updateInfo) throws RetryAfterException {
+            DefaultController controller = getController();
 
-            getController().sendDeploymentInstallEvent(from, to, fix);
-
-            DeploymentHandler deploymentHandler = getController().getDeploymentHandler();
+            controller.logInfo("Installing streaming %s update %s => %s", updateInfo.m_type, updateInfo.m_from, updateInfo.m_to);
 
             InputStream inputStream = null;
-            boolean success = false;
 
             try {
-                inputStream = deploymentHandler.getInputStream(to, fix);
+                inputStream = delegate.getInputStream(updateInfo.m_to, updateInfo.m_fixPackage);
+
+                startInstallation(updateInfo);
 
-                deploymentHandler.deployPackage(inputStream);
+                delegate.install(inputStream);
 
-                success = true;
+                endInstallation(updateInfo, true /* success */, null);
+            }
+            catch (RetryAfterException ex) {
+                // We aren't ready yet...
+                throw ex;
+            }
+            catch (InstallationFailedException ex) {
+                endInstallation(updateInfo, false /* success */, ex);
+            }
+            catch (IOException ex) {
+                endInstallation(updateInfo, false /* success */, ex);
             }
             finally {
                 closeSilently(inputStream);
-
-                getController().sendDeploymentCompletedEvent(from, to, fix, success);
             }
         }
 
@@ -213,10 +203,27 @@ public class DefaultController extends C
     }
 
     /**
-     * Base class for internal installer strategies. This implementation handles max update retry contraints and
+     * Small container for information about an update.
+     */
+    static class UpdateInfo {
+        final Version m_from;
+        final Version m_to;
+        final boolean m_fixPackage;
+        final String m_type;
+
+        public UpdateInfo(String type, Version from, Version to, boolean fixPackage) {
+            m_from = from;
+            m_to = to;
+            m_type = type;
+            m_fixPackage = fixPackage;
+        }
+    }
+
+    /**
+     * Base class for internal installer strategies. This implementation handles max update retry constraints and
      * delegates the rest to concrete implementations.
      */
-    abstract static class UpdateInstaller {
+    static abstract class UpdateInstaller {
         private final DefaultController m_controller;
         private Version m_lastVersionTried = null;
         private boolean m_lastVersionSuccessful = true;
@@ -226,72 +233,140 @@ public class DefaultController extends C
             m_controller = controller;
         }
 
-        public final boolean canInstallUpdate(Version fromVersion, Version toVersion, long maxRetries) {
-            if (toVersion.compareTo(fromVersion) > 0) {
-                // Possible newer version, lets check our administration whether we actually need to do something...
-                if (m_lastVersionTried != null && toVersion.equals(m_lastVersionTried)) {
-                    if (m_failureCount >= maxRetries) {
-                        m_controller.logDebug("Ignoring update %s => %s because max retries (%d) reached!", fromVersion, toVersion, maxRetries);
-                        return false;
-                    }
-                    if (!m_lastVersionSuccessful) {
-                        m_controller.logDebug("Ignoring update %s => %s because it failed previously!", fromVersion, toVersion);
-                        return false;
-                    }
-                }
+        /**
+         * Checks whether there's an update to install, and if so, uses the given delegate to actually install the
+         * update.
+         * 
+         * @param delegate
+         *            the update handle to use for installing the update;
+         * @param fixPackage
+         *            <code>true</code> if the update should be downloaded as a "fix package", or <code>false</code> if
+         *            it should be a "complete" update;
+         * @param maxRetries
+         *            the maximum number of times an update should be retries.
+         * @throws RetryAfterException
+         *             in case the server is too busy and we should defer our update to a later moment in time;
+         * @throws IOException
+         *             in case of problems accessing the server.
+         */
+        public final void installUpdate(UpdateHandler delegate, boolean fixPackage, long maxRetries) throws RetryAfterException, IOException {
+            Version fromVersion = delegate.getInstalledVersion();
+            Version toVersion = delegate.getHighestAvailableVersion();
 
-                m_controller.logDebug("Need to install update: newer deployment version available!");
-                return true;
-            }
-            else {
-                m_controller.logDebug("No need to install update: no newer deployment version available!");
-                return false;
+            UpdateInfo updateInfo = new UpdateInfo(delegate.getName(), fromVersion, toVersion, fixPackage);
+
+            // Check whether we actually do need to do something...
+            if (!canInstallUpdate(updateInfo, maxRetries)) {
+                return;
             }
-        }
 
-        public final void installUpdate(Version fromVersion, Version toVersion, boolean fixPackage) throws RetryAfterException {
             if (m_lastVersionTried == null || !toVersion.equals(m_lastVersionTried)) {
                 m_lastVersionTried = toVersion;
                 m_lastVersionSuccessful = true;
                 m_failureCount = 0;
             }
 
-            try {
-                doInstallUpdate(fromVersion, toVersion, fixPackage);
-                // As we've just successfully installed this update, reset the failure counter...
-                m_failureCount = 0;
-                m_lastVersionSuccessful = true;
-            }
-            catch (RetryAfterException e) {
-                // The server is busy. Re-throw so the controller can abort the sync and reschedule.
-                throw (e);
-            }
-            catch (DeploymentException e) {
-                getController().logWarning("Exception while deploying the package", e);
-                e.printStackTrace();
-                m_failureCount++;
-                m_lastVersionSuccessful = false;
-            }
-            catch (IOException e) {
-                getController().logWarning("Exception opening/streaming package inputstream", e);
-                e.printStackTrace();
-                m_failureCount++;
-            }
+            doInstallUpdate(delegate, updateInfo);
         }
 
+        /**
+         * Called when we should discard any pending installations.
+         */
         public final void reset() {
             m_lastVersionTried = null;
             m_failureCount = 0;
             doReset();
         }
 
-        protected abstract void doInstallUpdate(Version from, Version to, boolean fix) throws RetryAfterException, DeploymentException, IOException;
-
+        /**
+         * Called when an update is available and should be installed. Implementations should do the actual installation
+         * of the update using the given delegate.
+         * 
+         * @param delegate
+         *            the delegate to use for installing the update;
+         * @param updateInfo
+         *            some information about the update.
+         * @throws RetryAfterException
+         *             in case the server is too busy and we should defer our update to a later moment in time.
+         */
+        protected abstract void doInstallUpdate(UpdateHandler delegate, UpdateInfo updateInfo) throws RetryAfterException;
+
+        /**
+         * Called when we should discard any pending installations.
+         */
         protected abstract void doReset();
 
+        /**
+         * Should be called to notify that an installation is ended, successfully or unsuccessfully.
+         * 
+         * @param updateInfo
+         *            the information about the update;
+         * @param success
+         *            <code>true</code> if the installation was successful, <code>false</code> otherwise;
+         * @param cause
+         *            the (optional) cause why the installation failed.
+         */
+        protected final void endInstallation(UpdateInfo updateInfo, boolean success, Exception cause) {
+            m_lastVersionSuccessful = success;
+            if (cause instanceof InstallationFailedException || cause instanceof IOException) {
+                m_failureCount++;
+            }
+            else {
+                m_failureCount = 0;
+            }
+            m_controller.sendDeploymentCompletedEvent(updateInfo, success);
+        }
+
         protected final DefaultController getController() {
             return m_controller;
         }
+
+        /**
+         * Should be called to notify that an installation is started.
+         * 
+         * @param updateInfo
+         *            the information about the update.
+         */
+        protected final void startInstallation(UpdateInfo updateInfo) {
+            m_controller.sendDeploymentInstallEvent(updateInfo);
+        }
+
+        /**
+         * Determines whether, according to the given information about the possible update, we should actually perform
+         * an update or not.
+         * 
+         * @param updateInfo
+         *            the information about the possible update;
+         * @param maxRetries
+         *            the maximum number of times an installation should be retried.
+         * @return <code>true</code> if there is a possible update to install, <code>false</code> otherwise.
+         */
+        private boolean canInstallUpdate(UpdateInfo updateInfo, long maxRetries) {
+            Version fromVersion = updateInfo.m_from;
+            Version toVersion = updateInfo.m_to;
+            String type = updateInfo.m_type;
+
+            if (toVersion.compareTo(fromVersion) > 0) {
+                // Possible newer version, lets check our administration whether we actually need to do something...
+                if (m_lastVersionTried != null && toVersion.equals(m_lastVersionTried)) {
+                    if (m_failureCount >= maxRetries) {
+                        m_controller.logDebug("Ignoring %s update %s => %s because max retries (%d) reached!", type, fromVersion, toVersion, maxRetries);
+                        return false;
+                    }
+                    if (!m_lastVersionSuccessful) {
+                        m_controller.logDebug("Ignoring %s update %s => %s because it failed previously!", type, fromVersion, toVersion);
+                        return false;
+                    }
+                }
+
+                m_controller.logDebug("Need to install update: newer %s version available!", type);
+                return true;
+            }
+            else {
+                m_controller.logDebug("No need to install update: no newer %s version available!", type);
+                return false;
+            }
+        }
     }
 
     private volatile ScheduledFuture<?> m_scheduledFuture;
@@ -433,25 +508,27 @@ public class DefaultController extends C
         m_scheduledFuture = getExecutorService().schedule(this, seconds, TimeUnit.SECONDS);
     }
 
-    protected void sendDeploymentCompletedEvent(Version from, Version to, boolean fixPackage, boolean success) {
+    protected void sendDeploymentCompletedEvent(UpdateInfo updateInfo, boolean success) {
         Map<String, String> eventProps = new HashMap<String, String>();
+        eventProps.put("type", updateInfo.m_type);
         eventProps.put("name", getIdentificationHandler().getAgentId());
-        eventProps.put("fromVersion", from.toString());
-        eventProps.put("toVersion", to.toString());
-        eventProps.put("fixPackage", Boolean.toString(fixPackage));
+        eventProps.put("fromVersion", updateInfo.m_from.toString());
+        eventProps.put("toVersion", updateInfo.m_to.toString());
+        eventProps.put("fixPackage", Boolean.toString(updateInfo.m_fixPackage));
         eventProps.put("successful", Boolean.toString(success));
 
-        getEventsHandler().postEvent(AGENT_DEPLOYMENT_COMPLETE, eventProps);
+        getEventsHandler().postEvent(AGENT_INSTALLATION_COMPLETE, eventProps);
     }
 
-    protected void sendDeploymentInstallEvent(Version from, Version to, boolean fixPackage) {
+    protected void sendDeploymentInstallEvent(UpdateInfo updateInfo) {
         Map<String, String> eventProps = new HashMap<String, String>();
+        eventProps.put("type", updateInfo.m_type);
         eventProps.put("name", getIdentificationHandler().getAgentId());
-        eventProps.put("fromVersion", from.toString());
-        eventProps.put("toVersion", to.toString());
-        eventProps.put("fixPackage", Boolean.toString(fixPackage));
+        eventProps.put("fromVersion", updateInfo.m_from.toString());
+        eventProps.put("toVersion", updateInfo.m_to.toString());
+        eventProps.put("fixPackage", Boolean.toString(updateInfo.m_fixPackage));
 
-        getEventsHandler().postEvent(AGENT_DEPLOYMENT_INSTALL, eventProps);
+        getEventsHandler().postEvent(AGENT_INSTALLATION_START, eventProps);
     }
 
     private FeedbackChannel getFeedbackChannel(String name) {
@@ -478,44 +555,9 @@ public class DefaultController extends C
         return Collections.emptySet();
     }
 
-    private Version getHighestAvailableAgentVersion() throws RetryAfterException {
-        SortedSet<Version> available = new TreeSet<Version>();
-        try {
-            available = getAgentUpdateHandler().getAvailableVersions();
-        }
-        catch (IOException e) {
-            // Hopefully temporary problem due to remote IO or configuration. No cause to abort the sync so we just
-            // log it as a warning.
-            logWarning("Exception while retrieving agent versions", e);
-        }
-
-        return getHighestVersion(available);
-    }
-
-    private Version getHighestAvailableDeploymentVersion() throws RetryAfterException {
-        SortedSet<Version> available = new TreeSet<Version>();
-        try {
-            available = getDeploymentHandler().getAvailableVersions();
-        }
-        catch (IOException e) {
-            // Hopefully temporary problem due to remote IO or configuration. No cause to abort the sync so we just
-            // log it as a warning.
-            logWarning("Exception while retrieving deployment versions", e);
-        }
-
-        return getHighestVersion(available);
-    }
-
-    private Version getHighestVersion(SortedSet<Version> available) {
-        Version highest = Version.emptyVersion;
-        if (available != null && !available.isEmpty()) {
-            highest = available.last();
-        }
-        return highest;
-    }
-
-    private UpdateInstaller getUpdateInstaller(boolean streaming) {
-        if (streaming) {
+    private UpdateInstaller getUpdateInstaller() {
+        boolean updateUsingStreams = m_updateStreaming.get();
+        if (updateUsingStreams) {
             if (m_updateInstaller == null) {
                 m_updateInstaller = new StreamingUpdateInstaller(this);
             }
@@ -536,54 +578,24 @@ public class DefaultController extends C
         return m_updateInstaller;
     }
 
-    private void runAgentUpdate() throws RetryAfterException {
+    private void runAgentUpdate() throws RetryAfterException, IOException {
         logDebug("Checking for agent updates...");
 
-        AgentUpdateHandler agentUpdateHandler = getAgentUpdateHandler();
-
-        Version current = agentUpdateHandler.getInstalledVersion();
-        Version highest = getHighestAvailableAgentVersion();
-
-        if (highest.compareTo(current) < 1) {
-            logDebug("No agent update available for version %s", current);
-            return;
-        }
-
-        logInfo("Installing agent update %s => %s", current, highest);
+        long maxRetries = m_maxRetries.get();
+        boolean fixPackage = m_fixPackage.get();
 
-        InputStream inputStream = null;
-        try {
-            inputStream = agentUpdateHandler.getInputStream(highest);
-            agentUpdateHandler.install(inputStream);
-        }
-        catch (IOException e) {
-            // Hopefully temporary problem due to remote IO or configuration. No cause to abort the sync so we
-            // just log it as a warning.
-            // FIXME Does not cover failed updates and should handle retries
-            logWarning("Exception while installing agent update %s", e, highest);
-        }
-        finally {
-            closeSilently(inputStream);
-        }
+        UpdateInstaller updateInstaller = getUpdateInstaller();
+        updateInstaller.installUpdate(getAgentUpdateHandler(), fixPackage, maxRetries);
     }
 
-    private void runDeploymentUpdate() throws RetryAfterException {
+    private void runDeploymentUpdate() throws RetryAfterException, IOException {
         logDebug("Checking for deployment updates...");
 
-        DeploymentHandler deploymentHandler = getDeploymentHandler();
-
-        Version current = deploymentHandler.getInstalledVersion();
-        Version highest = getHighestAvailableDeploymentVersion();
-
         long maxRetries = m_maxRetries.get();
-        boolean updateUsingStreams = m_updateStreaming.get();
-        
-        UpdateInstaller updateInstaller = getUpdateInstaller(updateUsingStreams);
-        if (updateInstaller.canInstallUpdate(current, highest, maxRetries)) {
-            boolean fixPackage = m_fixPackage.get();
+        boolean fixPackage = m_fixPackage.get();
 
-            updateInstaller.installUpdate(current, highest, fixPackage);
-        }
+        UpdateInstaller updateInstaller = getUpdateInstaller();
+        updateInstaller.installUpdate(getDeploymentHandler(), fixPackage, maxRetries);
     }
 
     private void runFeedback() throws RetryAfterException {

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DeploymentHandlerImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DeploymentHandlerImpl.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DeploymentHandlerImpl.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DeploymentHandlerImpl.java Fri Sep 13 10:54:34 2013
@@ -31,6 +31,7 @@ import java.util.SortedSet;
 
 import org.apache.ace.agent.DeploymentHandler;
 import org.apache.ace.agent.DownloadHandle;
+import org.apache.ace.agent.InstallationFailedException;
 import org.apache.ace.agent.RetryAfterException;
 import org.apache.felix.deploymentadmin.DeploymentAdminImpl;
 import org.osgi.framework.BundleContext;
@@ -46,7 +47,68 @@ import org.osgi.service.packageadmin.Pac
 
 public class DeploymentHandlerImpl extends UpdateHandlerBase implements DeploymentHandler {
 
+    /**
+     * Internal EventAdmin that delegates to actual InternalEvents. Used to inject into the DeploymentAdmin only.
+     */
+    final class EventAdminBridge implements EventAdmin {
+        @Override
+        public void postEvent(Event event) {
+            getEventsHandler().postEvent(event.getTopic(), getPayload(event));
+        }
+
+        @Override
+        public void sendEvent(Event event) {
+            getEventsHandler().postEvent(event.getTopic(), getPayload(event));
+        }
+
+        private Map<String, String> getPayload(Event event) {
+            Map<String, String> payload = new HashMap<String, String>();
+            for (String propertyName : event.getPropertyNames()) {
+                payload.put(propertyName, event.getProperty(propertyName).toString());
+            }
+            return payload;
+        }
+    }
+    /**
+     * Internal LogService that wraps delegates to actual InternalLogger. Used to inject into the DeploymentAdmin only.
+     */
+    final class LogServiceBridge implements LogService {
+        @Override
+        public void log(int level, String message) {
+            log(level, message, null);
+        }
+
+        @Override
+        public void log(int level, String message, Throwable exception) {
+            switch (level) {
+                case LogService.LOG_WARNING:
+                    logWarning(message, exception);
+                    break;
+                case LogService.LOG_INFO:
+                    logInfo(message, exception);
+                    break;
+                case LogService.LOG_DEBUG:
+                    logDebug(message, exception);
+                    break;
+                default:
+                    logError(message, exception);
+                    break;
+            }
+        }
+
+        @Override
+        public void log(ServiceReference sr, int level, String message) {
+            log(level, message, null);
+        }
+
+        @Override
+        public void log(ServiceReference sr, int level, String message, Throwable exception) {
+            log(level, message, exception);
+        }
+    }
+
     private final DeploymentAdmin m_deploymentAdmin;
+
     private final boolean m_ownDeploymentAdmin;
 
     public DeploymentHandlerImpl(BundleContext bundleContext, PackageAdmin packageAdmin) {
@@ -66,20 +128,21 @@ public class DeploymentHandlerImpl exten
     }
 
     @Override
-    protected void onStart() throws Exception {
-        if (m_ownDeploymentAdmin) {
-            invokeMethod(m_deploymentAdmin, "start", new Class<?>[] {}, new Object[] {});
-        }
+    public SortedSet<Version> getAvailableVersions() throws RetryAfterException, IOException {
+        return getAvailableVersions(getEndpoint(getServerURL(), getIdentification()));
     }
 
     @Override
-    protected void onStop() throws Exception {
-        if (m_ownDeploymentAdmin) {
-            invokeMethod(m_deploymentAdmin, "stop", new Class<?>[] {}, new Object[] {});
-        }
+    public DownloadHandle getDownloadHandle(Version version, boolean fixPackage) throws RetryAfterException {
+        return getDownloadHandle(getPackageURL(version, fixPackage));
     }
 
     @Override
+    public InputStream getInputStream(Version version, boolean fixPackage) throws RetryAfterException, IOException {
+        return getInputStream(getPackageURL(version, fixPackage));
+    };
+
+    @Override
     public Version getInstalledVersion() {
         Version highestVersion = Version.emptyVersion;
         String identification = getIdentification();
@@ -94,35 +157,40 @@ public class DeploymentHandlerImpl exten
             }
         }
         return highestVersion;
-    }
+    };
 
     @Override
-    public void deployPackage(InputStream inputStream) throws DeploymentException {
-        m_deploymentAdmin.installDeploymentPackage(inputStream);
-    }
+    public String getName() {
+        return "deployment";
+    };
 
     @Override
-    public long getPackageSize(Version version, boolean fixPackage) throws RetryAfterException, IOException {
+    public long getSize(Version version, boolean fixPackage) throws RetryAfterException, IOException {
         return getPackageSize(getPackageURL(version, fixPackage));
-    };
+    }
 
     @Override
-    public InputStream getInputStream(Version version, boolean fixPackage) throws RetryAfterException, IOException {
-        return getInputStream(getPackageURL(version, fixPackage));
-    };
+    public void install(InputStream inputStream) throws InstallationFailedException, IOException {
+        try {
+            m_deploymentAdmin.installDeploymentPackage(inputStream);
+        }
+        catch (DeploymentException exception) {
+            throw new InstallationFailedException("Installation of deployment package failed!", exception);
+        }
+    }
 
     @Override
-    public DownloadHandle getDownloadHandle(Version version, boolean fixPackage) throws RetryAfterException, IOException {
-        return getDownloadHandle(getPackageURL(version, fixPackage));
-    };
+    protected void onStart() throws Exception {
+        if (m_ownDeploymentAdmin) {
+            invokeMethod(m_deploymentAdmin, "start", new Class<?>[] {}, new Object[] {});
+        }
+    }
 
     @Override
-    public SortedSet<Version> getAvailableVersions() throws RetryAfterException, IOException {
-        return getAvailableVersions(getEndpoint(getServerURL(), getIdentification()));
-    };
-
-    private URL getPackageURL(Version version, boolean fixPackage) throws RetryAfterException, IOException {
-        return getEndpoint(getServerURL(), getIdentification(), fixPackage ? getInstalledVersion() : Version.emptyVersion, version);
+    protected void onStop() throws Exception {
+        if (m_ownDeploymentAdmin) {
+            invokeMethod(m_deploymentAdmin, "stop", new Class<?>[] {}, new Object[] {});
+        }
     }
 
     private URL getEndpoint(URL serverURL, String identification) {
@@ -148,64 +216,7 @@ public class DeploymentHandlerImpl exten
         }
     }
 
-    /**
-     * Internal EventAdmin that delegates to actual InternalEvents. Used to inject into the DeploymentAdmin only.
-     */
-    final class EventAdminBridge implements EventAdmin {
-        @Override
-        public void postEvent(Event event) {
-            getEventsHandler().postEvent(event.getTopic(), getPayload(event));
-        }
-
-        @Override
-        public void sendEvent(Event event) {
-            getEventsHandler().postEvent(event.getTopic(), getPayload(event));
-        }
-
-        private Map<String, String> getPayload(Event event) {
-            Map<String, String> payload = new HashMap<String, String>();
-            for (String propertyName : event.getPropertyNames()) {
-                payload.put(propertyName, event.getProperty(propertyName).toString());
-            }
-            return payload;
-        }
-    }
-
-    /**
-     * Internal LogService that wraps delegates to actual InternalLogger. Used to inject into the DeploymentAdmin only.
-     */
-    final class LogServiceBridge implements LogService {
-        @Override
-        public void log(int level, String message) {
-            log(level, message, null);
-        }
-
-        @Override
-        public void log(int level, String message, Throwable exception) {
-            switch (level) {
-                case LogService.LOG_WARNING:
-                    logWarning(message, exception);
-                    break;
-                case LogService.LOG_INFO:
-                    logInfo(message, exception);
-                    break;
-                case LogService.LOG_DEBUG:
-                    logDebug(message, exception);
-                    break;
-                default:
-                    logError(message, exception);
-                    break;
-            }
-        }
-
-        @Override
-        public void log(ServiceReference sr, int level, String message) {
-            log(level, message, null);
-        }
-
-        @Override
-        public void log(ServiceReference sr, int level, String message, Throwable exception) {
-            log(level, message, exception);
-        }
+    private URL getPackageURL(Version version, boolean fixPackage) throws RetryAfterException {
+        return getEndpoint(getServerURL(), getIdentification(), fixPackage ? getInstalledVersion() : Version.emptyVersion, version);
     }
 }

Modified: ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadCallableImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadCallableImpl.java?rev=1522863&r1=1522862&r2=1522863&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadCallableImpl.java (original)
+++ ace/trunk/org.apache.ace.agent/src/org/apache/ace/agent/impl/DownloadCallableImpl.java Fri Sep 13 10:54:34 2013
@@ -18,7 +18,9 @@
  */
 package org.apache.ace.agent.impl;
 
-import java.io.BufferedInputStream;
+import static org.apache.ace.agent.impl.ConnectionUtil.close;
+import static org.apache.ace.agent.impl.ConnectionUtil.closeSilently;
+
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -26,169 +28,133 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.List;
-import java.util.Map;
 import java.util.concurrent.Callable;
 
-class DownloadCallableImpl implements Callable<Void> {
+import org.apache.ace.agent.DownloadHandle.DownloadProgressListener;
+import org.apache.ace.agent.DownloadState;
 
-    // test support
-    static final int FAIL_OPENCONNECTION = 1;
-    static final int FAIL_OPENINPUTSTREAM = 2;
-    static final int FAIL_OPENOUTPUTSTREAM = 3;
-    static final int FAIL_AFTERFIRSTWRITE = 4;
+/**
+ * Responsible for actually downloading content from a download handle.
+ */
+final class DownloadCallableImpl implements Callable<Void> {
+    private static final int SC_OK = 200;
+    private static final int SC_PARTIAL_CONTENT = 206;
 
     private final DownloadHandleImpl m_handle;
-    private final URL m_source;
+    private final DownloadProgressListener m_listener;
     private final File m_target;
     private final int m_readBufferSize;
-    private final int m_failAtPosition;
-
-    private volatile boolean m_abort = false;
 
-    DownloadCallableImpl(DownloadHandleImpl handle, URL source, File target, int readBufferSize, int failAtPosition) {
+    DownloadCallableImpl(DownloadHandleImpl handle, DownloadProgressListener listener, File target, int readBufferSize) {
         m_handle = handle;
-        m_source = source;
+        m_listener = listener;
         m_target = target;
         m_readBufferSize = readBufferSize;
-        m_failAtPosition = failAtPosition;
     }
 
     @Override
     public Void call() throws Exception {
-        return download();
-    }
-
-    /**
-     * Abort the download. Used instead of a cancel on the future so normal completion can take place.
-     */
-    void abort() {
-        m_abort = true;
-    }
-
-    @SuppressWarnings("resource")
-    private Void download() {
-
         int statusCode = 0;
-        Map<String, List<String>> headerFields = null;
-
-        BufferedInputStream inputStream = null;
-        BufferedOutputStream outputStream = null;
         HttpURLConnection httpUrlConnection = null;
-        try {
 
+        try {
             boolean partialContent = false;
             boolean appendTarget = false;
 
-            if (m_failAtPosition == FAIL_OPENCONNECTION)
-                throw new IOException("Failed on openConnection on request");
-            httpUrlConnection = (HttpURLConnection) m_source.openConnection();
+            httpUrlConnection = m_handle.openConnection();
 
             long targetSize = m_target.length();
             if (targetSize > 0) {
-                String rangeHeader = "bytes=" + targetSize + "-";
+                String rangeHeader = String.format("bytes=%d-", targetSize);
+
                 m_handle.logDebug("Requesting Range %s", rangeHeader);
+
                 httpUrlConnection.setRequestProperty("Range", rangeHeader);
             }
 
             statusCode = httpUrlConnection.getResponseCode();
-            headerFields = httpUrlConnection.getHeaderFields();
-            if (statusCode == 200) {
+            if (statusCode == SC_OK) {
                 partialContent = false;
             }
-            else if (statusCode == 206) {
+            else if (statusCode == SC_PARTIAL_CONTENT) {
                 partialContent = true;
             }
             else {
+                // TODO handle retry-after?!
                 throw new IOException("Unable to handle server response code " + statusCode);
             }
 
-            if (m_failAtPosition == FAIL_OPENINPUTSTREAM)
-                throw new IOException("Failed on openConnection on request");
-            inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
-
-            long contentLength = httpUrlConnection.getContentLength();
+            long totalBytes = httpUrlConnection.getContentLength();
             if (partialContent) {
                 String contentRange = httpUrlConnection.getHeaderField("Content-Range");
                 if (contentRange == null) {
                     throw new IOException("Server returned no Content-Range for partial content");
                 }
                 if (!contentRange.startsWith("bytes ")) {
-                    throw new IOException("Server returned non byes Content-Range " + contentRange);
+                    throw new IOException("Server returned non bytes Content-Range " + contentRange);
                 }
+
                 String tmp = contentRange;
                 tmp = tmp.replace("byes ", "");
                 String[] parts = tmp.split("/");
                 String start = parts[0].split("-")[0];
                 String end = parts[0].split("-")[1];
-                System.out.println("size:" + parts[1]);
-                System.out.println("from:" + start);
-                System.out.println("too:" + end);
 
-                if (parts[1].equals("*"))
-                    contentLength = -1;
-                else
-                    contentLength = Long.parseLong(parts[1]);
+                m_handle.logDebug("Size: %d, range from %d to %d.", parts[1], start, end);
+
+                if ("*".equals(parts[1])) {
+                    totalBytes = -1;
+                }
+                else {
+                    totalBytes = Long.parseLong(parts[1]);
+                }
             }
 
-            long progress = 0l;
+            long bytesRead = 0l;
             if (partialContent) {
-                progress = targetSize;
+                bytesRead = targetSize;
                 appendTarget = true;
             }
 
-            if (m_failAtPosition == FAIL_OPENOUTPUTSTREAM)
-                throw new IOException("Failed on outputStream");
-            outputStream = new BufferedOutputStream(new FileOutputStream(m_target, appendTarget));
+            InputStream inputStream = httpUrlConnection.getInputStream();
+            OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(m_target, appendTarget));
 
             byte buffer[] = new byte[m_readBufferSize];
-            int read = -1;
-            while (!m_abort && (read = inputStream.read(buffer)) >= 0) {
+            int read;
 
-                outputStream.write(buffer, 0, read);
-                progress += read;
-                m_handle.progressCallback(statusCode, headerFields, contentLength, progress);
-
-                if (m_failAtPosition == FAIL_AFTERFIRSTWRITE)
-                    throw new IOException("Failed after first write");
+            try {
+                while (!Thread.currentThread().isInterrupted() && (read = inputStream.read(buffer)) >= 0) {
+                    outputStream.write(buffer, 0, read);
+                    bytesRead += read;
 
-                if (Thread.currentThread().isInterrupted())
-                    m_abort = true;
+                    m_listener.progress(bytesRead, totalBytes);
+                }
             }
-
-            if (m_abort) {
-                m_handle.logDebug("Download stopped: %s" + m_source.toExternalForm());
-                m_handle.stoppedCallback(statusCode, headerFields, null);
+            finally {
+                closeSilently(outputStream);
+                closeSilently(inputStream);
             }
-            else {
-                m_handle.logDebug("Download completed: %s", m_source.toExternalForm());
-                m_handle.successfulCallback(statusCode, headerFields);
+
+            boolean stoppedEarly = (totalBytes > 0L && bytesRead < totalBytes);
+            if (stoppedEarly) {
+                m_handle.logDebug("Download stopped early: %d of %d bytes downloaded...", bytesRead, totalBytes);
+
+                m_listener.completed(new DownloadResultImpl(DownloadState.STOPPED, (File) null, statusCode));
+            } else {
+                m_handle.logDebug("Download completed: %d bytes downloaded...", totalBytes);
+                
+                m_listener.completed(new DownloadResultImpl(DownloadState.SUCCESSFUL, m_target, statusCode));
             }
         }
         catch (Exception e) {
-            m_handle.failedCallback(statusCode, headerFields, e);
+            m_handle.logWarning("Download failed!", e);
+
+            m_listener.completed(new DownloadResultImpl(DownloadState.FAILED, e, statusCode));
+        }
+        finally {
+            close(httpUrlConnection);
         }
-        cleanupQuietly(httpUrlConnection, inputStream, outputStream);
-        return null;
-    }
 
-    private static void cleanupQuietly(HttpURLConnection httpUrlConnection, InputStream inputStream, OutputStream outputStream) {
-        if (httpUrlConnection != null)
-            httpUrlConnection.disconnect();
-        if (inputStream != null)
-            try {
-                inputStream.close();
-            }
-            catch (IOException e) {
-                // ignore
-            }
-        if (outputStream != null)
-            try {
-                outputStream.close();
-            }
-            catch (IOException e) {
-                // ignore
-            }
+        return null;
     }
 }



Mime
View raw message