Wednesday, November 26, 2008

Loom 1.0 final is out. Let there be AWESOME.


After two years of work, 700+ java classes, 400+ tests, 35 javascript test pages and a hell of a week polishing rough edges, we finally got the release out the door.

(I enjoy an entire night of pure insomnia once a month, which looking behind is a great boost for productivity :)

With lots of bottled awesomenesstm inside, the following is a selection of the new features that come included in Loom 1.0:

This release includes a scaffolding tool



We finally got tired of starting projects from scratch, so Rafa got a week dedicated to it and prepared a tool that can kick start a project in five minutes, assuming a decent internet connection. Just download scaffolding.zip from the download page, unzip and run:


# Create a project named myapp
scaffolding create myapp

# Create the eclipse project
# (requires GRADLE_HOME defined)
cd myapp
gradle eclipse

# Deploy on your favorite web server
# (requires CATALINA_HOME or JETTY_HOME defined)
gradle deploy-tomcat
gradle deploy-jetty


Sometimes gradle timeouts while waiting for maven repositories; in that case, just wait a little and repeat the command.

The created template will be based on gradle, which is (some artistic license, here) a combination of ivy + groovy + steroids. Notice that Loom does not use groovy but as a substitute for ant (and a great one at it!), the rest is 100% java.

Open the project using eclipse, where compiled java files go directly into WEB-INF/classes. Open "Run as" and select "myapp - run tomcat" or "myapp - run jetty" (again, CATALINA_HOME or JETTY_HOME are required). No plugins are needed, no build process, nothing. Just compile and open a browser at http://localhost:8080/myapp



Now, create a new JPA entity with some attributes using eclipse (Loom does not require JPA, but this is easier this way) and add your own CRUD interface:


# execute this in your project home dir after MyPersistentClass has been compiled
scaffolding crud com.acme.model.MyPersistentClass


That's it. Restart the server and test your interface. The form should validate your JPA constraints, which can be modified at any time without re-creating the interface or anything.

This release includes Javascript and CSS repositories



Your javascript and CSS files can be concatenated, minified and gzipped in production without involving any ant or maven script.

This is similar to what JAWR does, but easier to configure and use. Its main features are:

  • Configured using spring: it does not require external files.

  • All files (javascript, CSS, referenced images) can be loaded from the classpath, which means that they can be provided by other jars. You can still use images inside CSS files, and they will be served - even from the classpath.

  • All files (again - css, javascript AND image files!) are aggressively cached based on their MD5 checksums. If you have ever worked with GWT, you may be familiar with the concept - if not, drop by our Devoxx talk where we will be talking about this as long as time allows.

  • You can choose between YUICompressor (javascript and CSS) and JSMin (javascript) just by dropping them on the lib folder. They are included in the /lib/compressors folder of the Loom distribution.


This should be easier to digest using an example. The spring file:


<bean class="org.loom.resources.WebResourceBundleRepository">
<property name="bundles">
<bean class="org.loom.config.SpringResourceMapFactory">
<property name="resources">
<value>
css=/css/base.css /css/basemod.css
ie=/css/yaml-3.0.5/core/iehacks.css /css/my_iehacks.css
demo=classpath:/js/prototype/prototype-1.6.0.3.js classpath:/js/loom/core.js \
classpath:/js/loom/format.js
</value>
</property>
</bean>
</property>


The JSP file:

<l:css resource="css"/>
<l:css resource="ie" ie="any"/>
<l:script resource="demo"/>


The generated html with config.development=true:

<link href="/myapp/resources/css/0/2008a6c2ba1c6009f5f84db99e6a76e0/base.css" rel="stylesheet" type="text/css"></link>
<link href="/myapp/resources/css/1/f6c4b21a2f6c9d265d14081243c2da40/basemod.css" rel="stylesheet" type="text/css"></link>

<!--[if IE]>
<link href="/myapp/resources/ie/0/658801c193faa8e7173e2cd220093e67/iehacks.css" rel="stylesheet" type="text/css"></link>
<link href="/myapp/resources/ie/1/658801c193faa8e7173e2cd220093e67/my_iehacks.css" rel="stylesheet" type="text/css"></link>
<![endif]-->

<script src="/myapp/resources/demo/0/b5684120e496c310977713be34be4868/prototype-1.6.0.3.js" type="text/javascript"></script>
<script src="/myapp/resources/demo/1/07139c2fd5d77ee9fa619ff335996068/core.js" type="text/javascript"></script>
<script src="/myapp/resources/demo/2/bd7026fb11a52e8fe8c671e894885e78/format.js" type="text/javascript"></script>



The generated html with config.development=false:

<link href="/myapp/resources/css/all/7c66dcfb53bb87201e74aef4f1211a63/" rel="stylesheet" type="text/css"></link>

