Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Prerequisites

To understand this article, you need a basic understanding of Java and maven. See Maven in five minutes for a quick introduction to maven.

Info
This tutorial describes the IGB 9 API, the development version of IGB planned for release to users in spring of 2016. For an earlier version describing the IGB 8 API, IGB 8 API (deprecated).

 

About Web Links

When users right-click an annotation, they see a context menu that contains one or more options called "linkouts," menus that run a Google Web search or open a Web page related to the selected item on some external site. The Weblinks module is responsible for creating and adding linkout menu items to the context menu.

The Weblinks module also lets users create all-new custom linkouts using regular expressions. It accomplishes this feat by allowing regular expression patterns to be applied to the annotation or track currently selected to dynamically populate a context menu for a given selection.  If an annotation or track id matches a user-defined pattern, then IGB creates a linkout menu item with URL defined in the custom linkout. 

Where the Web Links module hooks into IGB

The Weblinks module has two user interface hooks into the IGB application.

  1. The IGB Toolbar menu contains a menu option labeled Configure Web Links. This is the first hook.
  2. Selecting Configure Web Links opens the Web links configuration window. IGB includes several built-in linkout patterns in the top section. The middle section contains user-defined Custom Web Links.
  3. When users right-click an annotation, a context menu appears that contains menu items for each matching Web Link pattern. This is the second hook. Note that if only one Web Link pattern matches, it appears in the main menu, not in sub menus as shown below. 

 

 

...

Web links project and directory structure

The Web Links module, like all other modules in IGB, uses maven to compile and deploy the web links jar file, called an "artifact." The Web Links project resides in the IGB code repository. It uses the same directory structure as any other maven-based project. The following image shows Web Links directory structure within the larger IGB project:

...

Project configuration file - pom.xml

Let's review the Web Links module's POM (project object model) file. As with other maven projects, the POM file defines the overall structure of the project and its requirements, including:

...

Code Block
languagexml
titleWeb Links pom.xml
collapsetrue
<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>
    <parent>
        <groupId>com.affymetrix</groupId>
        <artifactId>igb-project</artifactId>
        <version>8.6.0</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <groupId>org.lorainelab.igb</groupId>
    <artifactId>weblinks</artifactId>
    <packaging>bundle</packaging>

    <name>WebLinks</name>
    
    <dependencies>
        <dependency>
            <groupId>biz.aQute.bnd</groupId>
            <artifactId>bndlib</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.affymetrix</groupId>
            <artifactId>genometry</artifactId>
            <scope>provided</scope>
        </dependency>  
        <dependency>
            <groupId>com.affymetrix</groupId>
            <artifactId>igb-services</artifactId>
            <scope>provided</scope>
        </dependency>      
        <dependency>
            <groupId>com.affymetrix</groupId>
            <artifactId>igbSwingExt</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--Start of logging dependencies-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--End of logging dependencies-->
        <dependency>
            <groupId>org.lorainelab.igb</groupId>
            <artifactId>igb-preferences</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.jidesoft</groupId>
            <artifactId>jide-ultimate</artifactId>
            <scope>provided</scope>            
        </dependency>
        <dependency>
            <groupId>com.affymetrix</groupId>
            <artifactId>affymetrix-common</artifactId>
            <scope>provided</scope>           
        </dependency>
        <dependency>
            <groupId>org.lorainelab.igb</groupId>
            <artifactId>igb-genoviz-extensions</artifactId>
            <scope>provided</scope>     
        </dependency>
        <dependency>
            <groupId>org.lorainelab.igb</groupId>
            <artifactId>synonym-lookup</artifactId>
            <scope>provided</scope>
        </dependency>
         <dependency>
            <groupId>org.lorainelab.igb</groupId>
            <artifactId>context-menu-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>${project.parent.basedir}/bundles/dynamic</directory>
                            <includes>
                                <include>${project.build.finalName}.jar</include>
                            </includes>
                            <followSymlinks>false</followSymlinks>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>install</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>${project.groupId}</groupId>
                                    <artifactId>${project.artifactId}</artifactId>
                                    <version>${project.version}</version>
                                </artifactItem>
                            </artifactItems>
                            <outputDirectory>${project.parent.basedir}/bundles/dynamic</outputDirectory>
                            <overWriteReleases>true</overWriteReleases>
                            <overWriteSnapshots>true</overWriteSnapshots>
                        </configuration>
                    </execution>
                </executions>
            </plugin>          
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                        <Import-Package>*</Import-Package>
                        <Export-Package/>
                        <Service-Component>*</Service-Component>
                        <Bundle-Description>${bundleDescription}</Bundle-Description>
                    </instructions>
                </configuration>
            </plugin>  
            <plugin>
                <groupId>org.lorainelab.igb</groupId>
                <artifactId>bundle-markdown-encoder</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>encodeMarkdown</goal>
                        </goals>
                    </execution> 
                </executions>
                <configuration>
                    <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
                </configuration>
            </plugin>       
        </plugins>
    </build>
