Dashboard > BobsGear Main Space > ... > Confluence Plugin Development Diary > Confluence Development Environment Setup > Survey of Bundled Plugin Functionality > Confluence Example Plugin - Amazon Web Services Plugin
Confluence Example Plugin - Amazon Web Services Plugin
Added by Garnet R. Chaney, last edited by Garnet R. Chaney on Apr 27, 2007  (view change)
Labels: 


The amazon plugin is the first example plugin I tried after doing the Confluence Development Environment Setup.


The reason for this article

Quickly after building the macro and trying it out, I discovered that the output of the Amazon plugin was broke. Java is not my first choice for programming language, so I had to learn a little Java during the course of fixing the macro. This page documents the process I went through to fix this macro.

The Purpose of the Amazon Plugin

Unfortunately, the macro is shipped without any kind of readme about it's functionality.

The purpose of this plugin is to use Amazon Web Services (AWS) to request product information for a particular ASIN, and display it on a wiki page. AWS is a nice service with a wealth of information, but beware that everytime someone looks at your webpage, AWS will be called each time you use this on your page. It is possible that a slow down on Amazon Web Services, or other internet connectivity problem between your server and Amazon.com, might cause your wiki website to be inaccessible. Also, AWS used to make some requirements about maximum frequency of access (1 call per second), and if you have a very popular wiki with pages that link to a lot of books, you could exceed their maximum access speed limits. Many websites get around that problem with the use of short data caching, but this macro plugin does not supply such a feature.

The Bobsgear project has already prepared some simple user macros for linking to Amazon that don't depend on AWS. Check out Amazon book linking macros.

Functionality demonstrated in the Amazon plugin

This plugin demonstrates:

  • How to get results from a web service
  • How to parse an XML response
  • How to pretty print an XML response into HTML
  • How to create a product object with fields accessible from Velocity
  • How to make that product object available to render the macro results to HTML using a Velocity Macro

First Try To Build The Amazon Plugin

Going to ear/war plugins directory, and running ..\tools\ant\bin\ant seems to work.

Tried to build the plugins/amazon plugin, but it failed:

C:\projects\confluence-2.4.5.earwar\plugins> ..\tools\ant\bin\ant -Dlibrary=amazon build
Buildfile: build.xml

checklibrary:

build:
    [mkdir] Created dir: C:\projects\confluence-2.4.5.earwar\plugins\amazon\clas
ses
    [javac] Compiling 2 source files to C:\projects\confluence-2.4.5.earwar\plugins\amazon\classes

BUILD FAILED
file:C:/projects/confluence-2.4.5.earwar/plugins/build.xml:46:
    C:\projects\confluence-2.4.5.earwar\plugins\lib not found.

Total time: 1 second

There is no lib directory under plugins. Created directory plugins/lib and tried building Amazon plugin. Got 13 errors.

Configuring the Project Properties

Edited the project.properties to set webpp.dir to my ${conflhome}, c:/projects/confluence-2.4.5.earwar/confluence

Build success for Amazon plugin

Now, build of Amazon works:

C:\projects\confluence-2.4.5.earwar\plugins>..\tools\ant\bin\ant -Dlibrary=amazon build
Buildfile: build.xml

checklibrary:

build:
    [javac] Compiling 2 source files to C:\projects\confluence-2.4.5.earwar\plug
ins\amazon\classes
    [javac] Note: C:\projects\confluence-2.4.5.earwar\plugins\amazon\src\java\co
m\atlassian\confluence\extra\amazon\AmazonAsinMacro.java uses or overrides a dep
recated API.
    [javac] Note: Recompile with -Xlint:deprecation for details.
    [mkdir] Created dir: C:\projects\confluence-2.4.5.earwar\plugins\amazon\dist

      [jar] Building jar: C:\projects\confluence-2.4.5.earwar\plugins\amazon\dis
t\plugins-amazon.jar

BUILD SUCCESSFUL
Total time: 3 seconds

Checking for the output:

 Directory of C:\projects\confluence-2.4.5.earwar\plugins\amazon\classes\com\atl
assian\confluence\extra\amazon

04/22/2007  10:12 AM    <DIR>          .
04/22/2007  10:12 AM    <DIR>          ..
04/22/2007  10:12 AM             6,735 AmazonAsinMacro.class
04/22/2007  10:12 AM             1,174 Product$Image.class
04/22/2007  10:12 AM             1,084 Product$Review.class
04/22/2007  10:12 AM             2,474 Product.class
               4 File(s)         11,467 bytes

 Directory of C:\projects\confluence-2.4.5.earwar\plugins\amazon\dist

