Hello~

January 6, 2010

Welcome everyone!

欢迎!

Yammer Express – A RESTful service client side implementation

January 6, 2010

With the growing popularity of Yammer usage in SunGard, Techie Solomon asked me if I could do a small standalone program to perform a GET to request SunGard yammers’ posts. The other purpose is to present how easy a client implementation is for a RESTful style service. So I went ahead.

Yammer API Doc provides its OAuth authentication steps and all the presentations and data formats we need.

  • register your application to get customer key and secret
  • get request token and request secret
  • user authorizes and get oauth verifier
  • extract data with request token and verifier

I used maven for build, here is the pom.xml file.

<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.yammer.sample</groupId>
	<artifactId>yammerRESTExpress</artifactId>
	<packaging>jar</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>Maven Quick Start Archetype</name>
	<url>http://maven.apache.org</url>

	<dependencies>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jaxrs</artifactId>
			<version>1.2.GA</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jaxb-provider</artifactId>
			<version>1.2.GA</version>
		</dependency>
		<dependency>
			<groupId>net.sourceforge.htmlunit</groupId>
			<artifactId>htmlunit-core-js</artifactId>
			<version>2.5</version>
		</dependency>
		<dependency>
			<groupId>net.sourceforge.htmlunit</groupId>
			<artifactId>htmlunit</artifactId>
			<version>2.5</version>
		</dependency>
	</dependencies>

</project>

RESTEasy is used to provide xml parsing for binded model object (you could choose JDOM as well).

I used htmlunit to imitate a browser action for user to log in and issue rights to YammerExpress to fetch yammer posts, so everything happened in background. The other way could be to use java.awt.Desktop to open this url and then using java console to take user’s input as verifier for further requests.

Message response object:

@XmlRootElement(name = "response")
public class YammerMessagesResponse {
	@XmlElementWrapper(name = "messages")
	@XmlElement(name = "message")
	public List<Msg> messageList;
}

class Msg {
	public String id;

	@XmlElement(name = "web-url")
	public String webUrl;

	@XmlElement(name = "body")
	public Body body;
}

class Body {
	public String plain;
}

Simple client program:

public class YammerClient {

	final static String CONSUMER_KEY = "consumer key";
	final static String CONSUMER_SECRET = "consumer secret";
	final static String REQUEST_TOKEN_URL = "https://www.yammer.com/oauth/request_token";
	final static String LOGIN_URL = "https://www.yammer.com/login";
	final static String AUTHORIZE_URL = "https://www.yammer.com/oauth/authorize?oauth_token=";
	final static String ACCESS_TOKEN_URL = "https://www.yammer.com/oauth/access_token";
	final static String SEARCH_URL = "https://www.yammer.com/api/v1/messages/";

	final static String USER = "USER@company.com";
	final static String PASSWORD = "PASSWORD";

	public static void main(String args[]) throws Exception {
		// get request token and token secret
		String[] returnValues = extractToken(REQUEST_TOKEN_URL, "POST", null,
				null, null, false);

		// user verify
		final WebClient webClient = new WebClient();
		HtmlPage page = webClient.getPage(LOGIN_URL);
		HtmlForm loginForm = page.getForms().get(0);
		loginForm.getInputByName("login").setValueAttribute(USER);
		loginForm.getInputByName("password").setValueAttribute(PASSWORD);
		HtmlSubmitInput button = loginForm.getInputByName("commit");
		button.click();

		page = (HtmlPage) webClient.getPage(new URL(AUTHORIZE_URL
				+ returnValues[0]));
		HtmlForm form = page.getForms().get(1); // authorize_app_form

		page = form.getInputByName("commit").click();

		String verifier = page.getElementById("callback-token")
				.getTextContent();

		// get access token
		returnValues = extractToken(ACCESS_TOKEN_URL, "POST", returnValues[0],
				returnValues[1], verifier, false);

		// get a list of messages
		extractToken(SEARCH_URL, "GET", returnValues[0], returnValues[1],
				returnValues[2], true);

	}