</project>

 

Parent Tag

Code Block
languagexml
<parent>
   <groupId>com.affymetrix</groupId>
   <artifactId>igb-project</artifactId>
   <version>8.6.0</version>
   <relativePath>../../pom.xml</relativePath>
</parent>

The parent tag indicates that the POM for the IGB code base, defined using a relative path, is the parent for the Web Links POM. This means that the Web Links POM can reference dependencies defined in the parent POM. The parent tag also indicates that the  is the par the IGB code base using a relative path. By inheriting from the parent POM, the Web Links module's POM can reference dependencies defined in the parent. Dependencies defined in the parent POM is compatible with IGB version 8.6.0 and higher.

Packaging Tag  

Code Block
languagexml
<packaging>bundle</packaging>

...

We are using packaging type of "bundle" to take advantage of the plug-able architecture of Maven itself and the custom packaging type which is defined in the Apache Felix Maven Bundle Plugin.  Throughout the IGB project we use this maven plugin to generate all of the OSGi meta-data which makes the jar file into a module that can be managed in the IGB OSGi runtime.  

Dependencies tag

Dependencies are externally provided artifacts (typically jar files) the Web Links module needs to compile and/or run. This section will highlight some of the important dependencies used in this module. The dependencies tag contains one or more dependency child tags, described in the following section.

Note that each dependency tag includes a scope tag with value of "provided." This is because the parent POM defines these dependencies in its dependencyManagement tag. All we need to do here is indicate artifacts groupId and artifactId. 

biz.aQute.bnd:bndlib

The bndlib library manages the Declarative Services annotations used throughout the IGB project.

Code Block
languagexml
<dependency>
   <groupId>biz.aQute.bnd</groupId>
   <artifactId>bndlib</artifactId>
   <scope>provided</scope>
</dependency>

 

com.affymetrix:genometry

Contains data models and utility methods used throughout the project. When the user right-clicks a item to select it, the Web Links module obtains a reference to the selected data model and uses it to create linkouts to external resources. These genometry module defines these data models and their interfaces. 

Code Block
languagexml
<dependency>
       <groupId>com.affymetrix</groupId>
       <artifactId>genometry</artifactId>
       <scope>provided</scope>
</dependency>  

com.affymetrix:igb-services

This is an API module which contains most of the service interfaces which allow hooks into the IGB platform, including the interface (IgbMenuItemProvider) the Web Links module uses to add a menu item to IGB's Tools menu. The following sections will discuss this in more detail.

Code Block
languagexml
<dependency>
       <groupId>com.affymetrix</groupId>
       <artifactId>igb-services</artifactId>
       <scope>provided</scope>
</dependency> 

 

com.affymetrix:igbSwingExt

Code Block
languagexml
<dependency>
     <groupId>com.affymetrix</groupId>
     <artifactId>igbSwingExt</artifactId>
     <scope>provided</scope>
</dependency> 

This module contains many custom swing components which are commonly used throughout the project.  In some cases the interfaces contained in the igb-services modules require the use of some of the custom swing components contained in this module.  We do this because we often have the need to add additional functions to basic swing components (e.g. weight for Menu Items to allow sorting in the context of a pluggable system).

com.google.guava:guava

Code Block
languagexml
<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <scope>provided</scope>
</dependency> 

