You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ja...@apache.org on 2013/11/28 12:01:14 UTC

svn commit: r1546342 - in /ace/trunk/org.apache.ace.agent.controller.itest: ./ .settings/ src/ src/org/ src/org/apache/ src/org/apache/ace/ src/org/apache/ace/agent/ src/org/apache/ace/agent/itest/ test/

Author: jawi
Date: Thu Nov 28 11:01:13 2013
New Revision: 1546342

URL: http://svn.apache.org/r1546342
Log:
ACE-433 - moved custom controller itest to its own project:

- this is in preparation for the new way custom controllers should be
  registered and used.


Added:
    ace/trunk/org.apache.ace.agent.controller.itest/   (with props)
    ace/trunk/org.apache.ace.agent.controller.itest/.classpath   (with props)
    ace/trunk/org.apache.ace.agent.controller.itest/.gitignore
    ace/trunk/org.apache.ace.agent.controller.itest/.project   (with props)
    ace/trunk/org.apache.ace.agent.controller.itest/.settings/
    ace/trunk/org.apache.ace.agent.controller.itest/.settings/org.eclipse.jdt.core.prefs
    ace/trunk/org.apache.ace.agent.controller.itest/bnd.bnd
    ace/trunk/org.apache.ace.agent.controller.itest/build.xml   (with props)
    ace/trunk/org.apache.ace.agent.controller.itest/src/
    ace/trunk/org.apache.ace.agent.controller.itest/src/.gitignore
    ace/trunk/org.apache.ace.agent.controller.itest/src/org/
    ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/
    ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/
    ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/
    ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/
    ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/BaseAgentControllerTest.java   (with props)
    ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/CustomAgentControllerTest.java   (with props)
    ace/trunk/org.apache.ace.agent.controller.itest/test/
    ace/trunk/org.apache.ace.agent.controller.itest/test/.gitignore

Propchange: ace/trunk/org.apache.ace.agent.controller.itest/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Thu Nov 28 11:01:13 2013
@@ -0,0 +1,8 @@
+bin
+bin_test
+generated
+store
+bundle-cache
+felix-cache
+test-output
+

Added: ace/trunk/org.apache.ace.agent.controller.itest/.classpath
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/.classpath?rev=1546342&view=auto
==============================================================================
Binary file - no diff available.

Propchange: ace/trunk/org.apache.ace.agent.controller.itest/.classpath
------------------------------------------------------------------------------
    svn:mime-type = application/xml

Added: ace/trunk/org.apache.ace.agent.controller.itest/.gitignore
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/.gitignore?rev=1546342&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.agent.controller.itest/.gitignore (added)
+++ ace/trunk/org.apache.ace.agent.controller.itest/.gitignore Thu Nov 28 11:01:13 2013
@@ -0,0 +1,3 @@
+/bin/
+/bin_test/
+/generated/

Added: ace/trunk/org.apache.ace.agent.controller.itest/.project
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/.project?rev=1546342&view=auto
==============================================================================
Binary file - no diff available.

Propchange: ace/trunk/org.apache.ace.agent.controller.itest/.project
------------------------------------------------------------------------------
    svn:mime-type = application/xml

Added: ace/trunk/org.apache.ace.agent.controller.itest/.settings/org.eclipse.jdt.core.prefs
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/.settings/org.eclipse.jdt.core.prefs?rev=1546342&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.agent.controller.itest/.settings/org.eclipse.jdt.core.prefs (added)
+++ ace/trunk/org.apache.ace.agent.controller.itest/.settings/org.eclipse.jdt.core.prefs Thu Nov 28 11:01:13 2013
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6

