Wednesday, 13 May 2009

Data Persistence in GAE with Clojure

If you want to persist stuff in Java, you've got a bewildering amount of choice

There's even an entire book about making the right decision! (Persistence in the Enterprise)

Google App Engine has gone with JDO using the Data Nucleus platform. In GAE this is split again into two APIs, the high-level one for persisting objects, and a lower-level one which allows you to persist raw data.

When using Clojure it makes more sense to go with the lower-level api. The higher-level one would require using annotations on objects which isn't supported at the moment in Clojure (as far as I know!).

So how do we store data in GAE? The example below saves a story to disk (as a learning exercise, I'm writing a quick and dirty reddit clone).


(ns news.savestory
(:use (news appengine))
(:gen-class :extends javax.servlet.http.HttpServlet)
(:import (com.google.appengine.api.datastore DatastoreServiceFactory Entity Key Query)))

(defn store
[data type]
(let [entity (Entity. (.toString type))]
(doseq [[k v] data]
(.setProperty entity (.toString k) v))
(.put (DatastoreServiceFactory/getDatastoreService) entity)
(.getKey entity)))

(defn -doGet
[_ request response]
(let [body (.getParameter request "storyLink")
title (.getParameter request "storyTitle")]
(let [w (.getWriter response)]
(.println w (store {:body body :title title} :story)))))


store takes a map and a type and persists it in the database and returns the key associated with this entity. Visiting the URL persists the data in the URL and returns the key.

Retrieving the data is much the same.


(ns news.viewstory
(:use (news appengine))
(:gen-class :extends javax.servlet.http.HttpServlet)
(:import (com.google.appengine.api.datastore DatastoreServiceFactory Entity Key Query KeyFactory)))

(defn entity-to-map
[entity]
(into (hash-map) (.getProperties entity)))

(defn getEntity
[id type]
(let [k (KeyFactory/createKey (.toString type) (Long/valueOf id))]
(entity-to-map
(.get (DatastoreServiceFactory/getDatastoreService) k))))

(defn -doGet
[_ request response]
(let [id (.getParameter request "storyId")
story (getEntity id :story)
w (.getWriter response)]
(doseq [[k v] story]
(.println w (str k "=" v)))))


entity-to-map just converts the properties of the entity into a friendly Clojure type.

So now I know how to authenticate users, next step is to get some basic UI together. There's a number of choices here (JSP, server side HTML generation in Clojure or just go with Ajax). I'm leaning towards the Ajax!

Labels: ,


Sunday, 10 May 2009

User authentication in GAE

Google App Engine provides a set of standard APIs for common tasks, such as user authentication, caching and persistent storage. This post looks at the user authentication API and creates a simple form that is authenticated against a Google login.

We'll use exactly the same structure and build scripts as the previous post, as that just makes life easier.

The servlet below checks whether there is an current user logged in. If not, then redirect to a prompt requiring a user to login, otherwise just display the users nickname.


(ns blogging.login
(:gen-class :extends javax.servlet.http.HttpServlet)
(:import (com.google.appengine.api.users User UserService UserServiceFactory)))

(defn greet
[user response]
(.setContentType response "text/plain")
(let [w (.getWriter response)]
(.println w (str "Hello, " (.getNickname user)))))

(defn -doGet
[_ request response]
(let [userService (UserServiceFactory/getUserService)
user (.getCurrentUser userService)]
(cond
(not (nil? user)) (greet user response)
:else (.sendRedirect response (.createLoginURL userService (.getRequestURI request))))))


If you deploy on the development server, then you get a screen like that shown below:



Neat! Next on the list, persisting data.

Labels: ,


Clojure on the Google App Engine

The Google App Engine offers a complete stack for deploying applications in the cloud. Initially, support only existed for Python, but recently support was announced for Java.

Although, the main page announces this as support for the Java Language, it's much more than that, it's support for the Java Virtual Machine. The list of languages on the JVM is huge. This means that in theory, any of these languages can now be hosted in the cloud.

So how easy is it to get Clojure going in the cloud?

Firstly, register at http://appengine.google.com and get yourself an account. Download the Java AppEngine SDK too and unpack that and get the development server up and running.

GAE is based on the Servlet 2.5 specification, so the typical directory structure looks very similar to any JSP/Servlet type application:



As GAE is based on servlets, we need to define a simple servlet for the mandatory hello world demo! This code goes in the src directory:


(ns helloclojure.servlet
(:gen-class :extends javax.servlet.http.HttpServlet))

(defn -doGet
[_ request response]
(let [w (.getWriter response)]
(.println w "Hello world!")))


:gen-class causes Clojure to emit a class file representing this name space. The - before doGet indicates that this is a member function with three arguments (the first representing "this", unused and therefore a _ symbol). So all we do for this servlet is write "hello world" whenever any request is made.

Next we need to make a standard web.xml descriptor and put that in META-INF. This registers the servlet and specifies the mapping between a URL format and the servlet that deals with the request.



<?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">
<servlet>
<servlet-name>helloclojure</servlet-name>
<servlet-class>helloclojure.servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloclojure</servlet-name>
<url-pattern>/helloclojure</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>


Also in the META-INF directory we need a descriptor for the GAE.



<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application></application>
<version>1</version>
</appengine-web-app>


That's all the scaffolding you need. Finally, we need some way to build and deploy this. Thankfully, someone has already looked at this, so I took their build script and made a few modifications (remove the test target for example) in the name of simplification.

The net result is the following Ant script.


<project name="helloclojure" basedir="." default="compile">

<property environment="env" />
<property name="sdk.dir" location="/home/jfoster/appengine-java-sdk-1.2.0" />
<property name="classes.dir" value="war/WEB-INF/classes" />
<property name="lib.dir" value="war/WEB-INF/lib" />
<property name="src.dir" value="src/" />

<import file="${sdk.dir}/config/user/ant-macros.xml"/>

<path id="project.classpath">
<pathelement path="${classes.dir}" />
<pathelement path="${src.dir}/helloworld" />
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
<fileset dir="${sdk.dir}/lib">
<include name="**/*.jar" />
</fileset>
</path>

<target name="clean">
<delete dir="${classes.dir}" />
</target>

<target name="init">
<mkdir dir="${classes.dir}" />
</target>

<target name="compile" depends="clean,init">
<java classname="clojure.lang.Compile" classpathref="project.classpath" failonerror="true">
<classpath path="${src.dir}" />
<sysproperty key="clojure.compile.path" value="${classes.dir}" />
<arg value="helloclojure.servlet" />
</java>
</target>

<target name="devserver" description="run local dev appserver" depends="compile">
<dev_appserver war="war" />
</target>

<target name="deploy" description="deploy to appspot" depends="compile">
<appcfg action="update" war="war" />
</target>

</project>


All code is available on my Git Repo

Labels: ,


This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]