• ALL
  • CATEGORIES

Using WebCenter Sites with Dropwizard and AngularJS, Part 1

As WebCenter Sites has become part of a larger ecosystem of Oracle products the type of projects that I’ve worked on have become more service-y or integration lead.

This tutorial in two parts looks at how we can use Sites in conjunction with Dropwizard and then AngularJS to deliver a standalone webservice.

In the first part we’ll look at integrating WebCenter Sites with Dropwizard.

Introduction to Dropwizard

Dropwizard is a project that grew out of development work at Yammer. In the project’s own words:

Its goal is to provide performant, reliable implementations of everything a production-ready web service needs. Because this functionality is extracted into a reusable library, your service remains lean and focused, reducing both time-to-market and maintenance burdens.

What this means in practice is that it provides the glue to tie together the following components:

  • Jetty – an http library that removes the need for a container
  • Jersey – the jax-rs reference implementation
  • Jackson – for working with json
  • Metrics – a yammer library for providing analytics

It also means that the artifacts we produce will be an executable jar and a configuration file – and that’s all we’ll need to run our webservice.

Getting Started

To start with you’ll need the following installed:

  • Local instance of WebCenter Sites
  • Eclipse
  • Maven

Now you can either clone this project from GitHub or you can follow along with the tutorial and create the project yourself.

First lets look at our POM:

<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>uk.co.manifesto.wcs</groupId>
  <artifactId>dropwizard-wcs</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>dropwizard-wcs</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <!-- dropwizard -->
    <dependency>
        <groupId>com.yammer.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>0.6.2</version>
    </dependency>  
    <dependency>
        <groupId>com.yammer.dropwizard</groupId>
        <artifactId>dropwizard-client</artifactId>
        <version>0.6.2</version>
    </dependency>      
    <!-- sites -->
    <dependency>
        <groupId>com.fatwire.wem.sso.cas</groupId>
        <artifactId>wem-sso-api-cas</artifactId>
        <version>11.1.1.8.0</version>   
    </dependency>
    <dependency>
        <groupId>com.fatwire.wem.sso</groupId>
        <artifactId>wem-sso-api</artifactId>
        <version>11.1.1.8.0</version>   
    </dependency>   
    <dependency>
        <groupId>com.fatwire.wem.api</groupId>
        <artifactId>rest-api</artifactId>
        <version>11.1.1.8.0</version>   
    </dependency>
    <!-- spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>3.2.4.RELEASE</version>
    </dependency>   
    <!-- cas -->
    <dependency>
        <groupId>org.jasig.cas</groupId>
        <artifactId>cas-client-core</artifactId>
        <version>3.1.9</version>
    </dependency>           
    <!-- test -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.0.2</version>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>1.6</version>
            <configuration>
                <createDependencyReducedPom>true</createDependencyReducedPom>
                <filters>
                    <filter>
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <mainClass>uk.co.manifesto.wcs.dropwizard.service.ArticleService</mainClass>
                            </transformer>
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <archive>
                    <manifest>
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>  
  </build>
</project>

It’s quite big this POM so we’ll take it bit by bit.

To start with there are some WCS dependencies that are in Maven Central so we’ll need to add them to our local repository.

(Those who completed our Rest API tutorials will find this step familiar.)

The easiest way to do this to navigate to the lib directory of the cs web application on the command line and run the following mvn commands

mvn install:install-file -Dfile=wem-sso-api-cas-11.1.1.8.0.jar -DgroupId=com.fatwire.wem.sso.cas -DartifactId=wem-sso-api-cas -Dversion=11.1.1.8.0 -Dpackaging=jar -DgeneratePom=true

mvn install:install-file -Dfile=wem-sso-api-11.1.1.8.0.jar -DgroupId=com.fatwire.wem.sso -DartifactId=wem-sso-api -Dversion=11.1.1.8.0 -Dpackaging=jar -DgeneratePom=true

mvn install:install-file -Dfile=rest-api-11.1.1.8.0.jar -DgroupId=com.fatwire.wem.api -DartifactId=rest-api -Dversion=11.1.1.8.0 -Dpackaging=jar -DgeneratePom=true

Then there’s the dropwizard dependencies.

As well as dropwizard-core we’ve also included dropwizard-client. This maven module provides us with, amongst other things, the Jersey Client that we’ll use to connect and talk to WebCenter Sites.

Maven Plugins

