# Eine Art PlugIn-System



## M_Kay (9. April 2008)

Hi,

ich habe vor eine Art PlugIn-System zu schreiben.
Ich habe mich dabei an folgende Seite gehalten: http://www.javangelist.de/space/Plugin

In der META-INF/services stehen nun die Klassen drin, die geladen/unterstuetzt werden sollen.
Auf der Seite ist auch beschrieben, wie ich mittels Iterator zB durch alle Klassen gehen und Methoden aufrufen kann.
Komisch finde ich, dass in dem Beispiel eine nicht statische Funktion direkt über die Klasse (oder doch nicht?) aufgerufen wird.
Wie kann ich jetzt Objekte der geladenenen Klassen erstellen, oder ist das nicht moeglich?

Gruss
M_Kay

EDIT: Ich sehe gerade, dass es die schicke funktion Class.newInstance() gibt. Mal schauen


----------



## takidoso (10. April 2008)

Hallo M_Kay,
ist je eine interssante Seite!, Früher habe ich mir so eine Art Klassenfinder aus alten Sourcen, die von "Weiß der Henker" kamen zusammen gebastelt.

Mal 'ne Blöde Frage: ist die Klasse Service in Java 6 eigetnlich immernoch im Paket *sun.misc* oder wurde da ein algemeineres Paket nun ersonnen? (habe da die Diskusion der Bug-Seite angesehen, da steht drauf fixed, aber nicht welches Paket)
Benötigt man eigetnlich, wenn man außerhalb des Klassenpfades Bibliotheken als Plugins anziehen will auch noch eine Datei in META-INF/service oder kann man sich die dann schenken?

mit neugierigen Grüßen

Takidoso

PS: hier ein Link der ähnliches diskutiert http://www.tutorials.de/forum/java/299124-plugins-f-r-eigenes-programm.html


----------



## Thomas Darimont (10. April 2008)

Hallo,

ich glaube du meinst hier den ServiceLoader...
schau mal hier:
http://www.tutorials.de/forum/java/...-anwenden-benutzen-bei-folgendem-problem.html

Gruß Tom


----------



## M_Kay (10. April 2008)

Argh, ich habe die Struktur meines (noch recht winzigen) Programms überarbeitet und schon will der keine Services mehr laden :X


```
public static ArrayList<ProtocolDownload> loadDownloadProtocols() {
        ArrayList<ProtocolDownload> pd = new ArrayList<ProtocolDownload>();
        
        Iterator iterator;
        iterator = Service.providers(ProtocolDownload.class);
        
        while(iterator.hasNext()) {
            ProtocolDownload dl = (ProtocolDownload) iterator.next();
            pd.add(dl);
            
            //TODO entf
            System.out.print(dl.getClass().getName());
        }
        
        return pd;
    }
```
Beim ersten Befehl in der While-Schleife bricht er mit folgendem Befehl ab:

```
Exception in thread "main" sun.misc.ServiceConfigurationError: mload.model.download.ProtocolDownload: Provider mload.model.download.protocols.HttpDownload could not be instantiated: java.lang.InstantiationException: mload.model.download.protocols.HttpDownload
    at sun.misc.Service.fail(Service.java:120)
    at sun.misc.Service.access$200(Service.java:111)
    at sun.misc.Service$LazyIterator.next(Service.java:276)
    at mload.model.Init.loadDownloadProtocols(Init.java:28)
    at mload.controller.MLController.load(MLController.java:45)
    at mload.MLoad.main(MLoad.java:24)
Caused by: java.lang.InstantiationException: mload.model.download.protocols.HttpDownload
    at java.lang.Class.newInstance0(Class.java:340)
    at java.lang.Class.newInstance(Class.java:308)
    at sun.misc.Service$LazyIterator.next(Service.java:271)
    ... 3 more
```
Er findet scheinbar die gewünschte Klasse, doch kann ich mit der Fehlermeldung nichts anfangen (vorher hats doch funktioniert ) :suspekt:

Kann mir jemand helfen?


----------



## M_Kay (10. April 2008)

Ok, es lag scheinbar daran, dass die Klasse, die geladen werden sollte, keinen Konstruktor ohne Parameter hatte (komisch, vorher hatte es funktioniert).

Gibt es eine Möglichkeit eben das zu erreichen? Also dass es mit Konstruktor funktionert, die Parameter enthalten?


----------



## zeja (10. April 2008)