04/22/2007  10:12 AM             8,017 plugins-amazon.jar

How to get Confluence to see the new plugin

Copy the plugins-amazon.jar to ${confhome}\confluence\WEB-INF\lib

Discovering how to use the macro

Unfortunately there is no documentation included with this plugin. So I browsed the plugins\amazon directory, and discovered an error message in the source, it's in [AmazonAsinMacro].java, in the execute routine:

"Could not find an ASIN to look up. Example usage: {asin:0375501843}"

Adding a call to the macro to a wiki page

Then put {asin:1594031878} on a page. In my case I put it on the demonstration space homepage: http://localhost:9090/confluence/display/ds/Confluence+Overview

Finding a Bug in the Amazon Plugin Output

You'll see a picture of the book, and it's title, and it's price. But the links to the book are broken! They are given as: http://localhost:9090/confluence/display/ds/$product.url

In the HTML, you'll see just $product.url This means that Velocity thinks the variable is undefined.

First Step - Understanding the Amazon Macro

Basic file structure of the source of a Confluence plugin

Under the plugins\amazon\src\java\com\atlassian\confluence\extra\amazon directory are the java source files:

  • Product.java
  • [AmazonAsinMacro].java
    There is also a velocity macro that handles rendering the macro output:
  • plugins\amazon\src\etc\com\atlassian\confluence\extra\amazon\product.vm
    There is also an xml file of properties at:
  • plugins\amazon\src\etc\atlassian-plugin.xml

Look at the atlassian-plugin.xml

I've said elsewhere that there was no readme for this macro. However, this file, which is involved in the building of the plugin, does supply some useful information. Here it is:

<atlassian-plugin name='Amazon Macro' key='confluence.extra.amazon'>
    <plugin-info>
        <description>Macros to retrieve and display information from amazon.com</description>
        <vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
        <version>1.0</version>
    </plugin-info>

    <macro name='asin' class='com.atlassian.confluence.extra.amazon.AmazonAsinMacro' key='asin'>
        <description>Display an Amazon product details by looking up its ASIN</description>
    </macro>
</atlassian-plugin>

Notice the name of the macro, name="asin". Unfortunately, there is no actual specific usage example here. Good thing we found the usage example in the error message in the java source code.

First look at rendering in a macro with Velocity

Take a look at product.vm: $product is followed by various fields such as $product.titlee, $product.mediumImage.url, $product.price, etc.

When the macro renders, we're seeing the price and title, but not the url. So let's take a look at the sources.

Routines in [AmazonAsinMacro].java