Finally we have some configuration for a couple of maven plugins.

The most interesting of these is the maven-shade-plugin. It provides us with a way to take all of the dependencies our project relies on and collapses them into one big jar. It also allows us to specify a main class for our jar that provides the entry point to our webservice.

Building the Application

In order to build a Dropwizard we need to create the following classes:

  • Client class – as we’re connecting to and utilising resources from WCS we need to create a client class to package up this functionality
  • Configuration class – this provides access to the values in our configuration file and provides the Dropwizard defaults
  • Representation class(es) – these classes describe the data we return in our API, Jackson uses these classes as part of its serialisation of JSON
  • Resource class – these are our Jax-RS classes. They describe the URIs and HTTP methods supported by our webservice
  • Health check class – our health check classes allow us to write tests to make sure that our webservice and the other services it relies on are behaving correctly
  • Service class – this class is the entry point for our application. It sets everything up and wires it together

Let’s get started with our client.

Client

Our Client for talking to WCS is very similar to the work we did in our previous Rest API tutorial so if you want to understand in more detail how it works you can go and have a quick read there, otherwise the pertinent methods are below:

public AssetsBean getAssets(String site, String assetType) {
    makeSureConnected();
    WebResource getResource = this.baseResource.path(String.format("/sites/%s/types/%s/search", site, assetType));
    Builder builder = getResource.accept(MediaType.APPLICATION_XML);
    return builder.get(AssetsBean.class);
}

public AssetBean getAsset(String site, String assetIdParam) {
    makeSureConnected();
    String[] params = assetIdParam.split(":");
    String assetType = params[0];
    String assetId = params[1];

    WebResource getResource = this.baseResource.path(String.format("/sites/%s/types/%s/assets/%s", site, assetType, assetId));
    Builder builder = getResource.accept(MediaType.APPLICATION_XML);
    return builder.get(AssetBean.class);
}

The method getAssets takes the name of an asset type as a parameter and returns a list of all the assets of that type in the form of an AssetsBean.

The method getAsset takes an parameter in the form {assetType}:{assetId} and returns an AssetBean that encapsulates the data for that particular asset.

It’s also worth having a quick look at our constructor as this gives us an indication of the data will need to store in our configuration class.

public WcsAssetClient(String baseUri, String username, String password) {
    this.client = new Client();
    this.baseUri = baseUri;
    this.baseResource = client.resource(baseUri + "REST");
    this.username = username;
    this.password = password;
}

So at the very least we’ll need to store

  • baseUri
  • username
  • password

and also, by the look of the other methods above

  • assetType
  • siteName

Configuration

Our configuration class really comes in two parts, the java class itself that contains our configurable properties and a YAML file that stores the runtime values for those properties.

Our YAML file looks like this

baseUri: http://localhost:9080/cs/
username: fwadmin
password: xceladmin
siteName: avisports
assetType: AVIArticle

This tells me that, as well as the authentication information, we’re going to make requests for the AVIArticle in the avisports site.

And our Configuration class looks like this

public class ArticleConfiguration extends Configuration{
    @NotEmpty
    @JsonProperty
    private String baseUri;

    @NotEmpty
    @JsonProperty
    private String username;

    @NotEmpty
    @JsonProperty
    private String password;

    @NotEmpty
    @JsonProperty
    private String siteName;

    @NotEmpty
    @JsonProperty
    private String assetType;

    … getters removed for brevity …

}

The @JsonProperty is a Jackson annotation we can use to negate the need to write setters for our properties and the @NotEmpty annotation is a hibernate validation annotation that lets us know that none of those properties can be empty.

Read more about the hibernate validation framework.

Representation

Now we could just reuse the AssetsBean and AssetBean classes that WebCenter Sites gives to return our JSON but that wouldn’t quite give us the separation we’re looking for with this project. So instead we’ll create our own representations of the article data.

We’ll create two separate beans for this: one to represent a list of articles and one to represent the article data itself.

This is what we want the output to look like for our list of articles. We’ll show an id, a name and a relative url to the resource itself

[
  {
    "id": 1328196047241,
    "name": "Veteran Skier Says Goodbye",
    "href": "/articles/1328196047241"
  }, {
    "id": 1328196047309,
    "name": "Rookie Skier Makes Her Mark",
    "href": "/articles/1328196047309"
  }
]

And this is what we want the output for the specific article to look like