Direkt geht das offenbar nicht mit sun.misc.Service ist aber ja recht leicht selber zu schreiben...

Lies halt die Datei die die Pluginnamen definieren selber per ClassLoader aus, lade die Klassen und erstelle entsprechende Instanzen mit den gewünschten Parametern.


----------



## M_Kay (10. April 2008)

Hi,

könntest du das evtl mit einem kleinen Beispiel erläutern? 

Bisher habe ich halt die Klassen über die Service Methode geladen und später dann mit Klasse.getConstructor und Klasse.newInstance(param) die Instance mit Parametern erstellt.
Aber ich musste wie gesagt einen Konstruktor ohne Parameter erstellen, auch wenn dieser leer ist, da er sonst in der o.g. Schleife einen Fehler ausgibt.

Gruss
M_Kay


----------



## zeja (10. April 2008)

Soo... nun:

Das Interface:

```
package de.tutorials;

public interface IPlugin {
	
	public void print();

}
```


Eine Implementierung:

```
package de.tutorials.plugin;

import de.tutorials.IPlugin;

public class Plugin implements IPlugin {
	
	private int i;
	private String strg;

	public Plugin(int i, String strg){
		this.i = i;
		this.strg = strg;
	}

	@Override
	public void print() {
		System.out.println(i + " " + strg);
	}

}
```

Und nun das Laden:

```
package de.tutorials;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;

public class PluginLoader {

	public static void main(String[] args) throws Exception {
		URLClassLoader loader = new URLClassLoader(new URL[] { new File(
				"Plugin.jar").toURI().toURL() });
		final Enumeration<URL> findResources = loader
				.findResources("META-INF/services/plugins");
		while (findResources.hasMoreElements()) {
			final URL url = findResources.nextElement();
			loadSpecifiedClasses(loader, url);
		}
                reader.close();
	}

	private static void loadSpecifiedClasses(URLClassLoader loader,
			final URL url) throws IOException, ClassNotFoundException,
			NoSuchMethodException, InstantiationException,
			IllegalAccessException, InvocationTargetException {
		final InputStream stream = url.openStream();
		BufferedReader reader = new BufferedReader(
				new InputStreamReader(stream));
		String line = null;
		while ((line = reader.readLine()) != null) {
			loadClass(loader, line);
		}
	}

	private static void loadClass(URLClassLoader loader, String className)
			throws ClassNotFoundException, NoSuchMethodException,
			InstantiationException, IllegalAccessException,
			InvocationTargetException {
		final Class<?> clss = loader.loadClass(className);
		final Constructor<?> construct = clss.getConstructor(int.class,
				String.class);
		final IPlugin newInstance = (IPlugin) construct.newInstance(1, "Hello");
		newInstance.print();
	}

}
```


----------



## M_Kay (10. April 2008)

Argh, ich bekomms nicht hin.
Die Klassen liegen ja in meinem Fall im Projekt selbst, nicht in einem externen jar.

```
URL thisurl = new File("./").toURI().toURL();
            URLClassLoader loader = new URLClassLoader(new URL[] {thisurl});
            
            final URL findResources = loader.findResource("/META-INF/service/mload.model.download.ProtocolDownload");
```
Das möchte irgendwie nicht funktionieren :suspekt:
findResources ist bei mir aber immer null.

Kann mir jemand helfen?


----------



## zeja (10. April 2008)

Dann mach nen eigenes Jar von. Das macht ja keinen Sinn das Plugins in dem Hauptprojekt mit drinliegen...

Danach mußt du dich vergewissern dass in deinem Jar unter META-INF/services auch tatsächlich die gesucht Datei liegt. Diese heißt immer gleich. IN der Datei stehen dann die Namen der zu ladenden Plugins.


----------



## M_Kay (10. April 2008)

Hehe, doch, die "PlugIns" liegen im Hauptprogramm-Jar 
Die Plugins sind also fest implementiert. Damit möchte ich erreichen, dass ich ohne größeren Aufwand die gleiche Funktionalität für eine andere Art von Objekten erstellen und das Programm gleich damit umgehen kann, ohne, dass ich im Source noch wild rumfummeln muss 

Folgendermaßen funktioniert es:

```
public static ArrayList<Class<?>> loadDownloadProtocols() {
        ArrayList<Class<?>> pd = new ArrayList<Class<?>>();
        
        try {
            URL url = ClassLoader.getSystemClassLoader().getResource("META-INF/services/mload.model.download.ProtocolDownload");
            URLClassLoader loader = new URLClassLoader(new URL[] {url});
    
            final InputStream stream = url.openStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
            String line = null;
    
            while ((line = reader.readLine()) != null) {
    
                Class<?> cl = loader.loadClass(line);
                pd.add(cl);
    
            }
        } catch (Exception exc) {}
        

        return pd;
    }
```
Die Funktion liefert mir die entsprechenden verfügbaren Klassen, die in der o.g. Datei aufgelistet sind.
Das ließe sich zwar alles mit Service.providers einfacher lösen, aber der scheint automatisch eine Instanz über einen parameterlosen Konstruktor zu erzeugen.

Ich bin jetzt am überlegen, ob ich die Service.providers in Kombination mit einem Konstruktor-Ersatz nehme, da es dort doch recht einfach geht.
Die Methode mit dem Laden über einen extra ClassLoader bringt leider ein paar Probleme mit sich. Bspw muss ich die Klassen als Class-Objekt zwischenspeichern und kann nicht direkt angeben, dass es sich dabei um eine von Klasse xxx abgeleitete Klasse handelt. Oder doch?


----------



## zeja (10. April 2008)

Nun denk mal drüber nach was die Service Klasse wohl intern macht? Die lädt auch die Klassen, erstellt Instanzen und castet diese dann. Du würdest dann über den parameterlosen Konstruktor Klassen erstellen lassen welche du nie verwendest... das ist doch viel weniger schön.

Schreib dir doch eine eigene Klasse, kapsel das ganze genauso wie in der Service Klasse und dann ist gut.

Mir ist allerdings nicht ganz klar warum du dann diesen Aufwand treibst... Den URLClassloader kannst du auf jeden Fall weglassen und den SystemClassLoader weiter verwenden.

Aber genausogut könntest du dann doch auch im Code deine Klassen in einer Liste sammeln und sie dann so instanziieren. Dann fällt das mit dem ClassLoader schonmal ganz weg (Class.forName() würde reichen).


----------



## M_Kay (11. April 2008)

Durch eine andere Sache war ich nun gezwungen doch einen parameterlosen Konstruktor einzubauen. Deshalb bin ich jetzt bei der ServiceLoader-Methode geblieben 

Gruss
M_Kay


----------



## takidoso (11. April 2008)

Ich denke der Sinn der parameterlosen Konstruktoren (Default Constructors) ist, dass ein allgemeingültiger Lader nicht wirklich riechen kann welche Parameter ein Konstructor benötigt. Wenn doch, dann vermutlich mit imensen Aufwand. Es ist IMHO der gleiche Gedanke wie mit den Beans.

Was ich schade an dieser mittlerweile Standardlösung finde ist, dass man da noch eine extra file benötigt um die Klassen anzugeben. Schöner fände ich die Lösung, dass man ein nacktes JAR einfach in ein definiertes Verzeichnis wirft, welches dann nach allen den Klassen durchsucht wird welche die "Basisklasse" oder Interface implementieren.
Ok es kostet vielleicht ein wenig mehr Performance, aber praktischer wäre es doch, oder?


----------



## zeja (11. April 2008)

Kostest mehr Performance, erlaubt weniger Kontrolle aber ist irgendwie einfacher ja. Und eigentlich in ner halben Stunde selber zu schreiben....

Genauso kann man sich nen eigenen ServiceLoader schreiben dem man eine Factory Klasse übergibt die die Instanziierung von Klassen übernimmt die Parameter benötigen.


----------



## Thomas Darimont (12. April 2008)

Hallo,

hier mal just 4 fun ein kleines Beispiel für einen Plugin Mechanismus der den ServiceLoader verwendet. Hier sieht man schön wie einfach man mit dem ServiceLoader eigene Erweiterungsmechanismen aufbauen kann ohne sich mit ClassLoader spielereien herumschlagen zu müssen.

Wir haben 3 Module:
Unsere Platform:             de.tutorials.platform.training.jar
Unsere Anwendung:            de.tutorials.platform.application.helloworld.jar
Erwieterungen zur Anwendung: de.tutorials.platform.application.helloworld.extensions.jar

Wir haben folgende Strutkur:
/deploy/de.tutorials.platform.training.jar
/deploy/applications/de.tutorials.platform.application.helloworld.jar
/deploy/extensions/de.tutorials.platform.application.helloworld.extensions.jar