[AmazonAsinMacro].java has the main structure of the plugin. It extends [BaseMacro]. You'll see some responses for attributes of macros (all of these are public so they can be called from outside):

  • isInline - false
  • hasBody - no body, so it returns false
  • getBodyRenderMode - [RenderMode].NO_RENDER (it renders it's own HTML response?)

Then there is a routine that returns a string (another public routine):

  • execute
    • This is the heart of what happens when the macro is found on a page.
    • Does some error checking, creates a search object, reads a response from Amazon, then passes the response to toHtml to be turned in HTML.

Key to this routine's function is the contextMap object. It is loaded with items that are used in the rendering of the velocity macro product.vm

The rest of the routines are private, since they are just for internal use of the macro:

  • toHtml - Returns the HTML
  • toPrettyXml([HttpResponsee] response) - Not called by anything. It calls parse to get the response, and then parse it to create a Document object. It then uses an XMLOutputter to format the raw XML from Amazon into nice HTML. We'll rearrange it later.
  • parse - Gets a response and parses it into Document
  • makeSearchUrl - Creats the URL to make a request from Amazon services
  • getSubscriptionID - This returns the subscription key needed to access Amazon. You should get your own developers token for Amazon Web Services, and substitute it for the definition of MY_AMAZON_ID
  • setHttpRetrievalService - Not sure the purpose of this.

Understanding Product.java

Product.java contains the definition for the product class. It contains a class Image to present the information about the product images. There is also a class to represent the product review. Then there are accessor routines to return the various fields used in the product.vm velocity macro. Notice that these are all public routines that return Strings:

  • $product.title - getTitle
  • $product.by - getBy, returning Author, Artist, or Director.
  • $product.price - getPrice
  • $product.smallimage - an Image object returned by getSmallImage

Second Step - Changing The Macro To Produce Some Debugging Output

We'll add a routine to Product.java to provide a response for $product.url:

    public String getUrl()
    {
        return "http://www.amazon.com";
    }

We don't yet know where to find the product url in the amazon response. So we'll add it's response to the output of the macro.

Remember the toPrettyXml routine that takes an [HttpResponse] object, reads it, then parses it for a Document before pretty printing the Document object? Since we already have a Document object, we'll refactor this routine so it can pretty print directly from it. So replace the existing toPrettyXml with these routines:

    private String toPrettyXml(Document dom)
    {
        XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
        return "<pre>" + new XmlFormatter().format(
               GeneralUtil.escapeXml(outputter.outputString(dom)), 
                                     "xml") + "</pre>";
    }

    private String toPrettyXml(HttpResponse response) 
                               throws IOException, JDOMException
    {
        Document dom = parse(response);
        return toPrettyXml(dom);
    }

Now we need to add an object called "amazonresponse" to the contextMap. Put this after the call to add the product to the contextMap:

        contextMap.put("amazonresponse", toPrettyXml(dom));

Finally, let's change product.vm to display the information from the amazonresponse object. Add this line just before the final </div> of product.vm:

<hr>$amazonresponse<hr>

Avoiding the macro hanging Confluence

If you build the above macro, copy it to the Confluence lib directory, and then access the page with the macro, you'll find that the page never returns. You also won't be able to overwrite the amazon-plugin in the lib version with a new version, unless you first shutdown and then restart tomcat.

The problem turns out to be our new getUrl routine.

Return new String() instead of a naked string, like this:

    public String getUrl()
    {
        // return "http://www.amazon.com";
        return new String("http://www.amazon.com");
    }

Trying it

Build the macro, then copy it to the lib directory.

Now go back to the browser and refresh. You'll see that the picture and title now link to www.amazon.com.

Third Step - Using our debugging output to fix getUrl

We've also got the pretty printed contents of the amazon response, for example:

<?xml version="1.0" encoding="UTF-8"?>
<ItemLookupResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2005-10-05">
  <OperationRequest>
    <HTTPHeaders>
      <Header Name="UserAgent" Value="Confluence/2.4.5 (http://www.atlassian.com/software/confluence)" />
    </HTTPHeaders>
    <RequestId>0SQFCVKJKM5BH276JA6P</RequestId>
    <Arguments>
      <Argument Name="ItemId" Value="1594031878" />
      <Argument Name="Service" Value="AWSECommerceService" />
      <Argument Name="SubscriptionId" Value="1163JES54SFRXDA3J202" />
      <Argument Name="ResponseGroup" Value="Medium" />
      <Argument Name="Operation" Value="ItemLookup" />
    </Arguments>
    <RequestProcessingTime>0.0479350090026855</RequestProcessingTime>
  </OperationRequest>
  <Items>
    <Request>
      <IsValid>True</IsValid>
      <ItemLookupRequest>
        <ItemId>1594031878</ItemId>
        <ResponseGroup>Medium</ResponseGroup>
      </ItemLookupRequest>
    </Request>
    <Item>
      <ASIN>1594031878</ASIN>
      <DetailPageURL>http://www.amazon.com/gp/redirect.html%3FASIN=1594031878%26tag=ws%26lcode=xm2%26cID=2025
%26ccmID=165953%26location=/o/ASIN/1594031878%253FSubscriptionId=1163JES54SFRXDA3J202</DetailPageURL>
      <SalesRank>81818</SalesRank>
      <SmallImage>
        <URL>http://ec1.images-amazon.com/images/P/1594031878.01._SCTHUMBZZZ_V24550016_.jpg</URL>
        <Height Units="pixels">75</Height>
        <Width Units="pixels">50</Width>
      </SmallImage>
      <MediumImage>
        <URL>http://ec1.images-amazon.com/images/P/1594031878.01._SCMZZZZZZZ_V24550016_.jpg</URL>
        <Height Units="pixels">160</Height>
        <Width Units="pixels">107</Width>
      </MediumImage>
      <LargeImage>
        <URL>http://ec1.images-amazon.com/images/P/1594031878.01._SCLZZZZZZZ_V24550016_.jpg</URL>
        <Height Units="pixels">500</Height>
        <Width Units="pixels">333</Width>
      </LargeImage>
      <ImageSets>
        <ImageSet Category="primary">
          <SwatchImage>
            <URL>http://ec1.images-amazon.com/images/P/1594031878.01._SCSWATCHZZ_V24550016_.jpg</URL>
            <Height Units="pixels">30</Height>
            <Width Units="pixels">20</Width>
          </SwatchImage>
          <SmallImage>
            <URL>http://ec1.images-amazon.com/images/P/1594031878.01._SCTHUMBZZZ_V24550016_.jpg</URL>
            <Height Units="pixels">75</Height>
            <Width Units="pixels">50</Width>
          </SmallImage>
          <MediumImage>
            <URL>http://ec1.images-amazon.com/images/P/1594031878.01._SCMZZZZZZZ_V24550016_.jpg</URL>
            <Height Units="pixels">160</Height>
            <Width Units="pixels">107</Width>
          </MediumImage>
          <LargeImage>
            <URL>http://ec1.images-amazon.com/images/P/1594031878.01._SCLZZZZZZZ_V24550016_.jpg</URL>
            <Height Units="pixels">500</Height>
            <Width Units="pixels">333</Width>
          </LargeImage>
        </ImageSet>
      </ImageSets>
      <ItemAttributes>
        <Author>John Agresto</Author>
        <Binding>Hardcover</Binding>
        <DeweyDecimalNumber>956.70443</DeweyDecimalNumber>
        <EAN>9781594031878</EAN>
        <ISBN>1594031878</ISBN>
        <Label>Encounter Books</Label>
        <ListPrice>
          <Amount>2595</Amount>
          <CurrencyCode>USD</CurrencyCode>
          <FormattedPrice>$25.95</FormattedPrice>
        </ListPrice>
        <Manufacturer>Encounter Books</Manufacturer>
        <NumberOfItems>1</NumberOfItems>
        <NumberOfPages>202</NumberOfPages>
        <PackageDimensions>
          <Height Units="hundredths-inches">100</Height>
          <Length Units="hundredths-inches">900</Length>
          <Weight Units="hundredths-pounds">100</Weight>
          <Width Units="hundredths-inches">590</Width>
        </PackageDimensions>
        <ProductGroup>Book</ProductGroup>
        <PublicationDate>2007-03-25</PublicationDate>
        <Publisher>Encounter Books</Publisher>
        <Studio>Encounter Books</Studio>
        <Title>Mugged by Reality: The Liberation of Iraq and the Failure of Good Intentions</Title>
      </ItemAttributes>
      <OfferSummary>
        <LowestNewPrice>
          <Amount>1200</Amount>
          <CurrencyCode>USD</CurrencyCode>
          <FormattedPrice>$12.00</FormattedPrice>
        </LowestNewPrice>
        <LowestUsedPrice>
          <Amount>1200</Amount>
          <CurrencyCode>USD</CurrencyCode>
          <FormattedPrice>$12.00</FormattedPrice>
        </LowestUsedPrice>
        <TotalNew>31</TotalNew>
        <TotalUsed>11</TotalUsed>
        <TotalCollectible>0</TotalCollectible>
        <TotalRefurbished>0</TotalRefurbished>
      </OfferSummary>
      <EditorialReviews>
        <EditorialReview>
          <Source>Book Description</Source>
          <Content>John Agresto spent a little over nine months in Iraq.  His job, was to help Iraq rebuild its 
once highly regarded education system.  As he left Iraq, Agresto was asked by the Pentagon to write a few paragraphs 
for the future about this formative and transitional time; from those paragraphs Mugged by Reality was born.</Content>
        </EditorialReview>
      </EditorialReviews>
    </Item>
  </Items>
</ItemLookupResponse>

Look at [DetailPageURL]. That looks like a good choice to return for the url. So change Product.java getUrl routine to this:

    public String getUrl()
    {
        // return "http://www.amazon.com";
        // return new String("http://www.amazon.com");
        return item.getChildText("DetailPageURL", ns);
    }

Recompile, copy to the lib directory, then reload the page with the macro. Test the product link, and you should end up at that book's page on Amazon.com. Viola! Mission accomplished.

Fourth Step - Cleanup

We need to remove our debugging information. That's a very simple operation. Just go to the product.vm file, and remove this line:

<hr>$amazonresponse<hr>

Fifth Step - Extend the macro further

Some things we could do:

  • Display more of the information returned from Amazon
  • Display it in different ways
  • Add parameters to the macro to control more advanced functionality
  • Cache the AWS information
  • Add a product search capability
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.4.3 Build:#705 Mar 21, 2007) - Bug/feature request - Contact Administrators
Complete Wiki Notation Guide