Everything Maven Part 2 - Twitter Portlet

A few weeks ago I wrote about reasons why you might want to invest some time in learning and using maven: "Everything maven Part I". Now it's time to roll up our sleeves and get down into guts of maven. In this article, I'll describe how you can use Maven to build and deploy a WCI portlet using the IDK. The portlet will connect to Twitter and return all your followers' latest tweets.

Step 1: Install Maven

Here's a link to the official maven2 installation guide. Here are the steps I used to install on my windows laptop:

  • Download and install the Sun Java JDK 6 update 18. Here's the link: http://java.sun.com/javase/downloads/widget/jdk6.jsp.
  • Download the latest maven installation zip file from here http://maven.apache.org/download.html
  • Extract the zip and move 'apache-maven-2.2.1' into c:/Program Files/Apache Software Foundation/apache-maven-2.2.1
  • Create a windows environment variable named "M2_HOME" and pointed it to the maven installation directory: M2_HOME=c:Program FilesApache Software
    Foundationapache-maven-2.2.1
  • Create a windows environment variable named "M2_REPO" and point it to "C:Documents And Settingsdave.m2repository" (this will make more sense after you read the section below on repositories).
  • Make sure that the "JAVA_HOME" environment variable is set. For example, the JAVA_HOME environment variable on my windows laptop is set to C:Program FilesJavajdk1.6.0_18
  • Add the mvn command to the windows "PATH" environment variable like so: PATH=...;%M2_HOME%bin
  • Finally, Test to ensure maven was installed correctly. Open a Windows DOS Command Prompt, and type "mvn -version". If you see the following you should be good to go:
    C:UsersDave>mvn -version
    Apache Maven 2.2.1 (r801777; 2009-08-06 15:16:01-0400)
    Java version: 1.6.0_18
    Java home: c:Program Files (x86)Javajdk1.6.0_18jre
    Default locale: en_US, platform encoding: Cp1252
    OS name: "windows 7" version: "6.1" arch: "x86" Family: "windows"

Maven is installed. Let's take a break, here's an airplane landing with one wing:
http://www.youtube.com/watch?v=XZiP4NaeYrE

Step 1b: Get the code

Here's a Zip containing the complete portlet code: function1-twitter-portlet. Feel free to refer to this as I describe the steps to create a maven portlet project below. This zip contains a fully functionaly maven project. Extract the zip and open the pom.xml file. Add your twitter credentials to the file, and then open a command prompt. Change directories to wherever you extracted the zip and then run the following command:

mvn install

If maven is installed correctly, and your twitter credentials are valid, this command should download required jars from maven, run tests to make sure it can connect to twitter, and build a deploy-able portlet war file under directory named 'target'!

Step 2: Create Basic pom.xml

Now that Maven is installed, we need to create an empty Maven Project. A the bare minimum, a Maven Project is nothing more than a folder that contains a single file named 'pom.xml'. In Maven terminology, "POM" stands for "Project Object Model". Maven will look for a pom.xml file to discover everything it needs to know about our Twitter Portlet Java Project. Once you've used Maven for a few different projects, this will become second nature...I promise. Eventually you'll have a template pom.xml that you can simply copy into a new directory each time you'd like to create a new maven project.

First, we'll create a new directory named 'twitter-portlet' (or just follow along by browsing the code from the zip file). Next, create a new pom.xml file and copy in the following

<?xml version="1.0" encoding="UTF-8"?> <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.function1</groupId> <artifactId>wci-twitter-portlet</artifactId> <packaging>war</packaging> <version>0.1</version> <name>WebCenter Interation Twitter Portlet</name> </project> 

