Skip to content

Commit

Permalink
klighd: added startup hook extension point/interface for service loader.
Browse files Browse the repository at this point in the history
This extension point allows any projects wanting to extend any KLighD
behavior for different run configurations to only need to register this
extension once via Eclipse extension and via Java ServiceLoader in
META-INF/services and programatically register everything needed in the
implementation of this extension with the KLighD API for registering
extensions programmatically.
  • Loading branch information
NiklasRentzCAU committed Aug 18, 2020
1 parent 5ca065c commit c32530a
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 2 deletions.
1 change: 1 addition & 0 deletions plugins/de.cau.cs.kieler.klighd/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<extension-point id="extensions" name="Compound extension point for all kinds of extensions" schema="schema/extensions.exsd"/>
<extension-point id="diagramSyntheses" name="Diagram syntheses registering point" schema="schema/diagramSyntheses.exsd"/>
<extension-point id="de.cau.cs.kieler.klighd.preservedProperties" name="Preserved Properties" schema="schema/preservedProperties.exsd"/>
<extension-point id="klighdStartupHook" name="Startup Hook for KLighD" schema="schema/klighdStartupHook.exsd"/>

This comment has been minimized.

Copy link
@sailingKieler

sailingKieler Aug 19, 2020

Member

Why these additional extension points?
klighdStartupHooks could easily be registered via the existing extensions point.

This comment has been minimized.

Copy link
@NiklasRentzCAU

NiklasRentzCAU Aug 19, 2020

Author Member

True, the preservedProperties extension point could as well be part of the existing extensions point. I saw the startupHooks as something independent of that as it does not extend some specific behavior of KLighD, but can be rather used for arbitrary extensions and setup of the progammatic KLighD API. Also, this needs to be handled seperately (see my comment on your other comment).
Besides, why is the diagramSyntheses extension point independent of the extensions point as well? Couldn't that also be part of the default extensions point?

This comment has been minimized.

Copy link
@sailingKieler

sailingKieler Aug 19, 2020

Member

Besides, why is the diagramSyntheses extension point independent of the extensions point as well? Couldn't that also be part of the default extensions point?

It could, was separated long time ago to simplify further things that never got implemented (and that most like would have never worked out :-/). For API compatibility I would not touch that anymore, but new things should be integrated into the extensions point. Dozens of Eclipse extension points don't make the tool better...

This comment has been minimized.

Copy link
@sailingKieler

sailingKieler Aug 19, 2020

Member

I would not touch that anymore, of course.

This comment has been minimized.

Copy link
@NiklasRentzCAU

NiklasRentzCAU Aug 20, 2020

Author Member

The preservedProperties extension point is now merged into the extensions point. I guess I will leave the startup hook one on its own, as i needs the instance and will then be executed after all other extension points have already been registered the usual way.

This comment has been minimized.

Copy link
@sailingKieler

sailingKieler Aug 20, 2020

Member

I would appreciate, if could merge it, too. This way you have to traverse the list of extensions twice and check for expected extension name, but that additional effort can be neglected for sure. Having the API in order is definitely worth the effort :-)

This comment has been minimized.

Copy link
@sailingKieler

sailingKieler Aug 20, 2020

Member

And thanks for merging the other one, of course!

This comment has been minimized.

Copy link
@NiklasRentzCAU

NiklasRentzCAU Aug 20, 2020

Author Member

This would require moving the extension point loading from the constructor to the static part after initializing the 'instance' variable. Do you know if that could break any behavior as the instance may not have all fields initialized yet after the constructor? I don't think so, as no other client code may intervene before the static block is completed anyway.

This comment has been minimized.

Copy link
@NiklasRentzCAU

NiklasRentzCAU Aug 20, 2020

Author Member

Moved it in the keith branch, so we now again only have the main 'extension' and the diagram syntheses as extensions. The loading of the extensions is now entirely in the static block after creating the instance to avoid problems with that.
@sailingKieler if you have anything else for this PR, please notify @soerendomroes now, as I will be unavailable for the next two weeks.

<extension
point="org.eclipse.core.runtime.preferences">
<initializer
Expand Down
127 changes: 127 additions & 0 deletions plugins/de.cau.cs.kieler.klighd/schema/klighdStartupHook.exsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Schema file written by PDE -->
<schema targetNamespace="de.cau.cs.kieler.klighd" xmlns="http://www.w3.org/2001/XMLSchema">
<annotation>
<appinfo>
<meta.schema plugin="de.cau.cs.kieler.klighd" id="klighdStartupHook" name="Startup Hook extension point"/>
</appinfo>
<documentation>
This extension point is to be used to programmatically register KLighD extensions during startup.
</documentation>
</annotation>

