Skip to content

Tutorial: Writing your own extensions

Per Minborg edited this page Aug 28, 2020 · 45 revisions

Speedment is built up using a number of different components. These components handle everything from translating the relational database results into an object-oriented metadata model for logging debug messages throughout the system. One important feature of the component system is the ability to extend the platform with your own code. In this tutorial, we will go through how to create a new custom component and add it to an existing Speedment project.

Step 1 - Create a component project

Each custom component should reside in its own maven project. This will allow you to add it as a dependency to any project where you want to make use of the component. Begin by creating a new project and specify the pom.xml file to extend the speedment main project with the same version as the project version:

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.speedment</groupId>
    <artifactId>speedment-awesome-plugin</artifactId>
    <version>3.2.0</version>
    <packaging>jar</packaging>
    
    <name>Awesome Speedment Plugin</name>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>com.speedment</groupId>
            <artifactId>runtime</artifactId>
            <version>${project.version}</version>
            <type>pom</type>
        </dependency>
    </dependencies>
</project>

Step 2 - Write a Component Class

The real magic of the plugins is done inside the component class. Here we can hook up code to run at different stages during the execution.

public final class Awesome extends AbstractComponent {

    @Override
    public Class<? extends Component> getComponentClass() {
        return Awesome.class;
    }

    @ExecuteBefore(INITIALIZED)
    public AbstractComponent initialize() {
        // Write some code here...
        return this;
    }

    @ExecuteBefore(RESOLVED)
    public AbstractComponent resolve( @WithState(INITIALIZED) OtherComponent other) {
        // ...or here...
        return this;
    }

    @ExecuteBefore(STARTED)
    public AbstractComponent start() {
        // ...or even here.
        return this;
    }
}

Other components can be made available by declaring them as parameters. During start-up components, progress through the states CREATED, INITIALIZED, RESOLVED and STARTED in that order. Initialization order dependencies of components may be expressed with the @WithState annotation. The number of available states to ask for in the @WithState annotation are limited by the fact that no component will enter a state before all other components have reached the preceding state (thus all components will reach for example INITIALIZED before any component can reach RESOLVED).

Here is an example of how a plugin could define a new DbmsType:

@ExecuteBefore(RESOLVED)
public AbstractComponent resolve(@WithState(RESOLVED) DbmsHandlerComponent dbmsHandler) {
    dbmsHandler.install(
            new ReallyCoolNewDbmsType()
    );
}

Another example of the power in the component system is the extendability of the GUI. In this example, we will add some extra logic to analyze the generated metamodel before it is shown in the GUI.

@ExecuteBefore(RESOLVED)
public AbstractComponent resolve(@WithState(RESOLVED) EventComponent eventComponent) {
    eventComponent.on(ProjectLoaded.EVENT, event -> {
        event.project().traverseOver(ForeignKey.class).forEach(fk -> {
            System.out.println("Found path between " +
                fk.ancestor(Table.class).map(Table::getName).get() + 
                " and " +
                fk.getForeignTableName() + "."
            );
        });
    }
    return this;
}

Step 3 - Using the Component in an External Project

If you want to use your new component in an external project and you want it to be executed in the same java runtime as for an example the GUI, you need to do two small adjustments to the speedment-maven-plugin-tag of your pom.xml. You need to add your new component project as a dependency to the Speedment plugin and configure it to load it.

...
<plugin>
    <groupId>com.speedment</groupId>
    <artifactId>speedment-maven-plugin</artifactId>
    <version>3.2.10</version>
         
    <!-- Add your awesome plugin to the list of dependencies -->       
    <dependencies>
        <dependency>
            <groupId>com.speedment</groupId>
            <artifactId>speedment-awesome-plugin</artifactId>
            <version>3.2.0</version>
        </dependency>
    </dependencies>
                
    <!-- Make sure the builder is loaded when the maven plugin is started -->
    <configuration>
        <components>
            <component implementation="com.speedment.plugin.awesome.AwesomeBuilder"></component>
        </components>
    </configuration>
</plugin>