So far, we haven't done anything more than describe our Twitter Portlet Project to Maven. Here's a short overview of each line:

  • The "modelVersion" is required by all Maven2 Projects and always has the value 4.0.0.
  • The "groupidId" tells Maven that this project belongs in the "com.function1" group. The best practice is to use your company's name for the groupid, but it can be whatever you choose. Multiple projects (multiple pom.xml files) can all share the same group id.
  • The "artifactId" defines the name of our specific project: wci-twitter-portlet.
  • "packaging" tells maven what type of project we're building. 99% of the time, this is either going to be "war" or "jar". In this case, of course, we want a war to deploy as a remote portlet.
  • The "version" tag defines what version of the wci-twitter-portlet we're working on. In this case, its the first version ever so I chose to call it version "0.1".
  • The "name" is just a human readable name. Maven will use this when generating documentation.

Step 3: Directory Structure

Maven is very particular about the directory structure of a project. If this is the first time you've used Maven, this might make you feel a bit trapped. When I first started with Maven I tried to fight it by using my own directory structure. Over time, I've come to see the light. Trust me, just go with it. I've realized that the directory structure used by Maven is a proven best practice and in the end it really makes things consistent and easy. Here's what the basic directory structure looks like:

  • Create a directory named src/main/java. This is where we will put custom java code.
  • Next create a directory named src/test/java. This is where unit test code goes.
  • Now create a directory named src/main/resources. Here's were all your configuration files go. For example: DTD, .properties files, xml files (such as log4j.xml), etc.
  • Finally, create a directory named src/main/webapps. Here's where your jsp's, and WEB-INF/web.xml file, and any static stuff like images, and javascript.

Ok, directory structure is done, Time for a break! I think the things that this guy builds are freaking cool:
http://www.youtube.com/watch?v=WcR7U2tuNoY&feature=related

Step 4: Generate an Eclipse Project

By this point, we've given maven enough information to be able to create an Eclipse project for us. switch to your command prompt and run the following command:

mvn eclipse:eclipse

You should see a message "BUILD SUCCESFUL". Open Eclipse and choose "File -> Import -> Existing Projects into Workspace". Choose the twitter-portlet directory and import the wci-twitter-portlet eclipse project (notice Maven used the artifactid for the name of the eclipse project).

Step 5: IDK and DEPENDENCIES

Our Twitter Portlet will need to use the IDK to get the current username. We'll need to depend on the WCI IDK. So, the next step is to download the IDK (if you haven't yet). Once it's downloaded, extract the installation zip and then find the WEB-INF directory under idk/10.3.0/devkit. Copy the WEB-INF folder into the wci-twitter-portlet directory into src/main/webapp/.

Now, delete the lib directory! Yep, you heard me. Just like taking off a bandaid, rip that lib directory out of there. After reading this article, you'll never have to copy the idk lib directory again (ok, except maybe when they upgrade to 11g). Now it's time to tell Maven to manage these jars for us.

Maven Repositories

Which leads us to the concept of Maven "repositories". Maven looks for jar dependencies inside repositories. The Maven Book does a much better job at describing the concept than I can, but basically, a repository is simply directory structure containing maven artifacts.

By default, Maven looks first for dependencies here: http://repo1.maven.org/maven2/

Thanks to convention over configuration, everything inside the repository is organized using the following structure:

groupId/artifactId/version/artifactId-version.ext

For example, log4j, version 1.2.9 can be found here:

http://repo1.maven.org/maven2/log4j/log4j/1.2.9/.

In this case, groupid and artifactid are both set to 'log4j'. Here's another example, 'axis' can be found here:

http://repo2.maven.org/maven2/org/apache/axis/axis/1.4/

There are several tools available to help search the maven repository for stuff. There's a Maven-Eclipse plugin that allows you to search and automatically add dedpendencies to a maven project. There's also a website that allows you to search the maven repository: http://mvnrepository.com/. For example, when you do a search for 'log4j' this site will show you all available versions. If you drill down into version 1.2.15, you'll see the following:

<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency> 

I know, I know, xml makes my eyes glaze over too. To be honest, I'm not crazy about the fact that maven relies 100% on xml. When you write java code, you gotta deal with xml, what can I say?

