Google App Engine Java + Restlet + Zillow

I’ve written a simple tiny test application to test out Restlet with Zillow web services on Google App Engine Java platform. You can test the application using this url:

http://agentlatif.appspot.com/resources/zillow/searchresults/78717/TX/Austin/11100%20Shalloow%20Water%20Rd

There are three main Java files in this application. I’ll briefly describe their main roles without getting into detail. Source code for all three java files along with web.xml file is attached for viewers.

RestletMain.java

RestletMain.java basically routes a URL to invoke ZillowResource java class. This is the main application entry point which extends org.restlet.Application. The routing uri I constructed may not be adherring to true REST style here. I’m learning REST and Restlet so please pardon my weak REST URI definition. More information about Restlet + GAE/J can be found here.

<pre>package com.latifrealtor;

import org.restlet.Application;
import org.restlet.Restlet;
import org.restlet.routing.Router;

import com.latifrealtor.education.EducationResource;
import com.latifrealtor.zillow.ZillowResource;

public class RestletMain extends Application {

	/**
	 * Creates a root Restlet that will receive all incoming calls.
	 */
	@Override
	public synchronized Restlet createRoot() {
		Router router = new Router(getContext());
		router.attach("/zillow/searchresults/{zip}/{state}/{city}/{street}", ZillowResource.class);
		return router;
	}
}

ZillowResource.java

This java class makes java.net call to Zillow’s REST based Zestimate web services call and retrieves house valuation estimates. This class is also parsing URL to extract out address information that is needed to make a successful Zillow call. Response from Zillow web services is parsed using a helper class called “ZillowXMLParser.java” and returned to any HTTP Client as JSON string.

package com.latifrealtor.zillow;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.logging.Logger;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;

public class ZillowResource extends ServerResource {
	private static final Logger log = Logger.getLogger(ZillowResource.class.getName());

	@Get
	public String getProcessor() {
		try {
			String street = this.getRequest().getAttributes().get("street").toString();
			String city = this.getRequest().getAttributes().get("city").toString();
			String state = this.getRequest().getAttributes().get("state").toString();
			String zip = this.getRequest().getAttributes().get("zip").toString();
			if(street.isEmpty() || city.isEmpty() || state.isEmpty() || zip.isEmpty()){
				throw new Exception("Street or City or State or Zip was not provided");
			}
			String responseStream = getSearchResults(street, city+","+state+","+zip);
			String zestimate = new ZillowXMLParser().getEstimate(responseStream);
			return zestimate;
		} catch (Exception e) {
			log.warning(e.getMessage());
			return e.getMessage();
		}
	}

	public String getSearchResults(String pAddress, String pCityStateZip) {
		StringBuffer sbf = new StringBuffer();
		try {
			String zUrl = "http://www.zillow.com/webservice";
			String zWS = "/GetSearchResults.htm?";
			String zwsid = "zws-id=" + "<< <em>YOUR ZILLOW API ID</em> >>";
			String address = "&address=" + (pAddress);
			String citystatezip = "&citystatezip=" + URLEncoder.encode(pCityStateZip, "UTF-8");

			String fullUrl = zUrl + zWS + zwsid + address + citystatezip;
			log.warning("Full Url : " + fullUrl);

			// String encodedURL = URLEncoder.encode(fullUrl, "UTF-8");
			URL url = new URL(fullUrl);

			try {
				// URL url = new URL("http://ww2.iparissa.com/shaeeta");
				BufferedReader reader = new BufferedReader(
						new InputStreamReader(url.openStream()));
				String line;

				log.warning("Reading lines from reader.readLine()");
				while ((line = reader.readLine()) != null) {
					//log.warning(line.toString());
					sbf.append(line.toString());
				}
				reader.close();
				return sbf.toString();
			} catch (MalformedURLException e) {
				log.warning(e.getMessage());
				return (e.getMessage());
			} catch (IOException e) {
				log.warning(e.getMessage());
				return (e.getMessage());
			}

		} catch (Exception e) {
			log.warning(e.getMessage());
			return (e.getMessage());
		}
	}
}

ZillowXMLParser.java

