# java.lang.ClassNotFoundException beim laden eines Plugins



## dzim (3. Jun 2009)

Hallo,

zum ersten mal seit einiger Zeit finde ich mal wieder ein paar Augenblicke, um mich um RCP zu kümmern.
Ich hatte vor einiger Zeit mal eine Anwendung gebaut, die einige Editoren u.ä. über das CNF lädt und öffnet.
Das System ist nicht gerade toll, aber es funktionierte soweit.

Hier ein kurzer Abriss:
* Hauptanwendung (mit CNF):
   - hat eine Extension, in der ein Baummodell angegeben werden kann (eine selbstgemachte Klasse mit Parent- und Children-Elementen, die PlatformObject erweitert)
   - hat einige Klassen, die es exportiert - u.a. die Klasse für die Extension, einen Stub für EditorInputs, ...
* Plugins:
   - erweitern die Klasse für das Baummodell, dieses wird dann im CNF angezeigt
   - haben mitunter eigene IEditorInput-Varianten, die meinen Stub überschreiben

Was geht bislang?
Das Baummenü wird angezeigt, die Editoren starten, solange der input direkt meine Stub-Klasse ist.
Sobald ich eigene Inputs schreibe, werden diese nicht mehr geladen und eine 

```
java.lang.ClassNotFoundException
```
fliegt mir um die Ohren.

Ich gehe mal davon aus, dass das laden des Baummenüs noch unabhängig des Bundle-Starts abläuft und die restlichen Klassen des Plugins erst geladen werden, wenn der Editor geladen wurde.

edit: Nein - scheint doch nicht so zu sein... Das hilft leider auch nicht. Wenn ich die Input-Klassen im Hauptprogramm ablege und von da starte findet er sie. Kann es sein, das der falsche Classloader verwendet wird... Ich müsste also den vom Bundle bekommen... argh!


----------



## dzim (3. Jun 2009)

Huch, ich war mal wieder zu schnell mit dem senden...

Ich wollte natürlich fragen, was für Erfahrungen ihr dort gemacht habt.
Irgendwie muss es doch möglich sein, beim öffnen von Editoren über 

```
iWorkbenchPage.openEditor(input, editorId, true, IWorkbenchPage.MATCH_ID);
```
nicht nur die editorenId zu übergeben, sondern auch einen Input... Und dieser muss doch auch aus dem Plugin, dass den Editor enthält zu laden sein... irgendwie!


----------



## Wildcard (3. Jun 2009)

Moment... ich bin nicht sicher das ich das verstehe... An welcher Stelle bekommst du eine ClassNotFoundException, wie hast du deinen Bundle Dependencies definiert und was hat das mit der zweiten Frage mit dem Input zu tun?


----------



## dzim (4. Jun 2009)

Hm... In meinem Kopf klingt immer alles so verständlich...

Ich versuch es noch mal:
Ich habe eine Anwendung, die einen Extension Point definiert. Ziel ist dabei eine Klasse zu extenden, die als eine Erweiterung von PlatformObject in meinem CNF angezeigt werden kann. Das klappt.
Zusätzlich wird bei einem Klick auf einen der Einträge, die durch das Modell im CNF erstellt werden, ein Editor geöffnet. Das klappt auch, solange ich als Editor-Input einen Input-Stub aus meiner Hauptanwendung nehme. Das Problem dabei wird nur sein, dass ich zum starten des Editors folgenden Quellcode zum laden des entsprechenden Inputs nutze:

```
Class<? extends TaskEditorInput> clazz = (Class<? extends TaskEditorInput>) Class
	.forName(task.getIEditorInputImpl());
input = clazz.getConstructor(Task.class).newInstance(task);
```
task ist dabei ein Objekt, das über den Extension-Point geladen wird - also ein Eintrag im CNF.
Dieses Stück quellcode wird in einer Action-Klasse definiert, die vom CNF genutzt wird. Also sucht der Classloader verständlicherweise nach der klasse im Kontext der Hauptanwendung mit dem CNF - nicht aber in dem Plugin, das dieses "task" definiert hat und die ID des zu öffnenden Editors und den Klassennamen des zu ladenen Inputs enthält.

War das etwas verständlicher?

(ich hoffe es...)


----------



## dzim (4. Jun 2009)

Ich denke ich hab das Problem jetzt zu einiger Zufriedenstellung gelöst - ohne das es in einen Hack ausgeartet ist:

Ich habe in dem Activator meiner Hauptanwendung eine runTaskModelExtension() Methode, die beim Aufruf des Konstruktors nach meinem Extension Point sucht und deren Modell in eine globale Baumstruktur lädt, welche CNF dann darstellt.

Ich habe in den Aktivator eine private Liste (mit getter) gelegt, die einfach die Namen der Contributors enthält, die die Extension besitzen.


```
private void runTaskModelExtension() {
		try {

			IConfigurationElement[] config = Platform.getExtensionRegistry()
					.getConfigurationElementsFor(PFS_EXTENSION_POINT_ID);
			for (IConfigurationElement e : config) {
				if (e.getName().equals(
						PFS_EXTENSION_POINT_BUNDLE_TO_LOAD_ELEMENT)) {

					contributors.add(e.getContributor().getName());

					Object o = e
							.createExecutableExtension(PFS_EXTENSION_POINT_BUNDLE_TO_LOAD_TASK_MODEL);
					if (o instanceof TaskModel) {
						pfsManagerModel.addRootTaskChild(((TaskModel) o)
								.getRoot());
					}
				}
			}
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
		}
	}
```
Der Name "BUNDLE_TO_LOAD*" ist schlecht gewählt, aber das nur am Rande...

Die Action, die von CNF aus gestartet wird und die Editoren u.s.w. lädt greift auf diese Liste zu und holt diese Bundles über Platform.getBundle(symbolicName) dort nutze ich die loadClass(className)-Methode

```
private void openEditor(IWorkbenchPage page, Task task)
			throws PartInitException {

		TaskEditorInput input = null;

		try {

			Class<? extends TaskEditorInput> clazz = null;

			if (task.getIEditorInputImpl() != null
					&& !task.getIEditorInputImpl().isEmpty()) {

				for (String contributor : PFSActivator.getDefault()
						.getContributors()) {

					Bundle bundle = Platform.getBundle(contributor);

					try {
						clazz = bundle.loadClass(task.getIEditorInputImpl());
					} catch (ClassNotFoundException e) {
						PFSActivator
								.getDefault()
								.getConsole()
								.getWarningMessagePrintStream()
								.println(
										contributor
												+ " bundle ("
												+ bundle
												+ ")\n\t>> does not contain the class '"
												+ task.getIEditorInputImpl()
												+ "'");
					}

					if (clazz != null) {
						System.out.println(contributor + " bundle (" + bundle
								+ ")\n\tclass '" + task.getIEditorInputImpl()
								+ "' loaded from this Bundle!");
						break;
					}
				}

				if (clazz == null)
					clazz = (Class<? extends TaskEditorInput>) Class
							.forName(task.getIEditorInputImpl());

				input = clazz.getConstructor(Task.class).newInstance(task);

			} else {
				input = new TaskEditorInput(task);
			}

		} catch (Exception e) {
			throw new PartInitException(
					"Couldn't load the IEditorInput implementation "
							+ task.getIEditorInputImpl() + "\n"
							+ e.getClass().getName() + ": "
							+ e.getLocalizedMessage(), e.getCause());
		}

		page.openEditor(input, task.getId(), true, IWorkbenchPage.MATCH_ID);
	}
```
Vieleicht nicht so schön, aber es funktioniert erst mal!


----------



## Wildcard (4. Jun 2009)

Ugh. Das ist hässlich. Dein Bundle sieht immer nur die Packages die es explizit importiert. Das ist gewollt, und eines der wichtigsten Features von OSGi. Willst du die Klasse *kennen*, musst du die beiden miteinander bekannt machen.
Warum sparst du dir das Reflection Zeug denn nicht einfach und nimmst statt einer Methode task.getIEditorInputImpl() einfach eine task.createIEditorInput und lässt die konkrete Implementierung das Objekt erzeugen?
Du kannst es auch über einen extension point, oder Adapter, oder sonstwas regeln, also warum willst du die Instanz unbedingt selbst erzeugen?


----------



## dzim (5. Jun 2009)

also die Idee mit task.createIEditorInput ist wirklich besser... Auf so was einfaches bin ich echt nicht gekommen!
Ich habe auch schon über einen Extension-Point nachgedacht, wusste aber noch nicht so recht, wie ich das mache...
Ich wüsste zwar, wie ich EditorID mit der Implementierenden Input-Klasse versehe - ich würde dann den Stub nur noch laden, wenn es keine Extension für Editor-Input gibt - aber funktioniert das dann noch mit Classloader?
Wird dann die Klasse aus dem jeweiligem Bundle geladen?
Dann muss aber meine Action zum laden der Editoren, Dailoge und Wizards die Extensions durchsuchen, oder?