Unsere Platform bietet die Möglichkeit Anwendungsfunktionalität mit Extensions (Erweiterungen) an ExtensionPoints (Erweiterungspunkten) über konkrete Contributions(Beiträge) zu erweitern. Ähnlichkeiten mit dem Eclipse Plugin-Mechanismus 
sind natürlich nur rein zufällig ;-)

Die Plugin Systeme von Eclipse und Netbeans sind natürlich sehr viel umfangreicher als der hier vorgestellte Ansatz.

Modul: de.tutorials.platform.training.jar

```
/**
 * 
 */
package de.tutorials;

/**
 * @author Thomas.Darimont
 * 
 */
public interface IApplication {
    void start();
}
```


```
/**
 * 
 */
package de.tutorials;

/**
 * @author Thomas.Darimont
 *
 */
public interface IPlatform {
    void launch();
    IContribution<IApplication>[] getContributionsFor(IApplication application, String target);
}
```


```
/**
 * 
 */
package de.tutorials;

/**
 * @author Thomas.Darimont
 *
 */
public interface IContribution<TApplication extends IApplication> {
    void contribute(TApplication application);
}
```



```
/**
 * 
 */
package de.tutorials;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Thomas.Darimont
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.TYPE})
public @interface Extension {
    String target();
}
```


```
/**
 * 
 */
package de.tutorials.internal;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import de.tutorials.Extension;
import de.tutorials.IApplication;
import de.tutorials.IContribution;
import de.tutorials.IPlatform;

/**
 * @author Thomas.Darimont
 * 
 */
public class Platform implements IPlatform {

	ServiceLoader<IApplication> applicationServiceLoader;
	ServiceLoader<IContribution<IApplication>> contributionServiceLoader;

	Map<String, IApplication> applicationNameToApplicationMap = new HashMap<String, IApplication>();
	Map<String, IContribution<IApplication>> contributionNameToContributionMap = new HashMap<String, IContribution<IApplication>>();

	public final static IPlatform INSTANCE = new Platform();
	private boolean launched;

	private Platform(){}
	
	@Override
	public void launch() {
		if (!launched) {
			System.out.println("Launching");
			init();
			loadApplications();
			loadExtensions();
			startApplications();
			launched = true;
		}
	}

	@SuppressWarnings("unchecked")
	private void init() {
		this.applicationServiceLoader = ServiceLoader.load(IApplication.class);
		this.contributionServiceLoader = (ServiceLoader<IContribution<IApplication>>) (Object) ServiceLoader
				.load(IContribution.class);
	}

	private void loadApplications() {
		for (IApplication application : applicationServiceLoader) {
			register(application);
		}
	}

	private void loadExtensions() {
		for (IContribution<IApplication> contribution : contributionServiceLoader) {
			register(contribution);
		}
	}

	private void register(IApplication application) {
		Class<?> applicationInterface = getSpecificApplicationInterface(application);

		System.out.printf("Register Application: %s from %s\n",
				applicationInterface.getName(), applicationInterface
						.getProtectionDomain().getCodeSource().getLocation());

		applicationNameToApplicationMap.put(applicationInterface.getName(),
				application);
	}

	/**
	 * @param application
	 * @return
	 */
	private Class<?> getSpecificApplicationInterface(IApplication application) {
		Class<?> applicationInterface = null;
		for (Class<?> iface : application.getClass().getInterfaces()) {
			for (Class<?> parentIterface : iface.getInterfaces()) {
				if (parentIterface.equals(IApplication.class)) {
					applicationInterface = iface;
					break;
				}
			}
		}
		return applicationInterface;
	}

	@SuppressWarnings("unchecked")
	private void register(IContribution<IApplication> contribution) {
		Class<? extends IContribution<IApplication>> contributionClazz = (Class<? extends IContribution<IApplication>>) (Object) contribution
				.getClass();
		String target = getExtensionTargetOf(contributionClazz);

		Class<IApplication> contributionTargetApplication = getTargetApplicationOf(contributionClazz);

		if (null != contributionTargetApplication) {
			System.out
					.printf(
							"Register Contribution: %s for target: %s application: %s from: %s\n",
							contribution.getClass().getName(), target,
							contributionTargetApplication.getName(),
							contributionClazz.getProtectionDomain()
									.getCodeSource().getLocation());

			contributionNameToContributionMap.put(contribution.getClass()
					.getName(), contribution);
		}
	}

	/**
	 * @param contributionClazz
	 * @return
	 */
	private String getExtensionTargetOf(
			Class<? extends IContribution<IApplication>> contributionClazz) {
		Extension extension = contributionClazz.getAnnotation(Extension.class);
		return extension.target();
	}

	/**
	 * @param contributionClazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Class<IApplication> getTargetApplicationOf(
			Class<? extends IContribution<IApplication>> contributionClazz) {
		Class<IApplication> contributionTargetApplication = null;
		try {

			for (Method method : contributionClazz.getDeclaredMethods()) {
				Class[] parameterTypes = method.getParameterTypes();
				if (!method.isBridge()
						&& parameterTypes.length == 1
						&& IApplication.class
								.isAssignableFrom(parameterTypes[0])) {
					// found ContributionTarget
					contributionTargetApplication = parameterTypes[0];
					break;
				}
			}
		} catch (Exception exception) {
			exception.printStackTrace();
		}
		return contributionTargetApplication;
	}

	private void startApplications() {
		// todo figure out application start order
		for (String applicationName : applicationNameToApplicationMap.keySet()) {
			System.out.println("Starting application: " + applicationName);
			applicationNameToApplicationMap.get(applicationName).start();
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public IContribution<IApplication>[] getContributionsFor(
			IApplication application, String target) {

		Class<?> applicationInterface = getSpecificApplicationInterface(application);
		List<IContribution<IApplication>> applicationSpecificContributionsForGivenTarget = new ArrayList<IContribution<IApplication>>();

		for (String contributionName : contributionNameToContributionMap
				.keySet()) {
			IContribution<IApplication> contribution = contributionNameToContributionMap
					.get(contributionName);

			Class<IApplication> contributionTargetApplicationType = getTargetApplicationOf((Class<? extends IContribution<IApplication>>) (Object) contribution
					.getClass());

			if (contributionTargetApplicationType.equals(applicationInterface)) {

				if (null == target || target.length() == 0
						|| target.equals(target)) {
					applicationSpecificContributionsForGivenTarget
							.add(contribution);
				}
			}
		}

		return applicationSpecificContributionsForGivenTarget
				.toArray(new IContribution[0]);
	}
}
```


