Tutorial: Creating an Insight Plug-in with the Insight Development Kit

This tutorial describes how to create a Spring Insight plug-in called insight-plugin-tiles using the Insight Development Kit. The purpose of the plug-in is to capture the amount of time that Apache Tiles spends rendering content. Apache Tiles is a templating framework built to simplify the development of Web application user interfaces.

In particular, the plug-in will track how much time is spent in the org.apache.tiles.TilesContainer#render method. The signature of the render method is:

TilesContainer#render(String definitionName, Object... requestItems)

The plug-in will intercept all calls to this method when it is invoked in the target application. Insight will track how much time is spent in this method and create new operations based on the definitionName.

In the tutorial it is assumed that you do not have any required product installed, and so the tutorial provides the steps to download and install everything you need. If, however, you have already installed one or more of these products, modify the tutorial accordingly. The full list of products or bundles required for this tutorial is as follows:

Follow these steps to create a sample Spring Insight plug-in:

  1. Create a workspace. For example, to create a workspace dev/insight-dev in your home directory:

    $ cd ~
    $ mkdir dev/insight-dev
    $ cd dev/insight-dev

    You will do all your development within the ~/dev/insight-dev directory. Everything that you download, install, and create will be contained here.

  2. Download tc Server Developer Edition and the Insight Development Kit from the VMware download site.

    Assuming you downloaded the files into ~/dev/insight-dev, you should see the following:

    $ ls
    vfabric-tc-server-developer-2.5.X.X.zip
    insight-developer-kit-1.0.X.X.zip
  3. Install tc Server, if necessary, by unpacking the ZIP file into the ~/dev/insight-dev directory.

    If you have already installed tc Server, you can skip this step.

    $ cd ~/dev/insight-dev
    $ unzip vfabric-tc-server-developer-2.5.X.X.zip

    Replace X.X with the actual version numbers. This creates a child directory called vfabric-tc-server-developer.

  4. Create a tc Server instance that uses the insight template and start it:

    $ cd vfabric-tc-server-developer
    $ ./tcruntime-instance.sh create insight-instance -t insight
    $ ./tcruntime-ctl.sh insight-instance start
  5. Verify that Insight is working.

    Browse to http://localhost:8080/insight to verify Insight started correctly. If so, you should see The Spring Insight Dashboard.

  6. Unpack the Insight Development Kit:

    $ cd ~/dev/insight-dev
    $ unzip vfabric-insight-developer-kit-1.0.X.X.zip

    Replace X.X with the actual version numbers. This creates a child directory called vfabric-insight-developer-kit-1.0.X.X, where X.X refers to a specific version.

  7. Create the insight-plugin-tiles plug-in.

    Use the sample plug-in from the Insight Development Kit to create a new plug-in, called insight-plugin-tiles, in your insight-dev directory:

    $ cd ~/dev/insight-dev
    $ cp -Rp vfabric-insight-developer-kit-1.0.X.X/samples/payme-insight-plugin insight-plugin-tiles

    The preceding action sets up an insight-plugin-tiles plug-in with a build system, test framework, and so on. Because this tutorial tells you which files to create, in the correct sequence, you can remove the ones that come with the payme-insight-plugin. These files can serve as a reference and starting point when you create your own plug-in.

    $ cd ~/dev/insight-dev/insight-plugin-tiles
    $ rm -rf src/main/java/org/myorg/insight/myplugin
    $ rm src/main/resources/META-INF/insight-plugin-myplugin.xml
    $ rm -rf src/main/resources/org/myorg/insight/myplugin
    $ rm -rf src/test/java/org/myorg/insight/myplugin
  8. If you want to use SpringSource Tool Suite (STS) as your IDE, and you have not yet installed it, download the latest version from STS Product Page, then install and start it.

  9. Import insight-plugin-tiles into SpringSource Tool Suite (Eclipse) or another IDE.

    From SpringSource Tool Suite:

    1. Select File > New > Java Project.

    2. Select Create Project from Existing Source.

    3. Enter ~/dev/insight-dev/insight-plugin-tiles (or whatever is appropriate for your environment) in the Directory field.

    4. Click Finish.

  10. Update the Maven pom.xml file by adding the following dependency in the <dependencies> section:

            <dependency>
                <groupId>org.apache.tiles</groupId>
                <artifactId>tiles-core</artifactId>
                <version>2.1.3</version>
                <scope>provided</scope>
            </dependency>

    The insight-plugin-tiles plug-in requires some Apache Tiles interfaces and classes. You can make these available by including the Maven dependency shown in the preceding snippet. You will usually want to use the provided scope. The target application provides the jars at runtime.

  11. Change the plug-in name in pom.xml.

    Update the properties at the top of the file to reflect what you are building. The artifactId, name, and version dictate the file that is generated by the build process.

    <groupId>org.myorg.insight</groupId>
        <artifactId>insight-plugin-tiles</artifactId>
        <name>insight-plugin-tiles</name>
        <packaging>jar</packaging>
        <version>0.0.1.SNAPSHOT</version>
  12. Create the TilesRenderOperation class.

    This class is an implementation of Operation that will hold the data you collect from the TilesContainer#render method. The class basically holds the tile's definitionName.

    Name this file TilesRenderOperation.java and create it in the following directory: src/main/java/org/myorg/insight/tiles.

    package org.myorg.insight.tiles;
    
    import com.springsource.insight.intercept.operation.BasicOperation;
    import com.springsource.insight.intercept.operation.OperationType;
    import com.springsource.insight.intercept.operation.SourceCodeLocation;
    
    public class TilesRenderOperation
        extends BasicOperation
    {
        public static final String NAME = "tiles_render_operation";
        public static final OperationType TYPE = OperationType.valueOf(NAME);
        private String definitionName;
        private String label;
    
        public TilesRenderOperation(SourceCodeLocation scl, String definitionName) {
            super(scl);
            this.definitionName = definitionName;
            this.label = "Tiles Render: " + definitionName;
        }
    
        public String getLabel() {
            return label;
        }
    
        public OperationType getType() {
            return TYPE;
        }
    
        public String getDefinitionName() {
            return definitionName;
        }
    }

  13. Create the TilesRenderOperationCollectionAspect class.

    This aspect will be woven into the target application at runtime by AspectJ. In this aspect you specify the method that you want to capture (TilesContainer.render()) as the collectionPoint pointcut. The aspect is also responsible for creating new instances of the TilesRenderOperation that you just created.

    Name this file TilesRenderOperationCollectionAspect.aj and create it in the following directory: src/main/java/org/myorg/insight/tiles.

    package org.myorg.insight.tiles;
    
    import org.apache.tiles.TilesContainer;
    import org.aspectj.lang.JoinPoint;
    
    import com.springsource.insight.collection.AbstractOperationCollectionAspect;
    import com.springsource.insight.intercept.operation.Operation;
    
    public aspect TilesRenderOperationCollectionAspect
        extends AbstractOperationCollectionAspect
    {
        public pointcut collectionPoint()
            : execution(void TilesContainer.render(String, Object...));
    
        @Override
        protected Operation createOperation(JoinPoint jp) {
            Object[] args = jp.getArgs();
            String definitionName = (String)args[0];
            return new TilesRenderOperation(getSourceCodeLocation(jp), definitionName);
        }
    }
  14. Create the DummyTilesContainer class for testing.

    In this step, you create a dummy implementation to test that all implementations of TilesContainer.render() are captured by the aspect.

    When the Target Application is really running (because it is deployed to a running tc Runtime instance, and so on), the target application will occasionally make calls to the TilesContainer.render() methods. During testing, you create a dummy implementation of TilesContainer to test that the Aspects are able to correctly intercept calls.

    Create src/test/java/org/myorg/insight/tiles/DummyTilesContainer.java:

    package org.myorg.insight.tiles;
    
    import java.io.IOException;
    import java.io.Writer;
    import java.util.Map;
    
    import org.apache.tiles.Attribute;
    import org.apache.tiles.AttributeContext;
    import org.apache.tiles.TilesApplicationContext;
    import org.apache.tiles.TilesContainer;
    
    public class DummyTilesContainer implements TilesContainer {
    
        public void endContext(Object... requestItems) {
        }
    
        public Object evaluate(Attribute attribute, Object... requestItems) {
            return null;
        }
    
        public TilesApplicationContext getApplicationContext() {
            return null;
        }
    
        public AttributeContext getAttributeContext(Object... requestItems) {
            return null;
        }
    
        public void init(Map<String, String> initParameters) {
        }
    
        public boolean isValidDefinition(String definition, Object... requestItems) {
            return false;
        }
    
        public void prepare(String preparer, Object... requestItems) {
        }
    
        public void render(String definition, Object... requestItems) {
        }
    
        public void render(Attribute attribute, Object... requestItems) throws IOException {
        }
    
        public void render(Attribute attribute, Writer writer, Object... requestItems) throws IOException {
        }
    
        public void renderContext(Object... requestItems) {
        }
    
        public AttributeContext startContext(Object... requestItems) {
            return null;
        }
    }
  15. Create the test.

    To verify that the code works, create a unit test. You will run the test as part of the Maven build to verify that TilesRenderOperation is successfully captured by the TilesRenderOperationCollectionAspect.

    Create src/test/java/org/myorg/insight/tiles/ TilesRenderOperationCollectionAspectTest.java:

    package org.myorg.insight.tiles;
    
    import static org.junit.Assert.assertEquals;
    
    import org.junit.Test;
    
    import com.springsource.insight.collection.OperationCollectionAspectSupport;
    import com.springsource.insight.collection.OperationCollectionAspectTestSupport;
    
    public class TilesRenderOperationCollectionAspectTest
        extends OperationCollectionAspectTestSupport
    {
        @Test
        public void myOperationCollected() {
            // Step 1:  Execute some implementation of TilesContainer#render
            DummyTilesContainer container = new DummyTilesContainer();
            container.render("myDefinition", "requestItem1", "requestItem2");
    
            // Step 2:  Get the Operation that was just created by our aspect
            TilesRenderOperation op = (TilesRenderOperation) getLastEntered(TilesRenderOperation.class);
    
            // Step 3:  Validate
            assertEquals("myDefinition", op.getDefinitionName());
            assertEquals("Tiles Render: myDefinition", op.getLabel());
            assertEquals(DummyTilesContainer.class.getName(), op.getSourceCodeLocation().getClassName());
            assertEquals("render", op.getSourceCodeLocation().getMethodName());
        }
    
        public OperationCollectionAspectSupport getAspect() {
            return TilesRenderOperationCollectionAspect.aspectOf();
        }
    }
  16. Run the test:

    $ cd ~/dev/insight-dev/insight-plugin-tiles
    $ mvn test

    If everything works correctly, you will see BUILD SUCCESSFUL.

    You can also run the test from the SpringSource Tool Suite. Right-click on TilesRenderOperationCollectionAspectTest.java and select Run-As > JUnit Test.

  17. Copy the insight-plugin-tiles plug-in into the tc Runtime instance.

    It is time to try out the plug-in in Insight to verify that it works. Deploy the new plug-in to the tc Runtime instance by copying the generated plug-in JAR file into the insight/collection-plugins directory of your tc Runtime instance (created in a previous step and called insight-instance):

    $ cd ~/dev/insight-dev/insight-plugin-tiles
    $ mvn clean package
    $ cp target/insight-plugin-tiles-0.0.1.SNAPSHOT.jar \
         ../vfabric-tc-server-developer/insight-instance/insight/collection-plugins

    Note: For clarity, the preceding command is shown on two lines, but you should execute it all on one line.

  18. Update the configuration of the Spring Insight application so that you can view trace information about the Insight application itself. By default, Spring Insight does not instrument itself.

    Change this behavior by commenting out the application.context.ignore.dashboard: localhost|insight property in the insight.properties file, which is located in the ../vfabric-tc-server-developer/insight-instance/insight directory.

    The following insight.properties snippet shows the property commented out (see section in bold):

    ...
    # Application Context Instrumentation
    # Specify application contexts which should not be instrumented
    # by Insight.  Removing contexts that do not need Insight will improve
    # startup time of the application. Each entry must be prefixed with
    # "application.context.ignore." and takes the form of
    # "[Host name]|[Context name]"
    #
    #application.context.ignore.myapp: localhost|my-app
    #application.context.ignore.dashboard: localhost|insight
    ...

    We perform this step because we want to use the Insight Dashboard itself, which uses Tiles to display its UI, as a way to see the plugin in action. Because the insight-plugin-tiles plug-in will also collect data for the Insight Dashboard, you can see the effect immediately, but you must first specify that you want Insight to instrument itself.

  19. Start (or restart if it is already running) the tc Runtime instance to view the new TilesRenderOperation. For example, to start:

    $ cd ../vfabric-tc-server-developer
    $ ./tcruntime-ctl.sh insight-instance start

    Navigate to http://localhost:8080/insight to see the insight-plugin-tiles plug-in in action. Choose the insight (Insight Dashboard) application to narrow down the traces to just those generated by Insight itself.

    Click around in the Insight Dashboard to generate some pages (select Browse Resources, or drill into Trace Operations). Here you select a Trace from the timeline and can see the TilesRenderOperation showing how much time it took to render different tiles when generating the result of a request to http://localhost:8080/insight/endpoints/insight.

    The drilldown into the Tiles Render: traces/trace operation shows details about the TilesRenderOperation. Notice the Return Value: void and Exception: null properties. Those (along with the Tiles Render Operation Operation) are generated automatically by examining the TilesRenderOperation. To customize the UI that is rendered for the TilesRenderOperation, you must add it to the plug-in, as described in the following steps.

  20. In your IDE, such as STS in this tutorial, add the tiles_render_operation.ftl view.

    Insight plug-ins can use FreeMarker to customize the view. Create the following FreeMarker template using your IDE:

    src/main/resources/org/myorg/insight/tiles/ tiles_render_operation.ftl:

    <#ftl strip_whitespace=true>
    <#import "/insight-1.0.ftl" as insight />
    
    <@insight.group label="Tiles Render Operation">
        <@insight.entry name="definitionName" value=operation.definitionName />
    <@insight.group>

    The operation variable is bound to an instance of TilesRenderOperation. The FreeMarker code

    operation.definitionName

    calls the method TilesRenderOperation.getDefinitionName(). You can access all properties from your TilesRenderOperation if they have a getter (getDefinitionName()).

  21. Register the view with the plug-in.

    Insight locates views for Operations by looking for insight-plugin-*.xml Spring Application Context descriptor files. To enable the custom view, you inform Insight about it by adding the view to it.

    Create src/main/resources/META-INF/insight-plugin-tiles.xml:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:insight="http://www.springframework.org/schema/insight-idk"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/insight-idk 
           http://www.springframework.org/schema/insight-idk/insight-idk-1.0.xsd">
    <insight:plugin name="tiles" version="0.0.1" publisher="[your name here]" />
    <insight:operation-view operation="tiles_render_operation" 
           template="org/myorg/insight/tiles/tiles_render_operation.ftl"/>
    </beans>

    This file points Insight at the tiles_render_operation.ftl view when it attempts to render a TilesRenderOperation. The TilesRenderOperation.getType() method returns an OperationType that Insight uses to compose the bean id (operation.tiles_render_operation).

  22. Create a test for the view.

    The view takes the TilesRenderOperation as an input parameter and renders the tiles_render_operation.ftl based on it. You will create a TilesRenderOperationViewTest to verify that this rendering works.

    The AbstractOperationViewTest allows you to test tiles_render_operation.ftl rendering in only a few lines. Here you create a TilesRenderOperation, run it through the TilesRenderOperationView and make sure the output contains strings specified in the operation.

    Create src/test/java/org/myorg/insight/tiles/ TilesRenderOperationViewTest.java:

    package org.myorg.insight.tiles;
    
    import static org.junit.Assert.assertTrue;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import com.springsource.insight.idk.AbstractOperationViewTest;
    import com.springsource.insight.idk.WebApplicationContextLoader;
    import com.springsource.insight.intercept.operation.SourceCodeLocation;
    @ContextConfiguration(locations = { "classpath:META-INF/insight-plugin-tiles.xml",
                                        "classpath:META-INF/test-app-context.xml" },
                          loader = WebApplicationContextLoader.class)
    @RunWith(SpringJUnit4ClassRunner.class)
    public class TilesRenderOperationViewTest
        extends AbstractOperationViewTest
    {
        public TilesRenderOperationViewTest() {
            super(TilesRenderOperation.TYPE);
        }
    
        @Test
        public void testView() throws Exception {
            SourceCodeLocation scl = new SourceCodeLocation("MyClass", "methodName", 45);
            TilesRenderOperation operation = new TilesRenderOperation(scl, "myTileDefinition");
    
            String content = getRenderingOf(operation);
            System.err.println(content);
    
            // Simply test for some expected contents within the HTML.
            assertTrue(content.contains("definitionName"));
            assertTrue(content.contains("myTileDefinition"));
        }
    }
  23. Run the test.

    $ cd ~/dev/insight-dev/insight-plugin-tiles
    $ mvn test

    The test should report BUILD SUCCESSFUL.

  24. Try out the new view in Insight.

    To try out the new view, you copy a new package of your plug-in to the tc Runtime instance and (re)start it:

    $ mvn clean package
    $ cp target/insight-plugin-tiles-0.0.1.SNAPSHOT.jar \
         ../vfabric-tc-server-developer/insight-instance/insight/collection-plugins
    $ cd ../vfabric-tc-server-developer
    $ ./tcruntime-ctl.sh insight-instance restart

    Note: For clarity, the second command in the preceding example is shown on two lines, but you should execute it all on one line.

    Click around in Insight to generate some traces. Here is a trace that was generated for /insight/applications/endpoints/insight: