Monday, May 10, 2010

Loom 2.0 is out

Running to get there
A major upgrade of the Loom web framework has been released. We made it a major version change since it includes some breaking changes in places where backwards-compatible was just not worth it.

Tag documentation has been improved a lot, but there are other features also worth seeing:

Works with AppEngine


We have been developing our own share of projects with Loom, and some are running in AppEngine. To make this possible we have forked a subproject with AppEngine image validation, datastore type converters (Key, GeoPt, etc), and transparent support for binding SimpleDS paged queries into paged tags.

Cache page fragments


We have added session and application-level caching of page fragments, such as:


<l:cache id="application-menu" scope="application">
<!-- application menu goes here -->
</cache>

Fragments can be cached separated by locale or not. This implementation is really simple, and things like cache timeout are not included yet.

Event parameters


Events now accept parameters:


public class BlogEntriesAction extends AbstractAction {

// parameter name is required
@Path("baz")
public Resolution baz(@QueryParam("id") int id) { ... }

// parameter name is not required when embedded in the URL
@Path("bar/{foo}")
public Resolution bar(String foo) { ... }

// You can mix embedded and non-embedded parameters, but
// embedded are expected to be resolved left-to-right
@Path("bazbar/{foo}")
public Resolution bazbar(String foo, @QueryParam("bar") int bar) { ... }

}

New JSON support


We finally deprecated our quick-and-dirty JSON implementation to adopt jackson. This makes some nice features possible:


// return a JSON response
public Resolution doFoo() {
return json(myObject);
}

// receive JSON objects as parameters
public Resolution bar(@JSON @QueryParam("baz") baz) { /* ... */ }

// render as JSON inside a page
public Resolution doFoo() {
return forward("mypage.jsp").setAttribute("bar", bar);
}

<script>
alert(${l:json(bar)});
</script>

Uploaded files


A breaking change introduced in 2.0 is that file uploads are no longer being automatically bound to java attributes. From now on you must handle these parameters yourself, and this affects the way file validations are being performed:


@FileValidation(
parameterName="uploadedDocument",
formats={ "txt", "rtf" },
maxFileSize=1024000)
public Resolution saveDocument() {
fileManager.merge(getRequest().getFileParameter("uploadedDocument"));
}

@ImageValidation(
parameterName="uploadedImage",
formats={ "jpg", "gif" },
maxWidth=100,
maxHeight=100,
maxFileSize=1024000)
public Resolution saveDocument() {
fileManager.merge(getRequest().getFileParameter("uploadedDocument"));
}

New paged features


Now you can use more than one paged container (table or list) in the same HTML page, or inject a plain Collection to be conveniently displayed as a single page.

Also: JSON paged lists! PagedListData instances can now be serialized JSON and handled by javascript to render the same HTML contents generated by the server-side tag.

More details about the current paged tags status can be found here.

New information methods


A new set of static methods MessageUtils.info(), warn() and error() have been added, which can be invoked anywhere in your code.


public class MyServiceImpl implements MyService {

public void myService() {
entityManager.put();
MessageUtils.info("save.success");
}

}

Messages are serialized in case of a redirect, and will be displayed on the next web request.

SSLPolicy


A new annotation has been added to indicate the level of encryption on the annotated events:

@SSLPolicy(REQUIRES_INSECURE)
public class FooAction extends AbstractAction {

public Resolution event1 {
// this event requires http:
}

@SSLPolicy(REQUIRES_SECURE)
public Resolution event2 {
// this event requires https:
}

}

Recaptcha support


In this release we are migrating from JCaptcha into ReCaptcha, since it seems to be the only implementation designed to face Human Computation attacks. It is also much easier than JCaptcha to configure and use, which is a plus.

@Recaptcha
public Resolution save() { ...}


<l:form action="Foo" event="save">
<a:recaptcha publicKey="[publicKey]"/>
</l:form>