<element name="extension">
<annotation>
<appinfo>
<meta.element />
</appinfo>
</annotation>
<complexType>
<sequence minOccurs="1" maxOccurs="unbounded">
<element ref="startupHook"/>
</sequence>
<attribute name="point" type="string" use="required">
<annotation>
<documentation>

</documentation>
</annotation>
</attribute>
<attribute name="id" type="string">
<annotation>
<documentation>

</documentation>
</annotation>
</attribute>
<attribute name="name" type="string">
<annotation>
<documentation>

</documentation>
<appinfo>
<meta.attribute translatable="true"/>
</appinfo>
</annotation>
</attribute>
</complexType>
</element>

<element name="startupHook">
<complexType>
<attribute name="id" type="string" use="required">
<annotation>
<documentation>

</documentation>
</annotation>
</attribute>
<attribute name="class" type="string" use="required">
<annotation>
<documentation>
The implementation of this hook. May use public KLighD API to register other extensions in KLighD programmatically.
</documentation>
<appinfo>
<meta.attribute kind="java" basedOn="de.cau.cs.kieler.klighd.IKlighdStartupHook"/>
</appinfo>
</annotation>
</attribute>
</complexType>
</element>

<annotation>
<appinfo>
<meta.section type="since"/>
</appinfo>
<documentation>
2.0.0
</documentation>
</annotation>

<annotation>
<appinfo>
<meta.section type="examples"/>
</appinfo>
<documentation>
[Enter extension point usage example here.]
</documentation>
</annotation>

<annotation>
<appinfo>
<meta.section type="apiinfo"/>
</appinfo>
<documentation>
[Enter API information here.]
</documentation>
</annotation>

<annotation>
<appinfo>
<meta.section type="implementation"/>
</appinfo>
<documentation>
[Enter information about supplied implementation of this extension point.]
</documentation>
</annotation>

<annotation>
<appinfo>
<meta.section type="copyright"/>
</appinfo>
<documentation>
KIELER - Kiel Integrated Environment for Layout Eclipse RichClient

http://rtsys.informatik.uni-kiel.de/kieler

Copyright 2020 by
+ Kiel University
+ Department of Computer Science
+ Real-Time and Embedded Systems Group

This code is provided under the terms of the Eclipse Public License (EPL).

</documentation>
</annotation>

</schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
*
* http://rtsys.informatik.uni-kiel.de/kieler
*
* Copyright 2020 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
*
* This code is provided under the terms of the Eclipse Public License (EPL).
*/
package de.cau.cs.kieler.klighd;

import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis;