Added: ace/trunk/org.apache.ace.agent.controller.itest/bnd.bnd
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/bnd.bnd?rev=1546342&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.agent.controller.itest/bnd.bnd (added)
+++ ace/trunk/org.apache.ace.agent.controller.itest/bnd.bnd Thu Nov 28 11:01:13 2013
@@ -0,0 +1,85 @@
+Test-Cases: ${classes;CONCRETE;EXTENDS;junit.framework.TestCase}
+-runbundles: \
+	org.mockito.mockito-all,\
+	org.apache.ace.test;version=latest,\
+	org.apache.ace.builder;version=latest,\
+	org.apache.felix.configadmin,\
+	org.apache.felix.dependencymanager,\
+	org.apache.felix.dependencymanager.shell,\
+	org.apache.felix.gogo.command,\
+	org.apache.felix.gogo.runtime,\
+	org.apache.felix.gogo.shell,\
+	org.apache.felix.http.jetty,\
+	org.apache.felix.log,\
+	biz.aQute.bnd,\
+	osgi.cmpn
+Bundle-Activator: org.apache.ace.agent.impl.Activator
+Private-Package: \
+	org.apache.ace.range,\
+	org.apache.ace.agent,\
+	org.apache.ace.agent.impl,\
+	org.apache.ace.agent.itest,\
+	org.apache.ace.agent.updater,\
+	org.apache.ace.feedback,\
+	org.apache.ace.feedback.util,\
+	org.apache.felix.deploymentadmin,\
+	org.apache.felix.deploymentadmin.spi,\
+	org.osgi.service.event,\
+	org.osgi.service.log,\
+	org.osgi.util.tracker,\
+	aQute.bnd.annotation.component,\
+	aQute.bnd.help,\
+	aQute.bnd.service.action,\
+	aQute.lib.deployer,\
+	aQute.lib.osgi.eclipse,\
+	aQute.libg.filelock,\
+	aQute.bnd.build,\
+	aQute.bnd.maven.support,\
+	aQute.bnd.settings,\
+	aQute.libg.command,\
+	aQute.libg.tuple,\
+	aQute.lib.osgi,\
+	aQute.bnd.annotation,\
+	aQute.bnd.annotation.metatype,\
+	aQute.bnd.component,\
+	aQute.bnd.make,\
+	aQute.bnd.make.component,\
+	aQute.bnd.make.metatype,\
+	aQute.bnd.maven,\
+	aQute.bnd.service,\
+	aQute.lib.base64,\
+	aQute.lib.collections,\
+	aQute.lib.filter,\
+	aQute.lib.hex,\
+	aQute.lib.io,\
+	aQute.lib.tag,\
+	aQute.libg.cryptography,\
+	aQute.libg.generics,\
+	aQute.libg.header,\
+	aQute.libg.qtokens,\
+	aQute.libg.reporter,\
+	aQute.libg.sed,\
+	aQute.libg.tarjan,\
+	aQute.libg.version
+-runee: JavaSE-1.6
+-runvm: -ea
+-runfw: org.apache.felix.framework
+-buildpath: osgi.core;version='[4.2,5)',\
+	org.apache.ace.agent;version=latest,\
+	org.apache.ace.builder;version=latest,\
+	org.apache.ace.test;version=latest,\
+	org.apache.felix.dependencymanager,\
+	org.apache.felix.http.jetty,\
+	biz.aQute.bnd,\
+	junit.osgi,\
+	org.mockito.mockito-all	
+-runsystempackages: sun.reflect
+-runproperties: \
+	org.apache.felix.eventadmin.Timeout=0,\
+	org.apache.felix.log.storeDebug=true,\
+	org.apache.felix.log.maxSize=1000
+Import-Package: \
+	!org.osgi.service.component.annotations,\
+	*
+Bundle-Version: 1.0.0
+Bundle-Name: Apache ACE Agent Custom Controller itest

Added: ace/trunk/org.apache.ace.agent.controller.itest/build.xml
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/build.xml?rev=1546342&view=auto
==============================================================================
Binary file - no diff available.

Propchange: ace/trunk/org.apache.ace.agent.controller.itest/build.xml
------------------------------------------------------------------------------
    svn:mime-type = application/xml

Added: ace/trunk/org.apache.ace.agent.controller.itest/src/.gitignore
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/src/.gitignore?rev=1546342&view=auto
==============================================================================
    (empty)

