Move the junit4osgi maven plugin to the trunk

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@720005 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/pom.xml b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/pom.xml
new file mode 100644
index 0000000..dafd5e5
--- /dev/null
+++ b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/pom.xml
@@ -0,0 +1,83 @@
+<!--
+	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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>org.apache.felix.ipojo.junit4osgi</groupId>
+	<artifactId>maven-junit4osgi-plugin</artifactId>
+	<packaging>maven-plugin</packaging>
+	<version>1.1.0-SNAPSHOT</version>
+	<name>maven-junit4osgi-plugin Maven Mojo</name>
+	<url>http://maven.apache.org</url>
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.maven</groupId>
+			<artifactId>maven-plugin-api</artifactId>
+			<version>2.0</version>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>3.8.1</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.framework</artifactId>
+			<version>1.4.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.maven</groupId>
+			<artifactId>maven-artifact</artifactId>
+			<version>2.0.7</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.maven</groupId>
+			<artifactId>maven-project</artifactId>
+			<version>2.0.7</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.maven</groupId>
+			<artifactId>maven-model</artifactId>
+			<version>2.0.7</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.maven</groupId>
+			<artifactId>maven-plugin-api</artifactId>
+			<version>2.0.7</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.ipojo</artifactId>
+			<version>1.1.0-SNAPSHOT</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.ipojo.handler.extender</artifactId>
+			<version>1.1.0-SNAPSHOT</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>ipojo.examples</groupId>
+			<artifactId>org.apache.felix.ipojo.junit4osgi</artifactId>
+			<version>1.1.0-SNAPSHOT</version>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Installer.java b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Installer.java
new file mode 100644
index 0000000..a7a8078
--- /dev/null
+++ b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Installer.java
@@ -0,0 +1,196 @@
+/*

+ * 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.felix.ipojo.junit4osgi.plugin;

+

+import java.io.File;

+import java.net.MalformedURLException;

+import java.net.URL;

+import java.util.List;

+

+import org.apache.maven.artifact.Artifact;

+import org.apache.maven.project.MavenProject;

+import org.osgi.framework.Bundle;

+import org.osgi.framework.BundleActivator;

+import org.osgi.framework.BundleContext;

+import org.osgi.framework.BundleException;

+

+/**

+ * Bundle Activator installing bundles in the embedded OSGi.

+ * Installed bundles are the junit4osgi framework, the required bundle and the artifact bundle (if enable).

+ * Bundles are installed from the local maven repository.

+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>

+ */