{
    "id": 1328196047241,
    "headlineField": "Farewell Win Makes Crowe King of Snow City",
    "subheadlineField": "No better way to end career than with a big win",
    "abstractField": "Veteran skier says goodbye in fitting fashion at the Winter Festival in Vancouver with all of his fellow skiers on site to cheer him on.",
    "authorField": "SELENA GRAVES",
    "postDateField": "Thu Feb 02 14:07:39 GMT 2012",
    "bodyField": "<p>Two days after unexpectedly announcing that he would retire after this season, David Crowe on Saturday won his record-breaking fifth Henkin downhill, largely considered skiing’s most difficult and prestigious race.</p>"
}

With this in mind our representations look like this

package uk.co.manifesto.wcs.dropwizard.representation;

public class Article {

         private final long id;
        private final String headlineField;
        private final String subheadlineField;
        private final String abstractField;
        private final String authorField;
        private final String postDateField;
        private final String bodyField;

        public Article(long id, String headlineField,  String subheadlineField, String abstractField,String authorField,String postDateField, String bodyField) {
            this.id = id;
            this.headlineField = headlineField;
            this.subheadlineField = subheadlineField;
            this.abstractField = abstractField;
            this.authorField = authorField;
            this.postDateField = postDateField;
            this.bodyField = bodyField;

        }

        … getters removed for brevity …

}

and

package uk.co.manifesto.wcs.dropwizard.representation;

public class ArticleLink {

    private final long id;
    private final String name;
    private final String href;

    public ArticleLink(long id, String name, String href) {
        this.id = id;
        this.name = name;
        this.href = href;
    }

    … getters removed for brevity …
}

As we’re creating our own representations we’ll also need a class to map from the beans we get back from WCS to our versions.

This is what our implementation looks like

package uk.co.manifesto.wcs.dropwizard.mapper;

import java.util.Map;

import com.fatwire.rest.beans.AssetBean;
import com.fatwire.rest.beans.AssetInfo;
import com.fatwire.rest.beans.Attribute;
import com.fatwire.rest.beans.FieldInfo;
import com.google.common.collect.Maps;

import uk.co.manifesto.wcs.dropwizard.representation.Article;
import uk.co.manifesto.wcs.dropwizard.representation.ArticleLink;

  public class ArticleMapper {

    public static Article mapArticle(AssetBean asset) {
        Map<String,String> am = Maps.newHashMap();
        for (Attribute attr : asset.getAttributes()) {
            if (attr.getName().equals("postDate")) {
                am.put(attr.getName(), attr.getData().getDateValue().toString());
            } else {
                am.put(attr.getName(), attr.getData().getStringValue());
            }
        }
        String assetId [] = asset.getId().split(":");

        return new Article(Long.parseLong(assetId[1]),
                                    am.get("headline"),
                                    am.get("subheadline"),
                                    am.get("abstract"),
                                    am.get("author"),
                                    am.get("postDate"),
                                    am.get("body")
                        );
    }

    public static ArticleLink mapArticleLink(AssetInfo assetInfo) {
        String [] assetTypeParams = assetInfo.getId().split(":");
        String assetId = assetTypeParams[1];
        String href = String.format("/articles/%s",assetId);
        String name = getValueFromFieldInfo(assetInfo, "name");

        return new ArticleLink(Long.parseLong(assetId),name,href);  
    }

    private static String getValueFromFieldInfo(AssetInfo assetInfo, String fieldName) {
        String fieldValue = "";
        for (FieldInfo fieldInfo : assetInfo.getFieldinfos()) {
            if (fieldInfo.getFieldname().equals(fieldName)) {
                fieldValue = fieldInfo.getData().toString();
            }
        }
        return fieldValue;
    }

}

Resource

Next we’ll build our JAX-RS resource. Our resource needs access to our Client and our Mapper to return the Representations we’ve just created.

Our constructor looks like this

private WcsAssetClient client;
private String siteName;
private String assetType;

public ArticleResource(WcsAssetClient client, String siteName, String assetType) {
    this.client = client;
    this.siteName = siteName;
    this.assetType = assetType;
}

and our two GET methods look like this

@GET
@Timed
public List<ArticleLink> getAllArticles() {
    List<ArticleLink> articleLinks = Lists.newArrayList();
    for (AssetInfo assetInfo : client.getAssets(siteName, assetType).getAssetinfos()) {
        articleLinks.add(ArticleMapper.mapArticleLink(assetInfo));
    }
    return articleLinks;
}