Added: ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/BaseAgentControllerTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/BaseAgentControllerTest.java?rev=1546342&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/BaseAgentControllerTest.java (added)
+++ ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/BaseAgentControllerTest.java Thu Nov 28 11:01:13 2013
@@ -0,0 +1,161 @@
+package org.apache.ace.agent.itest;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ace.agent.AgentConstants;
+import org.apache.ace.agent.ConfigurationHandler;
+import org.apache.ace.builder.DeploymentPackageBuilder;
+import org.apache.ace.it.IntegrationTestBase;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+
+import aQute.lib.osgi.Builder;
+import aQute.lib.osgi.Jar;
+
+public abstract class BaseAgentControllerTest extends IntegrationTestBase {
+
+    protected static class TestBundle {
+        private final File m_file;
+
+        public TestBundle(String name, Version version, String... headers) throws Exception {
+            m_file = createBundle(name, version, headers);
+        }
+
+        public File getFile() {
+            return m_file;
+        }
+    }
+
+    protected static class TestPackage {
+        private final String m_name;
+        private final Version m_version;
+        private final File m_file;
+
+        public TestPackage(String name, Version version, TestBundle... bundles) throws Exception {
+            m_name = name;
+            m_version = version;
+
+            File[] files = new File[bundles.length];
+            for (int i = 0; i < bundles.length; i++) {
+                files[i] = bundles[i].getFile();
+            }
+            m_file = createPackage(m_name, m_version, files);
+        }
+
+        public String getName() {
+            return m_name;
+        }
+
+        public File getFile() {
+            return m_file;
+        }
+
+        public Version getVersion() {
+            return m_version;
+        }
+    }
+
+    protected static File createBundle(String bsn, Version version, String... headers) throws Exception {
+        Builder b = new Builder();
+
+        try {
+            b.setProperty("Bundle-SymbolicName", bsn);
+            b.setProperty("Bundle-Version", version.toString());
+            for (int i = 0; i < headers.length; i += 2) {
+                b.setProperty(headers[i], headers[i + 1]);
+            }
+            b.setProperty("Include-Resource", "bnd.bnd"); // prevent empty jar bug
+
+            Jar jar = b.build();
+            jar.getManifest(); // Not sure whether this is needed...
+
+            File file = File.createTempFile("testbundle", ".jar");
+            file.deleteOnExit();
+
+            jar.write(file);
+            return file;
+        }
+        finally {
+            b.close();
+        }
+    }
+
+    protected static File createPackage(String name, Version version, File... bundles) throws Exception {
+        DeploymentPackageBuilder builder = DeploymentPackageBuilder.createDeploymentPackage(name, version.toString());
+
+        OutputStream fos = null;
+        try {
+            for (File bundle : bundles) {
+                builder.addBundle(bundle.toURI().toURL());
+            }
+
+            File file = File.createTempFile("testpackage", ".jar");
+            file.deleteOnExit();
+
+            fos = new FileOutputStream(file);
+            builder.generate(fos);
+
+            return file;
+        }
+        finally {
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    @Override
+    protected void configureProvisionedServices() throws Exception {
+        resetAgentBundleState();
+    }
+
+    protected void resetAgentBundleState() throws Exception {
+        Bundle agentBundle = m_bundleContext.getBundle();
+        File dataDir = m_bundleContext.getDataFile("");
+
+        // System.out.println("BaseAgentTest: Stopping agent bundle");
+        agentBundle.stop();
+        // System.out.println("BaseAgentTest: Cleaning bundle data dir (" + dataDir + ")");
+        cleanDir(dataDir);
+        // System.out.println("BaseAgentTest: Cleaning system properties");
+        Set<String> keysBeRemoved = new HashSet<String>();
+        for (Object key : System.getProperties().keySet()) {
+            if (key instanceof String && ((String) key).startsWith(AgentConstants.CONFIG_KEY_NAMESPACE)) {
+                keysBeRemoved.add((String) key);
+            }
+        }
+        for (String removeKey : keysBeRemoved) {
+            System.clearProperty(removeKey);
+        }
+        // System.out.println("BaseAgentTest: Starting agent bundle");
+        agentBundle.start();
+    }
+
+    protected void configureAgent(ConfigurationHandler handler, String... configuration) {
+        Map<String, String> config = new HashMap<String, String>();
+        for (int i = 0; i < configuration.length; i += 2) {
+            config.put(configuration[i], configuration[i + 1]);
+        }
+        handler.putAll(config);
+    }
+
+    private void cleanDir(File dir) {
+        if (!dir.isDirectory()) {
+            throw new IllegalStateException();
+        }
+        File[] files = dir.listFiles();
+        for (File file : files) {
+            if (file.isDirectory()) {
+                cleanDir(file);
+            }
+            file.delete();
+        }
+        dir.delete();
+    }
+}

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

Added: ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/CustomAgentControllerTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/CustomAgentControllerTest.java?rev=1546342&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/CustomAgentControllerTest.java (added)
+++ ace/trunk/org.apache.ace.agent.controller.itest/src/org/apache/ace/agent/itest/CustomAgentControllerTest.java Thu Nov 28 11:01:13 2013
@@ -0,0 +1,491 @@
+/*
+ * 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.itest;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.ace.agent.AgentConstants;
+import org.apache.ace.agent.AgentControl;
+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.FeedbackChannel;
+import org.apache.ace.agent.FeedbackHandler;
+import org.apache.ace.agent.UpdateHandler;
+import org.apache.ace.test.constants.TestConstants;
+import org.apache.ace.test.utils.NetUtils;
+import org.apache.felix.dm.Component;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+import org.osgi.service.http.HttpService;
+
+/**
+ * Tests that we can create an agent with a completely custom controller, see {@link CustomController} for more
+ * information about the actual implementation.
+ * 
+ * @see CustomController
+ */
+public class CustomAgentControllerTest extends BaseAgentControllerTest {
+    /**
+     * Provides a simple implementation of {@link AgentUser} that always acknowledges a download and/or installation.
+     */
+    static class AcknowledgingAgentUser implements AgentUser {
+        @Override
+        public boolean downloadAvailableUpdate(UpdateType updateType, String agentId, Version from, Version to) {
+            // Always proceed with a download...
+            return true;
+        }
+
+        @Override
+        public boolean installAvailableUpdate(UpdateType updateType, String agentId, Version from, Version to) {
+            // Always proceed with the installation...
+            return true;
+        }
+    }
+
+    /**
+     * Denotes a "user" of our agent that is monitoring our agent and able to respond to questions.
+     */
+    static interface AgentUser {
+        /**
+         * Asks the user whether or not to download an available update.
+         * 
+         * @param updateType
+         *            the type of update to download, cannot be <code>null</code>;
+         * @param agentId
+         *            the identification of the agent that has an update available;
+         * @param from
+         *            the current installed version to upgrade from;
+         * @param to
+         *            the available version to upgrade to.
+         * @return <code>true</code> if the update should be downloaded, <code>false</code> otherwise.
+         */
+        boolean downloadAvailableUpdate(UpdateType updateType, String agentId, Version from, Version to);
+
+        /**
+         * Asks the user whether or not to install an available update, after it has been downloaded.
+         * 
+         * @param updateType
+         *            the type of update to install, cannot be <code>null</code>;
+         * @param agentId
+         *            the identification of the agent that has an update available;
+         * @param from
+         *            the current installed version to upgrade from;
+         * @param to
+         *            the available version to upgrade to.
+         * @return <code>true</code> if the update should be installed, <code>false</code> otherwise.
+         */
+        boolean installAvailableUpdate(UpdateType updateType, String agentId, Version from, Version to);
+    }
+
+    /**
+     * The actual custom controller as {@link Runnable} task, that simply loops and executes its tasks until notified to
+     * stop.
+     * 
+     * @see #run()
+     */
+    class CustomController implements Runnable {
+        private volatile boolean m_stop = false;
+
+        /**
+         * Main loop, will sleep for a little and once every 500 ms will do the following:
+         * <ol>
+         * <li>Synchronize all agent feedback with the server (see {@link #sendFeedbackToServer()});</li>
+         * <li>Check for agent updates (see {@link #checkForUpdate(UpdateType)});</li>
+         * <li>Check for deployment updates (see {@link #checkForUpdate(UpdateType)}).</li>
+         * </ol>
+         * <p>
+         * Note that this implementation does very little error checking and is rather stubborn when it comes across
+         * failures: it simply keeps retrying, which, for this use case, is acceptable.
+         * </p>
+         * 
+         * @see #stop()
+         * @see #checkForUpdate(UpdateType)
+         * @see #sendFeedbackToServer()
+         */
+        @Override
+        public void run() {
+            while (!m_stop) {
+                try {
+                    TimeUnit.MILLISECONDS.sleep(500);
+                }
+                catch (InterruptedException exception) {
+                    // We're requested to stop...
+                    break;
+                }
+
+                if (m_stop) {
+                    // Check once more whether we're not stopped while sleeping...
+                    break;
+                }
+
+                sendFeedbackToServer();
+
+                checkForUpdate(UpdateType.AGENT);
+
+                checkForUpdate(UpdateType.DEPLOYMENT);
+            }
+        }
+
+        /**
+         * Stops the main loop and allows the {@link #run()} loop to terminate (after it has done all of its work).
+         */
+        public void stop() {
+            m_stop = true;
+        }
+
+        /**
+         * Does the actual check for either the agent or deployment updates, and if available:
+         * <ol>
+         * <li>asks the "user" whether it should download this update, and if so;</li>
+         * <li>downloads the update to a temporary location;</li>
+         * <li>if the download is complete, it asks the "user" whether it should proceed with installing it, and if so;</li>
+         * <li>installs the agent/deployment update.</li>
+         * </ol>
+         * <p>
+         * In case an exception occurs during this check, it is logged and the method returns (early). No exceptions are
+         * propagated. In production code, a little more sophisticated error checking should be performed.
+         * </p>
+         * 
+         * @param updateType
+         *            the type of update we're performing, cannot be <code>null</code>.
+         */
+        private void checkForUpdate(UpdateType updateType) {
+            try {
+                UpdateHandler updateHandler = getUpdateHandler(updateType);
+
+                Version installed = updateHandler.getInstalledVersion();
+                Version available = updateHandler.getHighestAvailableVersion();
+                if (installed != null && installed.compareTo(available) < 0) {
+                    // Update available, ask the user whether we should download it...
+                    if (!m_agentUser.downloadAvailableUpdate(updateType, getAgentId(), installed, available)) {
+                        // No, we may not download this update now...
+                        return;
+                    }
+
+                    System.out.printf("Downloading %s update (from v%s to v%s)...%n", updateType, installed, available);
+
+                    DownloadHandle downloadHandle = updateHandler.getDownloadHandle(available, false /* fixPackage */);
+
+                    Future<DownloadResult> future = downloadHandle.start(new DownloadProgressListener() {
+                        @Override
+                        public void progress(long bytesRead) {
+                            System.out.printf("Download progress: %d bytes read...%n", bytesRead);
+                        }
+                    });
+                    // Block until the download is complete...
+                    DownloadResult result = future.get();
+
+                    // Download is complete, ask the user once more if we're allowed to install the update...
+                    if (m_agentUser.installAvailableUpdate(updateType, getAgentId(), installed, available)) {
+                        System.out.printf("Installing %s update (from v%s to v%s)...%n", updateType, installed, available);
+
+                        // We've confirmation that we can install this update...
+                        updateHandler.install(result.getInputStream());
+                    }
+                }
+            }
+            catch (Exception exception) {
+                System.out.printf("%s update failed with %s.%n", updateType, exception.getMessage());
+                exception.printStackTrace(System.out);
+            }
+        }
+
+        /**
+         * @return the identification of the current agent, as returned by the agent's API.
+         */
+        private String getAgentId() {
+            return m_control.getAgentId();
+        }
+
+        /**
+         * Returns the update handler for the given {@link UpdateType}.
+         * 
+         * @param updateType
+         *            the type of update we want an update handler for, cannot be <code>null</code>.
+         * @return an {@link UpdateHandler} instance, never <code>null</code>.
+         */
+        private UpdateHandler getUpdateHandler(UpdateType updateType) {
+            UpdateHandler updateHandler;
+            if (UpdateType.AGENT == updateType) {
+                updateHandler = m_control.getAgentUpdateHandler();
+            }
+            else {
+                updateHandler = m_control.getDeploymentHandler();
+            }
+            return updateHandler;
+        }
+
+        /**
+         * Synchronizes the agent's feedback with the server by retrieving all feedback channels and sending their
+         * feedback to the server in turn.
+         * <p>
+         * In case an exception occurs during this check, it is logged and the method returns (early). No exceptions are
+         * propagated. In production code, a little more sophisticated error checking should be performed.
+         * </p>
+         */
+        private void sendFeedbackToServer() {
+            try {
+                FeedbackHandler feedbackHandler = m_control.getFeedbackHandler();
+                Set<String> channelNames = feedbackHandler.getChannelNames();
+                for (String channelName : channelNames) {
+                    FeedbackChannel channel = feedbackHandler.getChannel(channelName);
+
+                    System.out.printf("Synchronizing feedback of %s with server...%n", channelName);
+
+                    channel.sendFeedback();
+                }
+            }
+            catch (Exception exception) {
+                System.out.printf("Feedback synchronization failed with %s.%n", exception.getMessage());
+                exception.printStackTrace(System.out);
+            }
+        }
+    }
+
+    /**
+     * Stub servlet that acts as an ACE server for our agent. Does only the bare minimum with respect to a complete
+     * server.
+     */
+    static class StubDeploymentServlet extends HttpServlet {
+        private static final long serialVersionUID = 1L;
+
+        private final Map<String, TestPackage> m_packages = new HashMap<String, TestPackage>();
+        private final String m_agentId;
+
+        public StubDeploymentServlet(String agentId, TestPackage... testPackages) {
+            m_agentId = agentId;
+
+            for (TestPackage testPackage : testPackages) {
+                m_packages.put(testPackage.getVersion().toString(), testPackage);
+            }
+        }
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+            String pathInfo = req.getPathInfo();
+
+            if (pathInfo.startsWith("/auditlog/query")) {
+                resp.setContentType("text/plain");
+                PrintWriter writer = resp.getWriter();
+                writer.println(req.getParameter("tid") + "," + req.getParameter("logid") + ",0-10");
+                writer.close();
+            }
+            else if (pathInfo.startsWith("/deployment/")) {
+                String pathinfoTail = pathInfo.replaceFirst("/deployment/" + m_agentId + "/versions/?", "");
+                if (pathinfoTail.equals("")) {
+                    sendVersions(resp);
+                }
+                else {
+                    TestPackage dpackage = m_packages.get(pathinfoTail);
+                    if (dpackage == null) {
+                        throw new IllegalStateException("Test error! Should never happen... " + pathinfoTail);
+                    }
+                    sendPackage(dpackage, req, resp);
+                }
+            }
+            else if (pathInfo.startsWith("/agent/")) {
+                String tail = pathInfo.replaceFirst("/agent/" + m_agentId + "/org.apache.ace.agent/versions/", "");
+                if ("".equals(tail)) {
+                    sendVersions(resp);
+                }
+            }
+            else {
+                resp.setContentLength(0);
+                resp.setStatus(HttpServletResponse.SC_OK);
+            }
+            resp.flushBuffer();
+        }
+
+        @Override
+        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
+            String pathInfo = request.getPathInfo();
+            if (pathInfo.startsWith("/auditlog/")) {
+                InputStream is = request.getInputStream();
+                while (is.read() != -1) {
+                }
+                is.close();
+            }
+            response.setContentType("text/plain");
+            response.flushBuffer();
+        }
+
+        @Override
+        protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+            response.setContentType("text/plain");
+            response.flushBuffer();
+        }
+
+        private void sendPackage(TestPackage dpackage, HttpServletRequest req, HttpServletResponse resp) throws IOException {
+            InputStream is = null;
+            OutputStream os = null;
+            try {
+                is = new FileInputStream(dpackage.getFile());
+                os = resp.getOutputStream();
+
+                int read;
+                byte[] buffer = new byte[4096];
+                do {
+                    read = is.read(buffer);
+                    if (read >= 0) {
+                        os.write(buffer, 0, read);
+                    }
+                }
+                while (read >= 0);
+            }
+            finally {
+                if (is != null) {
+                    is.close();
+                }
+                if (os != null) {
+                    os.close();
+                }
+            }
+        }
+
+        private void sendVersions(HttpServletResponse resp) throws IOException {
+            PrintWriter writer = resp.getWriter();
+            for (String version : m_packages.keySet()) {
+                writer.println(version);
+            }
+            writer.close();
+
+            resp.setContentType("text/plain");
+            resp.setStatus(200);
+            resp.flushBuffer();
+        }
+    }
+
+    /**
+     * Denotes the kind of update.
+     */
+    static enum UpdateType {
+        AGENT, DEPLOYMENT;
+    }
+
+    private static final Version V1_0_0 = Version.parseVersion("1.0.0");
+    private static final String TEST_BUNDLE_NAME_PREFIX = "test.bundle";
+    private static final String AGENT_ID = "defaultTargetID";
+
+    // Injected by Felix DM...
+    private volatile HttpService m_http;
+    private volatile AgentControl m_control;
+    private volatile AgentUser m_agentUser;
+
+    /**
+     * Tests that we can provide a custom controller implementation based on the following use-case:
+     * <p>
+     * The agent should check for updates, and if found, ask the user whether it should proceed to download this update.
+     * If confirmed, the download of the update is started, and when complete, the user is asked once more whether to
+     * proceed with the installation of the update.
+     * </p>
+     * 
+     * @see CustomController
+     */
+    public void testCustomController() throws Exception {
+        CustomController controller = new CustomController();
+
+        Thread thread = new Thread(controller);
+        thread.start();
+
+        try {
+            waitForInstalledVersion(V1_0_0);
+        }
+        finally {
+            controller.stop();
+            thread.join();
+        }
+    }
+
+    @Override
+    protected void configureAdditionalServices() throws Exception {
+        configureAgent(m_control.getConfigurationHandler(), AgentConstants.CONFIG_CONTROLLER_DISABLED, "true");
+
+        TestBundle bundle1v1 = new TestBundle(TEST_BUNDLE_NAME_PREFIX.concat("1"), V1_0_0);
+        TestPackage package1 = new TestPackage(AGENT_ID, V1_0_0, bundle1v1);
+
+        StubDeploymentServlet servlet = new StubDeploymentServlet(AGENT_ID, package1);
+
+        String url = String.format("http://localhost:%d/", TestConstants.PORT);
+        NetUtils.waitForURL(url, 404, 10000);
+
+        m_http.registerServlet("/", servlet, null, null);
+
+        NetUtils.waitForURL(url, 200, 10000);
+    }
+
+    @Override
+    protected void configureProvisionedServices() throws Exception {
+        m_bundleContext.registerService(AgentUser.class.getName(), new AcknowledgingAgentUser(), null);
+    }
+
+    @Override
+    protected void doTearDown() throws Exception {
+        // Remove all provisioned components...
+        m_dependencyManager.clear();
+
+        m_http.unregister("/");
+
+        // Force an uninstall of all remaining test bundles...
+        for (Bundle bundle : m_bundleContext.getBundles()) {
+            String bsn = bundle.getSymbolicName();
+            if (bsn.startsWith(TEST_BUNDLE_NAME_PREFIX)) {
+                bundle.uninstall();
+            }
+        }
+    }
+
+    @Override
+    protected Component[] getDependencies() {
+        return new Component[] {
+            createComponent()
+                .setImplementation(this)
+                .add(createServiceDependency().setService(HttpService.class).setRequired(true))
+                .add(createServiceDependency().setService(AgentControl.class).setRequired(true))
+                .add(createServiceDependency().setService(AgentUser.class).setRequired(true))
+        };
+    }
+
+    private void waitForInstalledVersion(Version version) throws Exception {
+        DeploymentHandler deploymentHandler = m_control.getDeploymentHandler();
+
+        int timeout = 100;
+        while (!deploymentHandler.getInstalledVersion().equals(version)) {
+            Thread.sleep(100);
+            if (timeout-- <= 0) {
+                fail("Timed out while waiting for deployment " + version);
+            }
+        }
+    }
+}

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

Added: ace/trunk/org.apache.ace.agent.controller.itest/test/.gitignore
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.agent.controller.itest/test/.gitignore?rev=1546342&view=auto
==============================================================================
    (empty)