+public class Installer implements BundleActivator {

+    

+    /**

+     * The list of artifact containing bundles for the junit4osgi framework.

+     */

+    private List artifacts;

+    

+    /**

+     * The current maven project. 

+     */

+    private MavenProject project;

+    

+    /**

+     * Flag enabling/disbling the deployment of the current

+     * project artifact.

+     */

+    private boolean deployCurrent;

+    

+    /**

+     * List of bundle URLs to install. 

+     */

+    private List bundles;

+    

+    

+    /**

+     * Creates a Installer

+     * @param artifacts the list of artifact containing bundles for the junit4osgi framework.

+     * @param bundles the list of bundle URLs to install 

+     * @param project the current maven project

+     * @param deployCurrentArtifact flag enabling/disabling the deployment of the current project artifact

+     */

+    public Installer(List artifacts, List bundles, MavenProject project, boolean deployCurrentArtifact) {

+        this.artifacts = artifacts;

+        this.project = project;

+        deployCurrent = deployCurrentArtifact;

+        this.bundles = bundles;

+    }

+    

+    /**

+     * Installs and starts the iPOJO bundle.

+     * @param context  the bundle context.

+     * @throws BundleException when the bundle cannot be installed or started correctly

+     */

+    private void installIPOJO(BundleContext context) throws BundleException {

+        String path = getUrlByArtifactId("org.apache.felix.ipojo").toString();

+        Bundle bundle = context.installBundle(path);

+        bundle.start();

+    }

+    

+    /**

+     * Installs and starts the Junit4OSGi bundle.

+     * @param context  the bundle context.

+     * @throws BundleException when the bundle cannot be installed or started correctly

+     */

+    private void installJunit(BundleContext context) throws BundleException {

+        String path = getUrlByArtifactId("org.apache.felix.ipojo.junit4osgi").toString();

+        Bundle bundle = context.installBundle(path);

+        bundle.start();

+    }

+    

+    /**

+     * Installs and starts the iPOJO Extender Handler bundle.

+     * @param context  the bundle context.

+     * @throws BundleException when the bundle cannot be installed or started correctly

+     */

+    private void installExtender(BundleContext context) throws BundleException {

+        String path = getUrlByArtifactId("org.apache.felix.ipojo.handler.extender").toString();

+        Bundle bundle = context.installBundle(path);

+        bundle.start();

+    }

+    

+    /**

+     * Installs and Starts required bundles.

+     * @throws BundleException when a bundle cannot be installed or started correctly

+     */

+    private void deployBundles(BundleContext context) throws BundleException {

+        for (int i = 0; i < bundles.size(); i++) {

+            URL url = (URL) bundles.get(i);

+            Bundle bundle = context.installBundle(url.toString());

+            bundle.start();

+        }

+    }

+    

+    /**

+     * Gets the bundle URL from the artifact list.

+     * @param id the dependency id.

+     * @return the bundle URL or <code>null</code> if the URL cannot

+     * be found.

+     */

+    private URL getUrlByArtifactId(String id) {

+        for (int i = 0; i < artifacts.size(); i++) {

+            Artifact artifact = (Artifact) artifacts.get(i);

+            if (artifact.getArtifactId().equals(id)) {

+                try {

+                    return artifact.getFile().toURL();

+                } catch (MalformedURLException e) {

+                    e.printStackTrace();

+                }

+            }

+        }

+        return null;

+    }

+

+    /**

+     * Deploys the current bundle if enable.

+     * @param context the bundle context

+     * @throws BundleException when the bundle cannot be installed or started correctly.

+     */

+    private void deployProjectArtifact(BundleContext context)

+            throws BundleException {

+        if (!deployCurrent) {

+            return;

+        }

+

+        File file = project.getArtifact().getFile();

+        if (file.exists()) {

+            URL url = null;

+            try {

+                url = file.toURL();

+            } catch (MalformedURLException e) {

+                e.printStackTrace();

+            }

+            Bundle bundle = context.installBundle(url.toString());

+            bundle.start();

+        } else {

+            throw new BundleException("The current project artifact does not exist (" + file.getAbsolutePath() + ")");

+        }

+    }

+

+

+    /**

+     * Start bundles.

+     * @param context the bundle context.

+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)

+     */

+    public void start(BundleContext context) {

+        try {

+            installIPOJO(context);

+            installExtender(context);

+            installJunit(context);

+            deployBundles(context);

+            deployProjectArtifact(context);

+        } catch (BundleException e) {

+            System.err.println("Cannot start the framework : " + e.getMessage());

+            return;

+        }

+    }

+

+    /**

+     * Stopping methods.

+     * @param context the bundle context.

+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)

+     */

+    public void stop(BundleContext context) {

+        // Do nothing.

+    }

+

+}
\ No newline at end of file
diff --git a/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Junit4osgiPlugin.java b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Junit4osgiPlugin.java
new file mode 100644
index 0000000..6af90e8
--- /dev/null
+++ b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Junit4osgiPlugin.java
@@ -0,0 +1,461 @@
+/*
+ * 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.felix.ipojo.junit4osgi.plugin;/*
+
+ *
+ * Licensed 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.
+ */
+
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarFile;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestFailure;
+import junit.framework.TestListener;
+import junit.framework.TestResult;
+
+import org.apache.felix.framework.Felix;
+import org.apache.felix.ipojo.junit4osgi.plugin.log.LogServiceImpl;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Goal starting Felix and executing junit4osgi tests.
+ *  
+ * @goal test
+ * @phase integration-test
+ * @requiresDependencyResolution runtime
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ * 
+ */
+public class Junit4osgiPlugin extends AbstractMojo {
+
+    /**
+     * The Maven project.
+     * 
+     * @parameter expression="${project}"
+     * @required
+     * @readonly
+     */
+    private MavenProject project;
+    
+    /** @parameter expression="${plugin.artifacts}" */
+    private java.util.List pluginArtifacts;
+    
+    /**
+     * Base directory where all reports are written to.
+     * 
+     * @parameter expression="${project.build.directory}/junit4osgi-reports"
+     */
+    private File reportsDirectory;
+    
+    /**
+     * Base directory where all reports are written to.
+     * 
+     * @parameter expression="${project.build.directory}"
+     */
+    private File targetDir;
+    
+    /**
+     * Must the current artifact be deployed?.
+     * 
+     * @parameter expression="${deployProjectArtifact}" default-value="false"
+     */
+    private boolean deployProjectArtifact;
+    
+    /**
+     * Required bundles
+     * 
+     * @parameter expression="${bundles}"
+     */
+    private ArrayList bundles;
+    
+    int total;
+    int totalFailures;
+    int totalErrors;
+    
+    List errors = new ArrayList();
+    List failures = new ArrayList();
+    List results = new ArrayList();
+    
+    /**
+     * Log Service exposed by the plugin framework.
+     */
+    private LogServiceImpl logService;
+    
+   
+    public void execute() throws MojoFailureException {
+        
+        List bundles = parseBundleList();
+        bundles.addAll(getTestBundle());
+        
+        List activators = new ArrayList();
+        logService = new LogServiceImpl();
+        activators.add(logService);
+        activators.add(new Installer(pluginArtifacts, bundles, project, deployProjectArtifact));
+        Map map = new HashMap();
+        map.put("felix.systembundle.activators", activators);
+        map.put("org.osgi.framework.storage.clean", "onFirstInit");
+        map.put("ipojo.log.level", "WARNING");
+        map.put("org.osgi.framework.bootdelegation", "junit.framework, org.osgi.service.log");
+        map.put("org.osgi.framework.storage", targetDir.getAbsolutePath() + "/felix-cache"); 
+
+        
+        System.out.println("");
+        System.out.println("-------------------------------------------------------");
+        System.out.println(" T E S T S");        
+        System.out.println("-------------------------------------------------------");
+        
+        Felix felix = new Felix(map);
+        try {
+            felix.start();
+        } catch (BundleException e) {
+            e.printStackTrace();
+        }
+        
+        waitForStability(felix.getBundleContext());
+        
+
+        Object runner = waitForRunnerService(felix.getBundleContext());
+        invokeRun(runner, felix.getBundleContext());
+                
+        try {
+            felix.stop();
+            felix.waitForStop(5000);
+        } catch (Exception e) {
+            getLog().error(e);
+        }
+        
+        if (totalErrors > 0 || totalFailures > 0) {
+            throw new MojoFailureException("There are test failures. \n\n" +
+                    "Please refer to " + reportsDirectory.getAbsolutePath() + 
+                    " for the individual test results.");
+        }
+
+    }
+    
+    private void waitForStability(BundleContext context) throws MojoFailureException {
+        // Wait for bundle initialization.
+        boolean bundleStability = getBundleStability(context);
+        int count = 0;
+        while(!bundleStability && count < 500) {
+            try {
+                Thread.sleep(5);
+            } catch (InterruptedException e) {
+                // Interrupted
+            }
+            count++;
+            bundleStability = getBundleStability(context);
+        }
+        
+        if (count == 500) {
+            getLog().error("Bundle stability isn't reached after 500 tries");
+            dumpBundles(context);
+            throw new MojoFailureException("Cannot reach the bundle stability");
+        }
+        
+        boolean serviceStability = false;
+        count = 0;
+        int count1 = 0, count2 = 0;
+        while(! serviceStability && count < 500) {
+            try {
+                ServiceReference[] refs = context.getServiceReferences(null, null);
+                count1 = refs.length;
+                Thread.sleep(500);
+                refs = context.getServiceReferences(null, null);
+                count2 = refs.length;
+                serviceStability = (count1 == count2);
+            } catch (Exception e) {
+                getLog().error(e);
+                serviceStability = false;
+                // Nothing to do, while recheck the condition
+            }
+            count++;
+        }
+        
+        if (count == 500) {
+            getLog().error("Service stability isn't reached after 500 tries (" + count1 + " != " + count2);
+            dumpBundles(context);
+            throw new MojoFailureException("Cannot reach the service stability");
+        }
+        
+    }
+    
+    private boolean getBundleStability(BundleContext bc) {
+        boolean stability = true;
+        Bundle[] bundles = bc.getBundles();
+        for (int i = 0; i < bundles.length; i++) {
+            stability = stability && (bundles[i].getState() == Bundle.ACTIVE);
+        }
+        return stability;
+    }
+
+    private List parseBundleList() {
+        List toDeploy = new ArrayList();
+        if (bundles == null) {
+            return toDeploy;
+        }
+        
+        for (int i = 0; i < bundles.size(); i++) {
+            String bundle = (String) bundles.get(i);
+            try {
+                URL url = new URL(bundle);
+                toDeploy.add(url);
+            } catch (MalformedURLException e) {
+               // Not a valid url,
+               getLog().error(bundle + " is not a valid url, bundle ignored");
+              
+            }
+        }
+        
+        return toDeploy;
+    }
+    
+    private List getTestBundle() {
+        List toDeploy = new ArrayList();
+        Set dependencies = project.getDependencyArtifacts();
+        for (Iterator artifactIterator = dependencies.iterator(); artifactIterator.hasNext();) {
+            Artifact artifact = (Artifact) artifactIterator.next();
+            if (Artifact.SCOPE_TEST.equals(artifact.getScope())) { // Select scope=test.
+                File file = artifact.getFile();
+                try {
+                    JarFile jar = new JarFile(file);
+                    if (jar.getManifest().getMainAttributes().getValue("Bundle-ManifestVersion") != null) {
+                        toDeploy.add(file.toURL());
+                    }
+                } catch (Exception e) {
+                    getLog().error(e);
+                }
+            }
+        }
+        return toDeploy;
+    }
+    
+    private Object waitForRunnerService(BundleContext bc) {
+        ServiceReference ref = bc.getServiceReference(org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner.class.getName());
+        int count = 0;
+        while (ref == null && count < 1000) {
+            try {
+                Thread.sleep(5);
+            } catch (InterruptedException e) {
+                // Nothing to do
+            }
+            ref = bc.getServiceReference(org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner.class.getName());
+        }
+        if (ref != null) {
+            return bc.getService(ref);
+        }
+        getLog().error("Junit Runner service unavailable");
+        return null;
+    }
+    
+    private void invokeRun(Object runner, BundleContext bc) {
+        Method getTest;
+        
+        try {
+            getTest = runner.getClass().getMethod("getTests", new Class[0]);
+            List tests = (List) getTest.invoke(runner, new Object[0]);
+            Method run = getRunMethod(runner);
+            for (int i = 0; i < tests.size(); i++) {
+                executeTest(runner, (Test) tests.get(i),run, bc);
+            }
+            System.out.println("\nResults :");
+            if (failures.size() > 0) {
+                System.out.println("\nFailed tests:");
+                for (int i = 0; i < failures.size(); i++) {
+                    TestResult tr = (TestResult) failures.get(i);
+                    Enumeration e = tr.failures();
+                    while(e.hasMoreElements()) {
+                        TestFailure tf = (TestFailure) e.nextElement();
+                        System.out.println(" " + tf.toString());
+                    }
+                }
+            }
+            
+            if (failures.size() > 0) {
+                System.out.println("\nTests in error:");
+                for (int i = 0; i < errors.size(); i++) {
+                    TestResult tr = (TestResult) errors.get(i);
+                    Enumeration e = tr.errors();
+                    while(e.hasMoreElements()) {
+                        TestFailure tf = (TestFailure) e.nextElement();
+                        System.out.println(" " + tf.toString());
+                    }
+                }
+            }
+            
+            System.out.println("\nTests run: " + total + ", Failures: " + totalFailures + ", Errors:" + totalErrors + "\n");          
+        } catch (Exception e) {
+            getLog().error(e);
+        } 
+    }
+    
+    private Method getRunMethod(Object runner) {
+        Method[] methods = runner.getClass().getMethods();
+        for (int i = 0; i < methods.length; i++) {
+            if (methods[i].getName().equals("run") 
+                    && methods[i].getParameterTypes().length == 1
+                    && ! methods[i].getParameterTypes()[0].equals(Long.TYPE)) {
+                return methods[i];
+            }
+        }
+        getLog().error("Cannot find the run method");
+        return null;
+    }
+    
+    private String getTestName(Object test) {
+        try {
+            Method getName = test.getClass().getMethod("getName", new Class[0]);
+            String name = (String) getName.invoke(test, new Object[0]);
+            if (name == null) {
+                name = test.toString();
+            }
+            return name;
+        } catch (Exception e) {
+            getLog().error(e);
+            return null;
+        }
+            
+    }
+    
+    private void executeTest(Object runner, Test test, Method run, BundleContext bc) {
+        try {
+        XMLReport report = new XMLReport();
+        String name = getTestName(test);
+        System.out.println("Running " + name);
+
+        TestResult tr = new TestResult();
+        tr.addListener(new ResultListener(report));
+        test.run(tr);        
+        results.add(tr);
+        
+        if (tr.wasSuccessful()) {
+            System.out.println("Tests run: " + tr.runCount() + ", Failures: " + tr.failureCount() + ", Errors: " + tr.errorCount() + ", Time elapsed: " + report.elapsedTimeAsString(report.endTime - report.endTime) + " sec");
+        } else {
+            System.out.println("Tests run: " + tr.runCount() + ", Failures: " + tr.failureCount() + ", Errors: " + tr.errorCount() + ", Time elapsed: " + report.elapsedTimeAsString(report.endTime - report.endTime) + " sec <<< FAILURE!");
+            if (tr.errorCount() > 0) {
+                errors.add(tr);
+            }
+            if (tr.failureCount() > 0) {
+                failures.add(tr);
+            }
+        }
+        
+        total += tr.runCount();
+        totalFailures += tr.failureCount();
+        totalErrors += tr.errorCount();
+        
+        report.generateReport(test, tr, reportsDirectory, bc);
+        
+        } catch (Exception e) {
+            getLog().error(e);
+        }
+        
+       
+        
+    }
+    
+    public void dumpBundles(BundleContext bc) {
+        getLog().info("Bundles:");
+        Bundle[] bundles = bc.getBundles();
+        for (int i = 0; i < bundles.length; i++) {
+            getLog().info(bundles[i].getSymbolicName() + " - " + bundles[i].getState());
+        }
+    }
+    
+    public LogServiceImpl getLogService() {
+        return logService;
+    }
+    
+    
+    private class ResultListener implements TestListener {
+        
+        private XMLReport report;
+        private boolean abort;
+        
+        private PrintStream outBackup = System.out;
+        private PrintStream errBackup = System.err;
+        
+        private StringOutputStream out = new StringOutputStream();
+        private StringOutputStream err = new StringOutputStream();;
+        
+        public ResultListener(XMLReport report) {
+            this.report = report;
+        }
+
+        public void addError(Test test, Throwable throwable) {
+            report.testError(test, throwable, out.toString(), err.toString(), getLogService().getLoggedMessages());
+            abort = true;
+        }
+
+        public void addFailure(Test test,
+                AssertionFailedError assertionfailederror) {
+            report.testFailed(test, assertionfailederror, out.toString(), err.toString(), getLogService().getLoggedMessages());
+            abort = true;
+            
+        }
+
+        public void endTest(Test test) {
+           if (!abort) {
+               report.testSucceeded(test);
+           }
+           System.setErr(errBackup);
+           System.setOut(outBackup);
+           getLogService().reset();
+        }
+
+        public void startTest(Test test) {
+            abort = false;
+            report.testStarting();
+            System.setErr(new PrintStream(err));
+            System.setOut(new PrintStream(out));
+            getLogService().enableOutputStream();
+        }
+        
+    }
+    
+}
diff --git a/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Report.java b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Report.java
new file mode 100644
index 0000000..cbb8905
--- /dev/null
+++ b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/Report.java
@@ -0,0 +1,238 @@
+/*
+ * 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.felix.ipojo.junit4osgi.plugin;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import junit.framework.Test;
+
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * Test report. 
+ * This class is provides the basics to support several output format.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Report {
+    
+    /**
+     * Number of ran tests.
+     */
+    protected int completedCount;
+
+    /**
+     * Number of errors
+     */
+    protected int errorsCount;
+    
+    /**
+     * Number of failures
+     */
+    protected int failuresCount;
+    
+    
+    /**
+     * Failing tests.
+     */
+    private List failureSources = new ArrayList();
+    
+    /**
+     * Tests in error
+     */
+    private List errorSources = new ArrayList();
+    
+    /**
+     * Time at the beginning of the test execution. 
+     */
+    protected long startTime;
+
+    /**
+     * Time at the end of the test execution. 
+     */
+    protected long endTime;
+
+    /**
+     * Double format. 
+     */
+    private NumberFormat numberFormat = NumberFormat.getInstance( Locale.US );
+
+    /**
+     * New line constant.
+     */
+    protected static final String NL = System.getProperty( "line.separator" );
+   
+    /**
+     * Gets failing tests.
+     * @return the list of failing tests.
+     */
+    public List getFailureSources() {
+        return this.failureSources;
+    }
+
+    /**
+     * Gets tests in error.
+     * @return the list of test throwing unexpected exceptions
+     */
+    public List getErrorSources() {
+        return this.errorSources;
+    }
+    
+    /**
+     * Callback called when a test starts.
+     */
+    public void testStarting() {
+        startTime = System.currentTimeMillis();
+    }
+
+    /**
+     * Callback called when a test ends successfully.
+     */
+    public void testSucceeded() {
+        endTest();
+    }
+
+    /**
+     * Callback called when a test throws an unexpected error.
+     * @param test the test in error.
+     */
+    public void testError(Test test) {
+        ++errorsCount;
+        errorSources.add(test.toString());
+        endTest();
+    }
+
+    /**
+     * Callback called when a test fails.
+     * @param test the failing test.
+     */
+    public void testFailed(Test test) {
+        ++failuresCount;
+        failureSources.add(test.toString());
+        endTest();
+    }
+
+    /**
+     * Callback called when a test ends.
+     * This method handles common action when a test ends.
+     */
+    private void endTest() {
+        ++completedCount;
+
+        endTime = System.currentTimeMillis();
+
+        if (startTime == 0) {
+            startTime = endTime;
+        }
+    }
+    
+    
+    public int getNumErrors() {
+        return errorsCount;
+    }
+
+    public int getNumFailures() {
+        return failuresCount;
+    }
+
+    public int getNumTests() {
+        return completedCount;
+    }
+
+    /**
+     * Reset the report.
+     */
+    public void reset() {
+        errorsCount = 0;
+
+        failuresCount = 0;
+
+        completedCount = 0;
+
+        this.failureSources = new ArrayList();
+
+        this.errorSources = new ArrayList();
+
+    }
+
+
+    /**
+     * Returns the formatted String to display the given double.
+     * @param runTime the elapsed time
+     * @return the String displaying the elapsed time
+     */
+    protected String elapsedTimeAsString(long runTime) {
+        return numberFormat.format((double) runTime / 1000);
+    }
+
+    /**
+     * Returns the stacktrace as String.
+     * @param report ReportEntry object.
+     * @return stacktrace as string.
+     */
+    protected String getStackTrace(Test test, Throwable e) {
+
+        if (e == null) {
+            return "";
+        }
+
+        StringWriter w = new StringWriter();
+        if (e != null) {
+            e.printStackTrace(new PrintWriter(w));
+            w.flush();
+        }
+        String text = w.toString();
+        String marker = "at " + test.toString();
+
+        String[] lines = StringUtils.split(text, "\n");
+        int lastLine = lines.length - 1;
+        int causedByLine = -1;
+        // skip first
+        for (int i = 1; i < lines.length; i++) {
+            String line = lines[i].trim();
+            if (line.startsWith(marker)) {
+                lastLine = i;
+            } else if (line.startsWith("Caused by")) {
+                causedByLine = i;
+                break;
+            }
+        }
+
+        StringBuffer trace = new StringBuffer();
+        for (int i = 0; i <= lastLine; i++) {
+            trace.append(lines[i]);
+            trace.append("\n");
+        }
+
+        if (causedByLine != -1) {
+            for (int i = causedByLine; i < lines.length; i++) {
+                trace.append(lines[i]);
+                trace.append("\n");
+            }
+        }
+        return trace.toString();
+    }
+    
+    
+
+}
diff --git a/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/StringOutputStream.java b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/StringOutputStream.java
new file mode 100644
index 0000000..60186cf
--- /dev/null
+++ b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/StringOutputStream.java
@@ -0,0 +1,99 @@
+package org.apache.felix.ipojo.junit4osgi.plugin;
+
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/**
+ * Provides an OutputStream to an internal String. Internally converts bytes to
+ * a Strings and stores them in an internal StringBuffer.
+ */
+public class StringOutputStream extends OutputStream implements Serializable {
+
+    /**
+     * Id.
+     */
+    private static final long serialVersionUID = -5912060965986156224L;
+    
+    /**
+     * The internal destination StringBuffer.
+     */
+    protected StringBuffer buf = null;
+
+    /**
+     * Creates new StringOutputStream, makes a new internal StringBuffer.
+     */
+    public StringOutputStream() {
+        super();
+        buf = new StringBuffer();
+    }
+
+    /**
+     * Returns the content of the internal StringBuffer as a String, the result
+     * of all writing to this OutputStream.
+     * 
+     * @return returns the content of the internal StringBuffer
+     */
+    public String toString() {
+        return buf.toString();
+    }
+
+    /**
+     * Sets the internal StringBuffer to null.
+     */
+    public void close() {
+        buf = null;
+
+    }
+
+    /**
+     * Writes and appends a byte array to StringOutputStream.
+     * 
+     * @param b the byte array
+     * @param off the byte array starting index
+     * @param len the number of bytes from byte array to write to the stream
+     */
+    public void write(byte[] b, int off, int len) {
+        if ((off < 0) || (len < 0) || (off + len) > b.length) {
+            throw new IndexOutOfBoundsException(
+                    "StringOutputStream.write: Parameters out of bounds.");
+        }
+        byte[] bytes = new byte[len];
+        for (int i = 0; i < len; i++) {
+            bytes[i] = b[off];
+            off++;
+        }
+        buf.append(toCharArray(bytes));
+    }
+
+    /**
+     * Writes and appends a single byte to StringOutputStream.
+     * 
+     * @param b the byte as an int to add
+     */
+    public void write(int b) {
+        buf.append((char) b);
+    }
+    
+    /**
+     * Writes and appends a String to StringOutputStream.
+     * 
+     * @param s the String to add
+     */
+    public void write(String s) {
+        buf.append(s);
+    }
+    
+    /**
+     * Converts byte array to char array.
+     */
+    public static char[] toCharArray(byte[] barr) {
+        if (barr == null) {
+            return null;
+        }
+        char[] carr = new char[barr.length];
+        for (int i = 0; i < barr.length; i++) {
+            carr[i] = (char) barr[i];
+        }
+        return carr;
+    }
+}
diff --git a/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/XMLReport.java b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/XMLReport.java
new file mode 100644
index 0000000..6d3a722
--- /dev/null
+++ b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/XMLReport.java
@@ -0,0 +1,312 @@
+/*
+ * 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.felix.ipojo.junit4osgi.plugin;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.codehaus.plexus.util.xml.Xpp3DomWriter;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * This class generates test result as XML files compatible with Surefire.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class XMLReport extends Report {
+    
+    /**
+     * List of results.
+     */
+    private List results = new ArrayList();
+
+    /**
+     * A test ends successfully.
+     * @param test the test executed successfully.
+     */
+    public void testSucceeded(Test test) {
+        super.testSucceeded();
+
+        long runTime = this.endTime - this.startTime;
+
+        Xpp3Dom testCase = createTestElement(test, runTime);
+
+        results.add(testCase);
+    }
+
+    /**
+     * A test throws an unexpected errors.
+     * @param test the test in error
+     * @param e the thrown exception
+     */
+    public void testError(Test test, Throwable e, String out, String err, String log) {
+        super.testError(test);
+
+        writeTestProblems(test, e, "error", out, err, log);
+    }
+
+    /**
+     * A test fails.
+     * @param test the failing test
+     * @param e the thrown failure
+     */
+    public void testFailed(Test test, Throwable e, String out, String err, String log) {
+        super.testFailed(test);
+
+        writeTestProblems(test, e, "failure", out, err, log);
+    }
+
+    /**
+     * Utility method writing failed and in error test result in the report.
+     * @param test the test
+     * @param e the thrown error
+     * @param name type of failure ("error" or "failure")
+     */
+    private void writeTestProblems(Test test, Throwable e, String name, String out, String err, String log) {
+
+        long runTime = endTime - startTime;
+
+        Xpp3Dom testCase = createTestElement(test, runTime);
+
+        Xpp3Dom element = createElement(testCase, name);
+
+        String stackTrace = getStackTrace(test, e);
+
+        Throwable t = e;
+
+        if (t != null) {
+
+            String message = t.getMessage();
+
+            if (message != null) {
+                element.setAttribute("message", message);
+
+                element.setAttribute("type",
+                        (stackTrace.indexOf(":") > -1 ? stackTrace.substring(0,
+                                stackTrace.indexOf(":")) : stackTrace));
+            } else {
+                element.setAttribute("type", new StringTokenizer(stackTrace)
+                        .nextToken());
+            }
+        }
+
+        if (stackTrace != null) {
+            element.setValue(stackTrace);
+        }
+        
+        addOutputStreamElement( out, "system-out", testCase );
+
+        addOutputStreamElement( err, "system-err", testCase );
+        
+        addOutputStreamElement( log, "log-service", testCase );
+
+        results.add(testCase);
+    }
+
+    /**
+     * Generates the XML reports.
+     * @param test the test 
+     * @param tr the test result
+     * @param reportsDirectory the directory in which reports are created.
+     * @param bc the bundle context (to get installed bundles)
+     * @throws Exception when the XML report cannot be generated correctly
+     */
+    public void generateReport(Test test, TestResult tr, File reportsDirectory,
+            BundleContext bc) throws Exception {
+        long runTime = this.endTime - this.startTime;
+
+        Xpp3Dom testSuite = createTestSuiteElement(test, runTime);
+
+        showProperties(testSuite, bc);
+
+        testSuite.setAttribute("tests", String.valueOf(tr.runCount()));
+
+        testSuite.setAttribute("errors", String.valueOf(tr.errorCount()));
+
+        testSuite.setAttribute("failures", String.valueOf(tr.failureCount()));
+
+        for (Iterator i = results.iterator(); i.hasNext();) {
+            Xpp3Dom testcase = (Xpp3Dom) i.next();
+            testSuite.addChild(testcase);
+        }
+
+        File reportFile = new File(reportsDirectory, "TEST-"
+                + getReportName(test).replace(' ', '_') + ".xml");
+
+        File reportDir = reportFile.getParentFile();
+
+        reportDir.mkdirs();
+
+        PrintWriter writer = null;
+
+        try {
+            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
+                    new FileOutputStream(reportFile), "UTF-8")));
+
+            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + NL);
+
+            Xpp3DomWriter.write(new PrettyPrintXMLWriter(writer), testSuite);
+        } catch (UnsupportedEncodingException e) {
+            throw new Exception("Unable to use UTF-8 encoding", e);
+        } catch (FileNotFoundException e) {
+            throw new Exception("Unable to create file: " + e.getMessage(), e);
+        }
+
+        finally {
+            IOUtil.close(writer);
+        }
+    }
+
+    /**
+     * Creates a XML test case element.
+     * @param test the test
+     * @param runTime the elapsed time to execute the test.
+     * @return the XML element describing the given test.
+     */
+    private Xpp3Dom createTestElement(Test test, long runTime) {
+        Xpp3Dom testCase = new Xpp3Dom("testcase");
+        testCase.setAttribute("name", getReportName(test));
+        testCase.setAttribute("time", Long.toString(runTime) + " sec");
+        return testCase;
+    }
+
+    /**
+     * Creates a XML test suite element.
+     * @param test the test
+     * @param runTime the elapsed time to execute the test suite.
+     * @return the XML element describing the given test suite.
+     */
+    private Xpp3Dom createTestSuiteElement(Test test, long runTime) {
+        Xpp3Dom testCase = new Xpp3Dom("testsuite");
+        testCase.setAttribute("name", getReportName(test));
+        testCase.setAttribute("time", Long.toString(runTime) + " sec");
+        return testCase;
+    }
+
+    /**
+     * Computes report name.
+     * @param test the test
+     * @return the report name
+     */
+    private static String getReportName(Test test) {
+        String report = test.toString();
+
+        if (report.indexOf("(") > 0) {
+            report = report.substring(0, report.indexOf("("));
+        }
+        return report;
+    }
+
+    /**
+     * Creates an XML element
+     * @param element the parent element
+     * @param name the name of the element to create
+     * @return the resulting XML tree.
+     */
+    private Xpp3Dom createElement(Xpp3Dom element, String name) {
+        Xpp3Dom component = new Xpp3Dom(name);
+
+        element.addChild(component);
+
+        return component;
+    }
+
+    /**
+     * Adds system properties to the XML report.
+     * This method also adds installed bundles.
+     * @param testSuite the XML element.
+     * @param bc the bundle context
+     */
+    private void showProperties(Xpp3Dom testSuite, BundleContext bc) {
+        Xpp3Dom properties = createElement(testSuite, "properties");
+
+        Properties systemProperties = System.getProperties();
+
+        if (systemProperties != null) {
+            Enumeration propertyKeys = systemProperties.propertyNames();
+
+            while (propertyKeys.hasMoreElements()) {
+                String key = (String) propertyKeys.nextElement();
+
+                String value = systemProperties.getProperty(key);
+
+                if (value == null) {
+                    value = "null";
+                }
+
+                Xpp3Dom property = createElement(properties, "property");
+
+                property.setAttribute("name", key);
+
+                property.setAttribute("value", value);
+
+            }
+        }
+
+        Xpp3Dom buns = createElement(properties, "bundles");
+        Bundle[] bundles = bc.getBundles();
+        for (int i = 0; i < bundles.length; i++) {
+            String sn = bundles[i].getSymbolicName();
+            String state = "UNKNOWN";
+            switch (bundles[i].getState()) {
+                case Bundle.ACTIVE:
+                    state = "ACTIVE";
+                    break;
+                case Bundle.INSTALLED:
+                    state = "INSTALLED";
+                    break;
+                case Bundle.RESOLVED:
+                    state = "RESOLVED";
+                    break;
+                case Bundle.UNINSTALLED:
+                    state = "UNINSTALLED";
+                    break;
+            }
+            Xpp3Dom bundle = createElement(buns, "bundle");
+            bundle.setAttribute("symbolic-name", sn);
+            bundle.setAttribute("state", state);
+        }
+
+    }
+    
+    private void addOutputStreamElement(String stdOut, String name,
+            Xpp3Dom testCase) {
+        if (stdOut != null && stdOut.trim().length() > 0) {
+            createElement(testCase, name).setValue(stdOut);
+        }
+    }
+
+}
diff --git a/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/log/LogServiceImpl.java b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/log/LogServiceImpl.java
new file mode 100644
index 0000000..aaa6fe0
--- /dev/null
+++ b/ipojo/examples/junit4osgi/maven-junit4osgi-plugin/src/main/java/org/apache/felix/ipojo/junit4osgi/plugin/log/LogServiceImpl.java
@@ -0,0 +1,185 @@
+/*
+ * 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.felix.ipojo.junit4osgi.plugin.log;
+
+import org.apache.felix.ipojo.junit4osgi.plugin.StringOutputStream;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+/**
+ * An implementation of the log service to collect logged messages.
+ * This service implementation is also {@link BundleActivator} and is
+ * activated when the embedded OSGi platform starts.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class LogServiceImpl implements LogService, BundleActivator {
+    
+    /**
+     * Default output stream (not collected).
+     */
+    private StringOutputStream defaultStream;
+  
+    /**
+     * Collected output stream.
+     */
+    private StringOutputStream outputStream; 
+    
+    /**
+     * Creates the log service object.
+     */
+    public LogServiceImpl() {
+        defaultStream = new StringOutputStream();
+    }
+   
+    /**
+     * Enables the log messages collection.
+     */
+    public void enableOutputStream() {
+        outputStream = new StringOutputStream();
+    }
+    
+    /**
+     * Get collected log messages.
+     * @return the String containing the logged messages.
+     */
+    public String getLoggedMessages() {
+        return outputStream.toString();
+    }
+    
+    /**
+     * Re-initializes the collected message list.
+     */
+    public void reset() {
+        outputStream = null;
+    }
+
+    /**
+     * Logs a message.
+     * @param arg0 the log level
+     * @param arg1 the message
+     * @see org.osgi.service.log.LogService#log(int, java.lang.String)
+     */
+    public void log(int arg0, String arg1) {
+        write(computeLogMessage(arg0, arg1, null));
+    }
+
+    /**
+     * Logs a message with an attached exception.
+     * @param arg0 the log level
+     * @param arg1 the message
+     * @param arg2 the associated exception
+     * @see org.osgi.service.log.LogService#log(int, java.lang.String, java.lang.Throwable)
+     */
+    public void log(int arg0, String arg1, Throwable arg2) {
+        write(computeLogMessage(arg0, arg1, arg2));
+    }
+
+    /**
+     * Logs a message raised by the given service reference.
+     * @param arg0 the service reference
+     * @param arg1 the log level
+     * @param arg2 the message
+     * @see org.osgi.service.log.LogService#log(org.osgi.framework.ServiceReference, int, java.lang.String)
+     */
+    public void log(ServiceReference arg0, int arg1, String arg2) {
+        write(computeLogMessage(arg1, arg2, null));
+    }
+
+    /**
+     * Logs a message raised by the given service reference
+     * associated with an exception.
+     * @param arg0 the service reference
+     * @param arg1 the log level
+     * @param arg2 the message
+     * @param arg3 the exception
+     * @see org.osgi.service.log.LogService#log(org.osgi.framework.ServiceReference, int, java.lang.String)
+     */
+    public void log(ServiceReference arg0, int arg1, String arg2, Throwable arg3) {
+        write(computeLogMessage(arg1, arg2, arg3));
+    }
+    
+    /**
+     * Computes the string from the message.
+     * @param level the log level
+     * @param msg the message
+     * @param exception the exception (can be <code>null</code>
+     * @return the resulting String
+     */
+    private String computeLogMessage(int level, String msg, Throwable exception) {
+        String message = null;
+        switch (level) {
+            case LogService.LOG_DEBUG:
+                message = "[DEBUG] " + msg;
+                break;
+            case LogService.LOG_ERROR:
+                message = "[ERROR] " + msg;
+                break;
+            case LogService.LOG_INFO:
+                message = "[INFO] " + msg;
+                break;
+            case LogService.LOG_WARNING:
+                message = "[WARNING] " + msg;
+                break;
+        }
+        
+        if (exception != null) {
+            message = message + " : " + exception.getMessage() + "\n";
+        }
+        
+        return message;
+    }
+    
+    /**
+     * Writes the given message in the adequate output stream. 
+     * @param log the message
+     */
+    public void write(String log) {
+        if (outputStream != null) {
+            outputStream.write(log);
+        } else {
+            defaultStream.write(log);
+        }
+    }
+
+    /**
+     * Stars the log service implementation:
+     * Registers the service.
+     * @param bc the bundle context.
+     * @throws Exception should not happen.
+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+     */
+    public void start(BundleContext bc) throws Exception {
+        bc.registerService(LogService.class.getName(), this, null);
+    }
+
+    /**
+     * Stops the log service implementation.
+     * Does nothing.
+     * @param arg0 the bundle context
+     * @throws Exception should not happen.
+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+     */
+    public void stop(BundleContext arg0) throws Exception {
+        // Nothing to do.
+        
+    }
+
+}