@GET
@Path("{articleId}")
@Timed
public Article getArticle(@PathParam("articleId") String articleId) {
    AssetBean asset = client.getAsset(siteName, String.format("%s:%s",assetType, articleId));
    return ArticleMapper.mapArticle((asset));

}

The first returns a List of ArticleLinks and the second just an Article.

Of interest here is the @Timed annotation. This is a Dropwizard annotation that means that every time the method is called its execution is timed and the statistics are stored for future analysis.

Healthcheck

Dropwizard is quite particular about Healthchecks. So much so that if we don’t create one it will nag us until we do.

So without further ado here is our implementation. It simply checks to see that we can connect to WCS.

public class WcsHealthCheck extends HealthCheck {
    private final WcsAssetClient client;

    public WcsHealthCheck(WcsAssetClient client) {
        super("client");
        this.client = client;
    }

    @Override
    protected Result check() throws Exception {
        if (client.canConnect()) {
            return Result.healthy();
        }
        return Result.unhealthy("Can't connect to WCS please check the logs");
    }
}  

As you can see it’s a pretty simple class – we just to have to extend the HealthCheck class and override the check() method returning a Result class that describes our current state of health.

Service

We’re almost there and in fact the last thing we need to do is wire everything we’ve done together in a Service class

public class ArticleService extends Service<ArticleConfiguration> {

    public static void main(String[] args) throws Exception {
        new ArticleService().run(args);
    }

    @Override
    public void initialize(Bootstrap<ArticleConfiguration> bootstrap) {
        bootstrap.setName("article");
    }

    @Override
    public void run(ArticleConfiguration configuration, Environment environment) {

        final WcsAssetClient client = new WcsAssetClient(configuration.getBaseUri(), configuration.getUsername(), configuration.getPassword());
        environment.addResource(new ArticleResource(client, configuration.getSiteName(), configuration.getAssetType()));
        environment.addHealthCheck(new WcsHealthCheck(client));

    }

}

Here you can see how we use the Configuration to create our client and then how we pass that instance to our Resource and our HealthCheck.

Now if we build our project by running mvn clean package we should get a jar in the target directory called something like dropwizard-wcs-0.0.1-SNAPSHOT.jar

We can now start our service by executing the jar

java -jar target/dropwizard-wcs–0.0.1-SNAPSHOT.jar server articles.config

Where server is a dropwizard command and articles.config is our YAML file.

You should see some output like this

INFO  [2013-11-08 19:09:10,432] com.yammer.dropwizard.cli.ServerCommand: Starting article
INFO  [2013-11-08 19:09:10,436] org.eclipse.jetty.server.Server: jetty-8.y.z-SNAPSHOT
INFO  [2013-11-08 19:09:10,528] com.sun.jersey.server.impl.application.WebApplicationImpl: Initiating Jersey application, version 'Jersey: 1.17.1 02/28/2013 12:47 PM'
INFO  [2013-11-08 19:09:10,590] com.yammer.dropwizard.config.Environment: The following paths were found for the configured resources:

    GET     /articles (uk.co.manifesto.wcs.dropwizard.resource.ArticleResource)
    GET     /articles/{articleId} (uk.co.manifesto.wcs.dropwizard.resource.ArticleResource)

INFO  [2013-11-08 19:09:10,590] com.yammer.dropwizard.config.Environment: tasks = 

    POST    /tasks/gc (com.yammer.dropwizard.tasks.GarbageCollectionTask)

INFO  [2013-11-08 19:09:10,838] org.eclipse.jetty.server.AbstractConnector: Started InstrumentedBlockingChannelConnector@0.0.0.0:8080
INFO  [2013-11-08 19:09:10,843] org.eclipse.jetty.server.AbstractConnector: Started SocketConnector@0.0.0.0:8081

And now you can test the service by making requests to “http://localhost:8080/articles”

In Part 2 we’ll look at adding a simple GUI using AngularJS.

Leave a reply

You can use these tags:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

  1. Cemal says:

    Very useful Patrick. Thank you.

    Would this also work with Fatwire 7.6.2 REST API?

    We are hoping to utilise this approach potentially.

  2. Manasvi says:

    Very nice… Very helpful… Thank u

Sign up for the Manifesto newsletter and exclusive event invites