The bad news is that the scaffolding tool is not yet up-to-date so we had to drop it from this release. It will be added again when time permits :)

The full changelog is here, the list of breaking changes is here, and comments, as usual, are welcome :)

Tuesday, May 04, 2010

SimpleDS 1.0_RC1 for AppEngine has been released

For those that know me from somewhere else, SimpleDS is our open source framework for persistence in AppEngine. It has been compared with Objectify and Twig, and has been mentioned in the Google App Engine Blog a couple of months ago.

This is our first feature-complete release of SimpleDS. Lots of things have been included in so little time.

Getting up-to-date with AppEngine


It's hard to keep up the pace with these guys. This release includes:
  • Unindexed attributes.
  • Cursors support.
  • IN and != clauses.

Cache


We have included a great Level 1 and Level 2 cache. If you come from JDO/JPA, you may already know what this means:
  • Level 1 cache: This is basically a Map bound to the current thread. Until the end of the current request, any get() invocation will check this cache first. If a match is found, no invocation will be propagated to GAE.
  • Level 2 cache: Datastore entities are also stored in memcache, which is a second chance to get a positive match.

Cacheable entities must be marked with @Cacheable, with an optional expiration time. This feature will work with single and batch get(), and the cache entries are updated with put() and delete() invocations.

// Invoke memcache or the datastore
List data = entityManager.get(key1, key2, key3);

// this does not invoke anything (resolved by the Level 1 cache)
MyData d2 = entityManager.get(key1);

Functions


We are going extremely functional these days. This release includes a package with functions to transform collections and PagedList instances, which can be combined with batch get() to get even better performance results. This is a simple example, equivalent to a situation quite common when using relations:

// n + 1 requests to the datastore
List data = entityManager.find(query);
Collection parents = Lists.newArrayListwithCapacity(data.size());
for (MyData d : data) {
  parents.add(entityManager.get(d.getKey().getParent());
}

// Transformations: 2 requests 
List data = entityManager.find(query);
Collection parentKeys = Collections2.transform(data, new EntityToParentKeyFunction(MyData.class));
Collection parents = entityManager.get(parentKeys);

That's all it takes to get all entities and their parents, and store them in the Level 1 cache so any request by PK will not hit memcache or the datastore. This release includes functions to retrieve parent and foreign keys, and also works with PagedList. We have taken two real-world snapshots with just this optimization (transformation + cacheable), applied to a single loop:

Before: http://www.flickr.com/photos/koliseocom/4575062969/in/set-72157623904289518/
After: http://www.flickr.com/photos/koliseocom/4575696456/in/set-72157623904289518/

Notice that "before" are also requests to the datastore, while most of the "after" are requests to memcache.

Background tasks


We are aware that background tasks are in the roadmap for AppEngine, but we needed these today. This started as an exercise to upgrade the datastore schema (add properties, delete entities etc) and ended up as a full reusable implementation of tasks that I expect to deprecate once that AppEngine includes its own, probably better, implementation.

Background tasks require adding a servlet to web.xml (and optionally appengine-web.xml as an admin-console entry) and configuring the tasks on application start using plain Java. Tasks can be invoked directly by cron triggers, queues or by POST requests.

public class WriteBehindCacheTask extends IterableTask {

 protected WriteBehindCacheTask() {
  super("my-task-id");
 }

 @Override
 protected SimpleQuery createQuery(TaskRequest request) {
  return entityManager.createQuery(MyClass.class);
 }

 @Override
 protected void process(MyClass entity, TaskRequest request) {
  // ...modify entity...
  entityManager.put(entity);
 }

}

Query cursors and execution deferral will be done transparently (no need to limit or handle cursors). There are some implementation superclasses depending on what you need to do, and some of them just use the raw AppEngine datastore and don't even require SimpleDS to work.

Other features can be checked out at the SimpleDS home page and the changelog. Any feedback is welcome :)