This is a helper class to parse XML response from Zillow. We ran into problem using javax.xml.parsers package and found a solution from Google App Engine Java community forum here.  This java class basically parse Zillow response and returns a JSON formatted string.

package com.latifrealtor.zillow;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.logging.Logger;

import org.apache.xerces.impl.xpath.XPath;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.*;
import javax.xml.xpath.*;

public class ZillowXMLParser {
	private static final Logger log = Logger.getLogger(ZillowXMLParser.class.getName());

    public String getEstimate(String zillowXMLResponseStream)
            throws ParserConfigurationException, SAXException,
            IOException, XPathExpressionException, Exception {

        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(false); // never forget this!
        DocumentBuilder builder = domFactory.newDocumentBuilder();

        Document doc = null;
        try {
            if (!zillowXMLResponseStream.isEmpty()){ //check if respones is empty or not
                //Important to note:
                // if you use String on builder.parse(string) it will throw an error.
                InputStream is = new ByteArrayInputStream(zillowXMLResponseStream.getBytes("UTF-8"));
                doc = builder.parse(is);
            }else {
                throw new Exception("zillow response xml is empty");
            }

            // GAEJ Bug fix:
            // We need to use full class path to instantiate XPathFactoryImpl(), and
            // we also needed xalan-j_2_7_0.jar JAR to be placed in WEB-INF/lib folder to make Xalan work in GAE/J
            XPathFactory factory = new org.apache.xpath.jaxp.XPathFactoryImpl();

            XPathExpression expr = factory.newXPath().compile("//zestimate/amount/text()");
            String zestimateDollarAmount = (String) expr.evaluate(doc, XPathConstants.STRING);
            log.warning(zestimateDollarAmount);

            XPathExpression exprLastUpdated = factory.newXPath().compile("//zestimate/last-updated/text()");
            String zestimateLastUpdated = (String) exprLastUpdated.evaluate(doc, XPathConstants.STRING);
            log.warning(zestimateLastUpdated);

            XPathExpression exprValueLow = factory.newXPath().compile("//zestimate/valuationRange/low/text()");
            String zestimateValueLow = (String) exprValueLow.evaluate(doc, XPathConstants.STRING);
            log.warning(zestimateValueLow);

            XPathExpression exprValueHigh = factory.newXPath().compile("//zestimate/valuationRange/high/text()");
            String zestimateValueHigh = (String) exprValueHigh.evaluate(doc, XPathConstants.STRING);
            log.warning(zestimateValueHigh);

            String jsonReturn = "{"zestimateDollarAmount" : "" + zestimateDollarAmount +"" , " + ""zestimateLastUpdated" : "" + zestimateLastUpdated + """
        	+ ", "zestimateValueLow" : "" + zestimateValueLow +"" , " + ""zestimateValueHigh" : "" + zestimateValueHigh + """
        	+ "}";
            log.warning(jsonReturn);
            return jsonReturn;
        } catch (IllegalArgumentException iae) {
            log.warning(iae.getMessage());
            throw new Exception(iae);
        } catch (Exception e){
        	log.warning(  e.getMessage());
            throw new Exception("Error at FlickrImageBuilder " , e);
        }
    }
}

Web.xml

I’m attaching web.xml to provide help viewer configure their GAE/J web application to work properly with Restlet.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<display-name>first steps servlet</display-name>
	<display-name>first steps servlet</display-name>
	<!-- Application class name -->
	<context-param>
<param-name>org.restlet.application</param-name>
<param-value>
			com.latifrealtor.RestletMain
       </param-value>
	</context-param>

	<!-- Restlet adapter -->
	<servlet>
		<servlet-name>RestletServlet</servlet-name>
		<servlet-class>org.restlet.util.ServerServlet</servlet-class>
	</servlet>
	<!-- Catch all requests -->
	<servlet-mapping>
		<servlet-name>RestletServlet</servlet-name>
		<url-pattern>/resources/*</url-pattern>
	</servlet-mapping>

		<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>

</web-app>

One Comment

  1. JhinSeok Lee June 17, 2011 Reply

Add a Comment

Your email address will not be published. Required fields are marked *