Page tree
Skip to end of metadata
Go to start of metadata

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.

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 does this 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:

  • project identifiers - name, artifactId and groupId that facilitate importing the project into IGB
  • dependencies - other modules and libraries the project depends on called "dependencies." It assigns a unique identifier for the Web Links module jar file (called an artifact) and includes instructions for building and releasing the module
  • parent - specifies the parent POM; in this case, the igb project POM, which defines dependencies used by Web Links and other IGB modules
  • build and plug-in tags - these tell maven how to build and install the project, as well as which tools to use

The entire POM file appears here, followed by explanations for individual sections.

Web Links pom.xml
<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

<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. 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 are compatible with IGB version 8.6.0 and higher.

Packaging Tag  

<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.

<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. The genometry module defines these data models and their interfaces. 

<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.

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


com.affymetrix:igbSwingExt

<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

<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 demand 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. 

See http://www.slf4j.org/manual.html.

<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.

<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".

<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.

The Web Links module accesses implementation of the  IGB Service interfaces via the service registry managed by the OSGI runtime. There are several ways this could be done; however, our preferred way to access services is with the use of the Declarative Service @Component annotation.

Example of Service Access Using DS
@Component(immediate = true)
public class LinkControl implements AnnotationContextMenuProvider {

    private static final String SEARCH_WEB_ICONPATH = "searchweb.png";
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MenuIcon.class);    
    private IgbService igbService;

    public LinkControl() {
    }
    @Activate
    private void activate() {
    }
    @Reference
    public void setIgbService(IgbService igbService) {
        this.igbService = igbService;
    }
...
  @Override
    public Optional<ContextMenuItem> buildMenuItem(AnnotationContextEvent event) {
        if (event.getSelectedItems().isEmpty()) {
            return Optional.empty();
        }
        SeqSymmetry primarySym = event.getSelectedItems().get(0);
        if (primarySym instanceof CdsSeqSymmetry) {
            primarySym = ((CdsSeqSymmetry) primarySym).getPropertySymmetry();
        }
        return buildContextMenuItem(primarySym); // a private method defined elsewhere in the class
    }

In the above code block, the @Component annotation declares that this class will be managed as a "component" by the OSGi runtime.  See the Declarative Services specification for more details (http://wiki.osgi.org/wiki/Declarative_Services).  

Next, there is the creation of a privately scoped IgbService variable along with a setter for this variable.  The setter is annotated with the @Reference annotation to signal to the OSGi component runtime that this setter in an injection point for another component managed by the runtime.  When IgbService becomes available it will be "injected" (in the dependency injection sense of the terminology) into this service for reference before the component is "Activated".  

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.

The key method is "buildMenuItem" which accepts an AnnotationContextEvent, created when users right-click an annotation in the main display in IGB. Note that in IGB, an "annotation" is defined as anything with start and end coordinates on genomic sequence, such as gene models, RNA-Seq read alignments, and so on.

com.lorainelab.context.menu.AnnotationContextMenuProvider
package org.lorainelab.igb.context.menu;

public interface AnnotationContextMenuProvider {
    public Optional<ContextMenuItem> buildMenuItem(AnnotationContextEvent event);
    public MenuSection getMenuSection();
    public static enum MenuSection {
        INFORMATION, SEQUENCE, APP, UI_ACTION;
    }
} 

When LinkControl intercepts such an event, it examines the event and returns an instance of ContextMenuItem, as appropriate. The ContextMenuItem class is a "model" object only; it doesn't specify which GUI framework must be used to create the menu and add it to the IGB interface.

Note that this design facilitates moving IGB from using Swing to JavaFX, a newer GUI toolkit that is replacing Swing in Java applications.

IgbMenuItemProvider

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


public interface IgbMenuItemProvider {
    public String getParentMenuName();
    public JRPMenuItem getMenuItem();
    public int getMenuItemWeight();
} 

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

@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;
    }
}

As you can see here, the code is straightforward.  The @Component annotation is used to register this class as a component providing an instance of the WindowServiceLifecycleHook interface.  When the application is being shutdown, the stop method of this interface is called for each instance in the service registry.