	private static String[] extractToken(String requestUrl,
			String requestMethod, String token, String tokenSecret,
			String verifier, Boolean isSearch) throws Exception {
		URL url = new URL(requestUrl);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setDoInput(true);
		if (requestMethod != null) {
			conn.setRequestMethod(requestMethod);
			conn.setRequestProperty("Authorization", composeOauthHeader(token,
					tokenSecret, verifier));
		}

		try {
			if (isSearch) {
				YammerMessagesResponse msg = ReaderUtility.read(
						YammerMessagesResponse.class,
						MediaType.APPLICATION_XML_TYPE, conn.getInputStream());
				for (Msg aMsg : msg.messageList) {
					System.out.println(aMsg.id + " - " + aMsg.webUrl + " - "
							+ aMsg.body.plain);
				}

			} else {
				BufferedReader in = new BufferedReader(new InputStreamReader(
						conn.getInputStream()));
				String str = "";
				while ((str = in.readLine()) != null) {
					token = str.split("&")[0].substring(str.split("&")[0]
							.indexOf("=") + 1);
					tokenSecret = str.split("&")[1].substring(str.split("&")[1]
							.indexOf("=") + 1);
				}
			}

		} catch (IOException e) {
			e.printStackTrace();

		} finally {
			conn.disconnect();
		}
		return new String[] { token, tokenSecret, verifier };
	}

	private static String composeOauthHeader(String token, String tokenSecret,
			String verifier) {

		StringBuffer buff = new StringBuffer();
		buff.append("OAuth realm=\"");
		buff.append("\", oauth_consumer_key=\"");
		buff.append(CONSUMER_KEY);
		buff.append("\", ");

		if (token != null) {
			buff.append("oauth_token=\"");
			buff.append(token);
			buff.append("\", ");
		}

		buff.append("oauth_signature_method=\"");
		buff.append("PLAINTEXT");
		buff.append("\", oauth_signature=\"");
		buff.append(CONSUMER_SECRET);
		buff.append("%26");
		if (tokenSecret != null) {
			buff.append(tokenSecret);
		}
		buff.append("\", oauth_timestamp=\"");
		buff.append(new GregorianCalendar().getTime());
		buff.append("\", oauth_nonce=\"");
		buff.append(new GregorianCalendar().getTime());

		if (verifier != null) {
			buff.append("\", ");
			buff.append("oauth_verifier=\"");
			buff.append(verifier);
		}
		buff.append("\", oauth_version=\"1.0\"");
		return buff.toString();
	}

}

After execution, we could get result set looking like this:

28510011 - https://www.yammer.com/sungard.com/messages/28510011 - Is anybody looking at Adobe Lifecycle Mosaic (other than the Banks segment) for creating "contextual workspaces"? http://www.adobe.com/products/livecycle/mosaic/
28509644 - https://www.yammer.com/sungard.com/messages/28509644 - http://code.google.com/p/flex-ui-selenium/
28509631 - https://www.yammer.com/sungard.com/messages/28509631 - Haven't really used it yet but my understanding is that it doesn't do real UI testing. So it's only useful for business or integration logic.
28509098 - https://www.yammer.com/sungard.com/messages/28509098 - This could come in very handy for testing Flex applications: http://opensource.adobe.com/wiki/display/flexunit/FlexUnit;jsessionid=8C6B91F02A0534399140FF1110E55C8B #yam #flex Curious to see how well it works
28508680 - https://www.yammer.com/sungard.com/messages/28508680 - Judith - just read your message. i will reach out to you
28507739 - https://www.yammer.com/sungard.com/messages/28507739 - Most responses have referred to the Infinity Platform. I know I can re-write to that platform. What I was hoping to find was someone that captured the economics of the process. I'll send a note to Ron Lang as well. Kindest regards, Gil
28507643 - https://www.yammer.com/sungard.com/messages/28507643 - Make the best of it: People stuck in Newark airport break into 'Hey Jude" http://www.nbcnewyork.com/news/local-beat/Dont-Be-Afraid-During-Newark-Scare-Passengers-Break-into-Hey-Jude-80716192.html
28504644 - https://www.yammer.com/sungard.com/messages/28504644 - has #joined the SunGard network.

Yammer provides a RESTful interface and its individual resources are identified in requests – web URIs. So as long as a client has a resource’s representation (usually in the format of xml), it could easily perform different actions (adding, fetching, deletion etc) on the server, provided it has permission to do so.