Hm. Aber sö könnte ich über verschiedene ExtensionPoints die ganze sache auch etwas sauberer machen... Dann hab ich halt nicht nur den einen für das CNF sondern gleich 4 für CNF, Edior/Input-Kombi, Dialoge und Wizards...

edit:
Leider funktioniert das mit der createInput nicht... es scheint, das an der Stelle schon der Classloader der Hauptanwendung die Kontrolle über den task übernommen hat.
Es kommt wieder zu der ClassNotFoundException...


----------



## dzim (5. Jun 2009)

Nach dem das mit dem createInput nich geklappt hat, habe ich meinen ExtensionPoint erweitert und nun pro Editor eine Input-Klasse festlege, bekomme ich nun leider die Meldung, das mein Plugin mit dem Editor nicht in der Lage war die entsprechende Input-Klasse zu laden



> org.eclipse.core.runtime.CoreException: Plug-in "com.ipoque.p2p.tracker.rcp.dbmanager" was unable to instantiate class "com.ipoque.p2p.tracker.rcp.dbmanager.ui.editor.input.LocationListTaskEditorInput".
> !STACK 0
> java.lang.InstantiationException: com.ipoque.p2p.tracker.rcp.dbmanager.ui.editor.input.LocationListTaskEditorInput



Das passiert beim Aufruf der IConfigurationElement#createExecutableExtension()-Methode, mit denen ich den Extension-Point nach diesem Mapping absuche.

edit:
Ich seh das Problem gerade: createExecutableExtension kann nur leere Konstruktoren - ich habe aber nur einen, dem das task-Objekt wieder übergeben wird...

edit2:
Ok, für Editoren hab ich jetzt chic gemacht und über die Extension gelöst. Für Wizards wird es auch gehen. Dialoge allerdings werde ich vorerst weiter auf die hässliche Weise laden müssen, da diese (TitleAreaDialogs) eine ParentShell benötigen. Dort würde es vielleicht nur gehen, wenn ich einen Argumentenfreien Konstruktor erstelle, der sich die ParentShell aus der aktiven WorkbenchSite holt... aber das versuche ich erst später!


----------



## Wildcard (5. Jun 2009)

Ich müsste zwar lügen wenn ich behaupten würde deinen Fehlerbeschreibungen immer ganz folgen zu können, aber naja, du scheinst ja einen Schritt weiter zu sein.


> Ok, für Editoren hab ich jetzt chic gemacht und über die Extension gelöst. Für Wizards wird es auch gehen. Dialoge allerdings werde ich vorerst weiter auf die hässliche Weise laden müssen, da diese (TitleAreaDialogs) eine ParentShell benötigen. Dort würde es vielleicht nur gehen, wenn ich einen Argumentenfreien Konstruktor erstelle, der sich die ParentShell aus der aktiven WorkbenchSite holt... aber das versuche ich erst später!


Du siehst doch überall um dich rum wie das in Eclipse getan wurde.
Ein EditorPart bspw erzeugt Widgets und hat dennoch einen Default Constructor. Wie macht man das? Einfach eine createPartControl(Composite parent) ins Interface aufnehmen (oder in deinem Fall eben eine shell)


----------



## dzim (8. Jun 2009)

Ja. Hast ja recht. *schäm*

Im Prinzip hab ich das für mein eigenes Modell ja auch so gelöst (Das hatte ursprünglich auch keinen Default Constructor). Ich werd da noch ein bisschen dran herumschrauben, bis ich vielleicht eine mich zufriedenstellende Lösung gefunden habe.

BTW: Tut mir leid, das meine Fehlerbeschreibungen immer so wirr sind... Manchmal sollte ich vielleicht erst denken, dann tippen ;-)


----------



## dzim (8. Jun 2009)

Ich habe die ganze Problematik jetzt anders gelöst:
Nachdem ich mir die Doc zur IConfigurationElement#createExecutableExtension()-Methode noch einmal angeschaut habe, ist mir eine denkbar einfache Lösung eingefallen: Eine Factory! Ich habe die Extension so angepasst, dass man eine Factory (das Interface dieser habe ich schnell gebaut) angeben muss, die die Klassen laden kann und fertig. Klappt!

Danke für die konstruktive Kritik! Das bringt mich doch meistens noch auf den einen oder anderen Gedanken...


----------