A very useful utility module which is used extensively in our project.  We highly recommend you take a look at the content of this project (see https://code.google.com/p/guava-libraries/), and occasionally even demmand in our interfaces you leverage some of the collection data structures from Guava (e.g. Multimap). You will thank us later!

org.slf4j:slf4j-api

This module (Simple Logging Facade for Java) provides a logging API developers should use to report status information to the console, useful for debugging. Never write directly to stdout or stderr. Use SLFJ instead. 

...

Code Block
languagexml
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <scope>provided</scope>
</dependency>

 

org.lorainelab.igb:context-menu-api

This API module defines the interfaces and methods Web Links must implement in order to add items to the annotation context (right-click) menu.

Code Block
languagexml
<dependency>
   <groupId>org.lorainelab.igb</groupId>
   <artifactId>context-menu-api</artifactId>
   <scope>provided</scope>
</dependency>

 

 

Build and plugins section

maven-clean-plugin

The utility of this plugin in our pom is largely context specific.  In our case, we are simply  hooking into the clean phase of the maven lifecycle to delete a copy of the compiled module we make using the maven-dependency-plugin.  

maven-dependency-plugin

The utility of this plugin is specific to the context of our build, but this plugin is used to hook into the install phase of the maven lifecycle and make a copy of the module in a location where we will later have OSGi install it during development runs.  

maven-bundle-plugin

This is the most important plugin to be aware of as an IGB module developer.  This plugin is a wrapper around the bnd project which is known as the "Swiss army knife of OSGi".

Code Block
languagexml
<plugin>
     <groupId>org.apache.felix</groupId>
     <artifactId>maven-bundle-plugin</artifactId>
     <extensions>true</extensions>
     <configuration>
         <instructions>
             <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
             <Import-Package>*</Import-Package>
             <Export-Package/>
             <Service-Component>*</Service-Component>
         </instructions>
     </configuration>
</plugin> 

Instructions Tag

Bundle-SymbolicName 

This tag is used to provide the unique module name which will be used by OSGi along with the version to identify the module.

Import-Package

We use a wildcard import in this tag to specify we want to let bnd leverage Maven to populate the imports of our OSGi Manifest file. For more information on this tag see http://wiki.osgi.org/wiki/Import-Package

Export-Package

We include this tag here mostly for reference, and as you can see, we do not specify any exports from this implementation module.  This means no classes from this module will ever be available to other modules and we have successfully hidden all of our implementation details.

Service-Component

This is a special tag which instructs the felix bundle plugin to scan our modules classes for the Declarative Service annotations and generate the required service descriptor meta-data for OSGi.  For more information on this topic see http://wiki.osgi.org/wiki/Service-Component and http://www.aqute.biz/Bnd/Components

...

How the Web Links module uses IGB API extension points

IGB Services (com.affymetrix:igb-services)

IGB Services is a collection of useful utilities the IGB framework provides for external modules - IGB Apps - to reference. The core IGB code also uses this services API. It contains interfaces that are publicly exposed as well as implementation classes which are not. Hiding the implementation details while exposing the API gives the project greater flexibility and gives developers a clearer guidelines on how to extend IGB.

...

As you can see, there is an activate method annotated with the @Activate annotation to signal that this method should be called once all service dependencies have been satisfied.  The content of the method makes it clear that the igbService variable must have been instantiated before this reference.  This short block of code is a great example of the power of Declarative Services to manage the lifecycle of our services.  With almost no work, we can publish and consume services from the registry.

Context Menu API (org.lorainelab.igb:context-menu-api)

The LinkControl class implements the interface AnnotationContextMenuProvider, defined in the context-menu-api module. This interface defines methods that enable the framework (IGB itself) to add menu items to context menus when users right-click them in IGB.

...

Note that this design facilitates moving IGB from using Swing to JavaFX, a newer GUI toolkit that is replacing Swing in Java applications. framework handles the work of creating 

IgbMenuItemProvider

This interface, defined in the IGB Services module, is designed allow module developers to hook into the applications main toolbar.

...

The interface is simple enough, but the getParentMenuName method will likely not return a String literal in the eventual 1.0 release of our API (a custom enum type would be more appropriate).  

Weblinks use of IgbMenuItemProvider

The WebLinksAction class implements this interface resulting in the following menu item being created.

WindowServiceLifecycleHook

This interface allows a module to hook into the global application lifecycle events of startup and shutdown.  In the case of the Weblinks module, we want to be signaled about the application shutdown in order to export any user generated "Linkouts".  

Weblinks use of WindowServiceLifecycleHook

Code Block
languagejava
@Component(name = WebLinkExportRoutine.COMPONENT_NAME, immediate = true, provide = WindowServiceLifecycleHook.class)
public class WebLinkExportRoutine implements WindowServiceLifecycleHook {
    private static final Logger logger = LoggerFactory.getLogger(WebLinkExportRoutine.class);
    public static final String COMPONENT_NAME = "WebLinkExportRoutine";
    private WebLinkExporter exporter;
    public WebLinkExportRoutine() {
    }
    @Override
    public void stop() {
        if (exporter != null) {
            exporter.exportUserWebLinks();
        }
    }
    @Override
    public void start() {
    }
    @Reference
    public void setWebLinkUtils(WebLinkExporter exporter) {
        this.exporter = exporter;
    }
}

...