/**
* Interface for the KLighD extension to add functionality in terms of other extensions during the startup of KLighD
* programmatically. This is meant to register e.g. {@link IAction}, {@link AbstractDiagramSynthesis}, and other
* extensions programmatically using the programmatic extension registration in {@link KlighdDataManager} to avoid
* having to use Eclipse extensions or Java Service loader in different launch scenarios.
*
* @author nre
*/
public interface IKlighdStartupHook {

/**
* The hook that gets executed during the startup of KLighD.
*/
void execute();

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -52,7 +51,6 @@
import com.google.common.collect.Multimap;

import de.cau.cs.kieler.klighd.internal.ISynthesis;
import de.cau.cs.kieler.klighd.internal.macrolayout.KlighdDiagramLayoutConnector;
import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis;
import de.cau.cs.kieler.klighd.syntheses.GuiceBasedSynthesisFactory;
import de.cau.cs.kieler.klighd.syntheses.ReinitializingDiagramSynthesisProxy;
Expand All @@ -71,6 +69,9 @@ public final class KlighdDataManager {
/** identifier of the extension point for model transformations. */
private static final String EXTP_ID_DIAGRAM_SYNTHESES = "de.cau.cs.kieler.klighd.diagramSyntheses";

/** identifier of the extension point for startup hooks. */
private static final String EXTP_ID_STARTUP_HOOK = "de.cau.cs.kieler.klighd.klighdStartupHook";

/** name of the 'viewer' element. */
private static final String ELEMENT_VIEWER = "viewer";

Expand All @@ -95,6 +96,9 @@ public final class KlighdDataManager {
/** name of the 'offscreenRenderer' element. */
private static final String ELEMENT_OFFSCREEN_RENDERER = "offscreenRenderer";

/** name of the 'startupHook' element. */
private static final String ELEMENT_STARTUP_HOOK = "startupHook";

/** name of the 'id' attribute in the extension points. */
private static final String ATTRIBUTE_ID = "id";

Expand Down Expand Up @@ -142,6 +146,9 @@ public final class KlighdDataManager {
private static final String INSTANTIATION_FAILURE_MSG =
"KLighD: An unexpected failure occured while instantiating "
+ "class <<CLAZZ>>. See attached trace for details." + NEW_LINE;

private static final String STARTUP_HOOK_ERROR_MSG =
"KLighD: an unexpected failure occured while executing a startup hook. See attached trace for details.";

/** the singleton instance. */
private static KlighdDataManager instance;
Expand All @@ -151,6 +158,12 @@ public final class KlighdDataManager {
*/
static {
instance = new KlighdDataManager();
// Load the startup hooks after creating the instance, as they should use this instance to hook into.
if (Klighd.IS_PLATFORM_RUNNING) {
loadStartupHooksViaExtensionPoint();
} else {
loadStartupHooksViaServiceLoader();
}

This comment has been minimized.

Copy link
@sailingKieler

sailingKieler Aug 19, 2020

Member

Is there any reason for putting this here and not appending to the existing evaluations in the constructor ? (except for confusing readers)

This comment has been minimized.

Copy link
@NiklasRentzCAU

NiklasRentzCAU Aug 19, 2020

Author Member

As the comment above suggests, the hooks are supposed to use the programmatic API of KLighD to register further behavior. For that to work, the way to do that needs to work first:

KlighdDataManager.getInstance().register[...].register[...];

is the default way to do the programmatic registrations, and for the the instance needs to be initialized first. If we load this extension point in the constructor, the instance is not returned yet and will cause a NPE. That is why this is seperate of loading the other extensions.

This comment has been minimized.

Copy link
@sailingKieler

sailingKieler Aug 19, 2020

Member

Damn, you're right.

}

/**
Expand Down Expand Up @@ -717,6 +730,62 @@ private void loadDiagramSynthesesViaServiceLoader(Map<String, ISynthesis> idSynt
}
}

/**
* Loads the registered {@link de.cau.cs.kieler.klighd.IKlighdStartupHook} from the extension point.
*/
private static final void loadStartupHooksViaExtensionPoint() {
final Iterable<IConfigurationElement> extensions = Iterables.filter(
Arrays.asList(
Platform.getExtensionRegistry().getConfigurationElementsFor(EXTP_ID_STARTUP_HOOK)
),
element -> ELEMENT_STARTUP_HOOK.equals(element.getName())
);

for (final IConfigurationElement element : extensions) {
final String id = element.getAttribute(ATTRIBUTE_ID);
if (Strings.isNullOrEmpty(id))
reportError(EXTP_ID_STARTUP_HOOK, element, ATTRIBUTE_ID, null, null);

else {

// initialize model transformation from the extension point
IKlighdStartupHook startupHook = null;
try {
startupHook = (IKlighdStartupHook) element.createExecutableExtension(ATTRIBUTE_CLASS);

} catch (final CoreException exception) {
Klighd.handle(
new Status(IStatus.ERROR, Klighd.PLUGIN_ID,
CORE_EXCEPTION_ERROR_MSG.replace("<<CLAZZ>>",
element.getAttribute(ATTRIBUTE_CLASS)), exception));
}

if (startupHook != null) {
try {
startupHook.execute();
} catch (Throwable t) {
Klighd.log(new Status(IStatus.ERROR, Klighd.PLUGIN_ID, STARTUP_HOOK_ERROR_MSG, t));
}
}
}
}
}

/**
* Loads the registered {@link de.cau.cs.kieler.klighd.IKlighdStartupHook} via Java {@link ServiceLoader}.
*/
private static void loadStartupHooksViaServiceLoader() {

for (IKlighdStartupHook startupHook : ServiceLoader.load(IKlighdStartupHook.class,
KlighdDataManager.class.getClassLoader())) {
try {
startupHook.execute();
} catch (Throwable t) {
Klighd.log(new Status(IStatus.ERROR, Klighd.PLUGIN_ID, STARTUP_HOOK_ERROR_MSG, t));
}
}
}

private void registerDiagramSynthesisClass(final String id,
Class<? extends AbstractDiagramSynthesis<?>> clazz, boolean wrapWithReinitializer,
Map<String, ISynthesis> idSynthesisMapping,
Expand Down

0 comments on commit c32530a

Please sign in to comment.