So, if we added this snippet of xml to or pom.xml, maven will know that our project uses log4j. In fact, maven will be so nice as to find and download the appropriate log4j jar and make it available on the classpath of our project.

Maven is smart - as smart as they come. Instead of downloading jars every time you do a build, it only downloads the jars once. And instead of downloading the same jars into every maven project, it downloads them into a directory under your operating system user's home directory. For example, on windows, this is C:Documents and SettingsDave.m2repository. On mac, this is /Users/Dave/.m2/repository.

Ok, that was a lot. Here's a quick summary: The Maven repository located at http://repo2.maven.org is a remote repository. When you tell maven that you need log4j by including the xml snippet above, it will first check your local maven repository (which is under your os user's home directory). If it can't find the jar, it will then check http://repo1.maven.org/maven2/.

Dealing with IDK Jars

So, the problem is that none of the idk jars are available in either of those places. To solve this, we'll need to copy the idk jars into a local maven repository of some sort.

The first step in doing so, is to re-arrange the jars inside the idk's lib directory to match the directory structure that maven expects. Like so:

Next, we need to tell Maven to look at our new local directory repository in addition to the default repo at http://repo2.maven.org/maven2. Here's the snippet of xml inside pom.xml that does so:

<repositories> <repository> <id>thirdparty</id> <name>Thirdparty Jars</name> <url>file://${basedir}/thirdparty/m2/repository</url> <layout>default</layout> </repository> </repositories> 

Now that the idk dependencies are available, lets add them to our project. We're going to add a snippet of xml for each jar. Here's a few examples to give you an idea:

 <dependencies> <dependency> <groupId>com.oracle.idk</groupId> <artifactId>activation</artifactId> <version>10.3</version> </dependency> <dependency> <groupId>com.oracle.idk</groupId> <artifactId>axis</artifactId> <version>10.3</version> </dependency> <dependency> <groupId>com.oracle.idk</groupId> <artifactId>commons-discovery</artifactId> <version>10.3</version> </dependency> ... more depenedencies ... </dependencies> 

Phwew! We're getting to the end...before we do, here's a old spice commercial that cracks me up:
http://www.youtube.com/watch?v=owGykVbfgUE

Step 6: Write the Code!

At this point, we have everything to need to create a portlet that uses the IDK. Please take a look inside the zip to see the code. But why stop there? Let's add some twitter! It just so happens that there's a library called twitter4j available via maven:

 <dependency> <groupId>org.twitter4j</groupId> <artifactId>twitter4j-core</artifactId> <version>2.1.0</version> </dependency> 

Run mvn eclipse:eclipse again to update your eclipse project with the new twitter4j jar. (If Eclipse is open while you run this command, remember to do a "refresh" in Eclipse so that Eclipse see's the new jar)

Now, we can add some code to our portlet that uses twitter4j to retrieve the latest status from each twitter friend. Here's the relevant snippet. Feel free to peruse the full source int he zip file:

 public List<String> getFriendsMostRecentTweet() { List<String> tweets = new ArrayList<String>(); String senderID = getTwitterLogin(); String senderPassword = getTwitterPassword(); Twitter twitter = new TwitterFactory().getInstance(senderID, senderPassword); PagableResponseList<User> statuses; try { statuses = twitter.getFriendsStatuses(); for (User nextTwitterUser : statuses) { tweets.add(nextTwitterUser.getName() + ": " + nextTwitterUser.getStatusText()); } } catch (TwitterException e) { System.out.println("Unable to retrieve tweets"); return null; } return tweets; } 

Finally: Running the command "mvn install" will create a war file named wci-twitter-portlet-0.1.war under the target directory. Deploy that war to tomcat (or weblogic or whatever you're using as a remote java portal server). Create the remote server, webservice, and portlet objects inside the WCI Portlet and marvel at the twitter messages!

Stay In Touch