```
/**
 * 
 */
package de.tutorials;

import de.tutorials.internal.Platform;

/**
 * @author Thomas.Darimont
 * 
 */
public class ExtensiblePlatformExample {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IPlatform platform = Platform.INSTANCE;
        platform.launch();
    }
}
```

Modul:de.tutorials.platform.application.helloworld.jar


```
/**
 * 
 */
package de.tutorials;

/**
 * @author Thomas.Darimont
 *
 */
public interface IExtensionPoints{
    public final static String MESSAGES = "de.tutorials.IExtensionPoints#MESSAGES";
}
```


```
/**
 * 
 */
package de.tutorials;

import java.util.List;

/**
 * @author Thomas.Darimont
 *
 */
public interface IHelloWorldApplication extends IApplication{
    public List<String> getMessages();
}
```


```
/**
 * 
 */
package de.tutorials.internal;

import java.util.ArrayList;
import java.util.List;

import de.tutorials.IApplication;
import de.tutorials.IContribution;
import de.tutorials.IExtensionPoints;
import de.tutorials.IHelloWorldApplication;

/**
 * @author Thomas.Darimont
 * 
 */
public class HelloWorldApplication implements IHelloWorldApplication {

    private List<String> messages = new ArrayList<String>();

    private void init() {
        for (IContribution<IApplication> messageContribution :  Platform.INSTANCE.getContributionsFor(this,IExtensionPoints.MESSAGES)) {
             messageContribution.contribute(this);
        }
    }

    @Override
    public void start() {
        init();
        for (String message : messages) {
            System.out.println(message);
        }
    }

    public List<String> getMessages() {
        return messages;
    }
}
```


```
/**
 * 
 */
package de.tutorials.contribution.internal;

import de.tutorials.Extension;
import de.tutorials.IContribution;
import de.tutorials.IExtensionPoints;
import de.tutorials.IHelloWorldApplication;

/**
 * @author Thomas.Darimont
 *
 */
@Extension(target = IExtensionPoints.MESSAGES)
public class AnotherHelloWorldMessagesContribution implements 

IContribution<IHelloWorldApplication>{
    @Override
    public void contribute(IHelloWorldApplication application) {
        application.getMessages().add("Huhu");
        application.getMessages().add("Moin");
        application.getMessages().add("Tach");
    }
}
```