<!--[if IE]>
<link href="/myapp/resources/ie/all/da188973e7712595d03da0bbbfa059f4/" rel="stylesheet" type="text/css"></link>
<![endif]-->

<script src="/myapp/resources/demo/all/1c004c851402d83a84e3d7a2fb73a375/" type="text/javascript"></script>


The same page, with development=false

These resources have been automatically concatenated, gzipped, MD5-hashed (just once!) and set to expire ten years from now. The browser will never ask for them again, as long as they do not get modified or the user hits the "refresh" button. During normal navigation only the html file will be retrieved.

But there's more: during development, your changes will be detected on real time, but the browser will only ask for the resources that have changed. The typical 304 traffic between server and client will just not happen.

Go test the feature live on the new demo, just turn on / off the debug switch (at the top right corner) and open firebug to see the number of files involved for each request. Now, navigate between pages and keep an eye on your firebug net tab. No files are being requested.

Wait, there's more.

This release includes partial JAX-RS support



We have added support for the mapping annotations of the JAX-RS spec. In fact, the CRUD interface created by scaffolding does just that:


public class MortgagesAction extends AbstractAction {

@RetrieveEntity(on={"save", "edit"})
private Mortgage mortgage;

@GET @Path("/")
public Resolution list() { ... }

@GET @Path("create")
public Resolution create() { ... }

@GET @Path("/{mortgage.id}")
public Resolution edit() { ... }

@POST @Path("/{mortgage.id?}")
public Resolution save() { ... }

@DELETE @Path("/{mortgage.id?}")
public Resolution delete() { ... }

}


Combine with JavaRebel to enjoy The Extra Cool Development Experience. Open your Action, modify your mappings, refresh the browser - and only your Action class gets reloaded. New Actions cannot be added on-the-fly and there are other limitations, but this first release is really promising.

This release includes some serious pagination components.



I have tested almost every pagination solution in the wild since 1999, checking them off with my old list of (almost) reasonable requirements:

  • Pagination should be done by the database if possible. Having said that, sometimes I don't have a database, and the web framework should do its work.

  • There is almost no difference between a paged list (<ul>) and a paged table (<table>).

  • Pagination and sorting should be transparent, or almost. That is tricky when it involves the database.

  • It should include a professional look and feel by default. This is not my merit, kudos go to Antonio Lupetti.



<l:pagedTable data="${action.mortgages}" id="mortgages">
<l:column sortable="false">
<l:inputCheckbox name="selectedRows" class="selectRow checkbox" value="${row.id}" renderLabel="false"/>
</l:column>
<l:column property="id" />
<l:column property="name" action="Mortgages" event="edit">
<l:param name="mortgage.id" value="${row.id}"/>
</l:column>
<l:column property="address" />
<l:column property="principalLoanBalance" title="Loan" />
<l:column property="creationDate" class="date" />
</l:pagedTable>




Again, you can check examples with tables and lists in the demo application.

This release includes LocaleAwareException and HttpException



This is just a pet feature, but I love it. You can throw exceptions that will get to the user as any normal input validation errors, or throw HTTP errors like 404 at any point of the code. It's handy.

This release includes extensibility



Two years are a lot of time, in framework years. We kept the original goal of a lightweight and focused web framework, and moved all the server and javascript components that started cropping everywhere to the addons project (take it easy - it's not that mature yet).

The point is not about the addons project but about this whole new approach to extensibility: just drop a META-INF/loom-descriptor.properties in your jar file like this:


# The list of annotation processors provided by this jar
annotation-processors=org.loom.addons.multiupload.MultiUploadAnnotationProcessor, \
org.loom.addons.confirmation.RequiresConfirmationAnnotationProcessor, \

# The list of Converter factories provided by this jar
converter-factories=org.loom.addons.autocompleter.AutocompletedConverterFactory

# The list of messages locations provided by this jar
messages=classpath:resources/addons-messages


Loom will load it from the classpath and the application developer may use its contents:

  • Java annotations

  • Validators and Converters

  • Javascript components, including CSS and image files

  • Internationalized messages


And, of course, its own JSP tags as usual.

And that's it?



Damn, no. This is the framework we are using to build applications, every day. We expect it to keep growing with a steady pace with all the know-how in our way for years to come.

I only wish "steady" could mean "slower" in this context. I can live without the stress of the last few weeks :)

4 comments:

Ale Sarco said...

Felicitaciones Nacho! I will give it a try in the next days.

Alejandro Scandroli said...

Felicitaciones y felicidades Ignacio!

Nacho Coloma said...

Gracias :) Las dos Ășltimas semanas han sido como enhebrar una aguja en medio de una pelĂ­cula de los Hermanos Marx. Pasaban demasiadas cosas a la vez.

Jose said...

Suerte

Post a Comment