```
/**
 * 
 */
package de.tutorials.contribution.internal;

import de.tutorials.Extension;
import de.tutorials.IContribution;
import de.tutorials.IExtensionPoints;
import de.tutorials.IHelloWorldApplication;

/**
 * @author Thomas.Darimont
 * 
 */
@Extension(target = IExtensionPoints.MESSAGES)
public class HelloWorldMessagesContribution implements 

IContribution<IHelloWorldApplication> {
    @Override
    public void contribute(IHelloWorldApplication application) {
        application.getMessages().add("Hey");
        application.getMessages().add("Hello");
        application.getMessages().add("Hi");
    }
}
```

Unter META-INF/services liegt nun die Datei de.tutorials.IApplication

```
de.tutorials.internal.HelloWorldApplication
```

Unter META-INF/services liegt nun die Datei de.tutorials.IContribution

```
de.tutorials.contribution.internal.HelloWorldMessagesContribution
de.tutorials.contribution.internal.AnotherHelloWorldMessagesContribution
```

In diesem Fall erweitert sich die Anwendung per default selbst mit zwei Contributions.

Modul: de.tutorials.platform.application.helloworld.extensions.jar

```
/**
 * 
 */
package de.tutorials.contribution.internal;

import de.tutorials.Extension;
import de.tutorials.IContribution;
import de.tutorials.IExtensionPoints;
import de.tutorials.IHelloWorldApplication;

/**
 * @author Thomas.Darimont
 * 
 */
@Extension(target = IExtensionPoints.MESSAGES)
public class YetAnotherHelloWorldMessagesContribution implements 

IContribution<IHelloWorldApplication> {
    @Override
    public void contribute(IHelloWorldApplication application) {
        application.getMessages().add("Foo");
        application.getMessages().add("Bar");
        application.getMessages().add("Buu");
    }
}
```

Unter META-INF/services liegt nun die Datei de.tutorials.IContribution

```
de.tutorials.contribution.internal.YetAnotherHelloWorldMessagesContribution
```

Nun kann man via:

```
java  
-cp 
deploy/de.tutorials.platform.training.jar;deploy/applications/*;deploy/extensions/* 
de.tutorials.ExtensiblePlatformExample
```

Die Platform mit den darin befindlichen Anwendungen und den entsprechenden Plugins 

starten. Seit Java 6 kann man mit -cp xxx/* alle Jars die im Verzeichnis xxx liegen
automatisch in den classpath aufnehmen ohne sie einzeln auflisten zu müssen.

Ausgabe:

```
D:\eclipse\workspaces\workspace-3.4M5\de.tutorials.platform.training>java  -cp deploy/de.tutorials.platform.training.jar;deploy/applications/*;deploy/extensions/* de.tutorials.ExtensiblePlatformExample 
Launching
Register Application: de.tutorials.IHelloWorldApplication from file:/D:/eclipse/workspaces/workspace-3.4M5/de.tutorials.platform.training/deploy/applications/de.tutorials.platform.application.helloworld.jar
Register Contribution: de.tutorials.contribution.internal.HelloWorldMessagesContribution for target: MESSAGES application: de.tutorials.IHelloWorldApplication from: file:/D:/eclipse/workspaces/workspace-3.4M5/de.tutorials.platform.training/deploy/applications/de.tutorials.platform.application.helloworld.jar
Register Contribution:de.tutorials.contribution.internal.AnotherHelloWorldMessagesContribution for target:MESSAGES application:de.tutorials.IHelloWorldApplication from:file:/D:/eclipse/workspaces/workspace-3.4M5/de.tutorials.platform.training/deploy/applications/de.tutorials.platform.application.helloworld.jar
Register Contribution:de.tutorials.contribution.internal.YetAnotherHelloWorldMessagesContribution for target: MESSAGES application: de.tutorials.IHelloWorldApplication from: file:/D:/eclipse/workspaces/workspace-3.4M5/de.tutorials.platform.training/deploy/extensions/de.tutorials.platform.application.helloworld.extensions.jar
Starting application: de.tutorials.IHelloWorldApplication
Hey
Hello
Hi
Foo
Bar
Buu
Huhu
Moin
Tach
```

Gruß Tom


----------

