I've pushed my initial work on generating Ceylon war files to branches
of ceylon-compiler[1] and ceylon-sdk[2], and am looking for feedback
on the approach, code, and features.
These changes allow you to deploy ceylon modules as wars, with any EE
annotations in those modules properly scanned.
First, let's walk through how it works. There are two pieces: the
`ceylon war` command (in ceylon-compiler), and the `ceylon.war` module
(in ceylon-sdk). The `ceylon war` command takes a module-name (or
module-name/version), and generates a war file for that single
module. The war is constructed by loading the module (along with the
core language modules and the ceylon.war module, which I'll talk about
in a sec), and generating the classpath for that module (using the same
mechanism as `ceylon classpath`). The items on that classpath are copied
to WEB-INF/lib/, but any .car files are renamed to .jar. We do that
renaming so the deployer for the servlet container can properly scan and
add those jars to the effective classpath. And that is enough for
servlet/jaxrs service/whatever annotations to be scanned and registered
from Ceylon classes, except they won't have access to the metamodel.
The next piece is getting the metamodel initialized (or initialised,
if you prefer). We write a list of all the libs we added to the war as
META-INF/libs.txt, and write a WEB-INF/web.xml that registers a
ServletContextListener (the default web.xml is [3]). That
ServletContextListener is the WarInitializer[4], and lives in the
ceylon.war module. The WarInitializer checks to see if we are running
from an exploded war, and, if not, uses libs.txt to extract all the
JARs to a temporary on-disk flat repo. If it doesn't have to extract,
it uses WEB-INF/lib/ as the flat repo. It then uses that repo to
initialize the metamodel, and runs the base module that the war is for
(based on the module name and version that we store in
META-INF/module.properties).
# Concerns
## the ceylon.war module
The ceylon.war module exists solely to put WarInitializer.class (which
itself is written in java) on the effective classpath. This is a
module that a user would never import (the war tool loads it
directly before calculating the lib list for the war), so is this
really the correct place for WarInitializer? I made it a module for
build convenience - it needs the servlet API jar, and I didn't want to
make that jar part of the compiler build when it is available on herd
via javax.servlet. Is there a better place for it?
## multi-module support
Right now, there is a 1-1 correspondence between a war and a module. I
considered allowing you to package multiple modules in one war
(`ceylon war mod.a/1 mod.b/1 ...`), but didn't want to add logic to
deal with version conflicts between dependencies of those modules. Are
multi-module wars worthwhile? Maybe this is where an artifact
descriptor comes in to play - if we detect any version conflicts, we
abort, requiring you to provide an artifact descriptor as an option to
the tool that provides the definitive module specs.
## Java annotations
It's nice that we can use standard JavaEE annotations from Ceylon, but
I ran in to a few cases where the interop is sub-optimal.
The first case is @WebServlet (which we use from Ceylon as webServlet) -
from java, you can specify it with just a path (@WebServlet("/foo")), or
with named params (@WebServlet(urlPatterns = {"/foo"}, ...)). In Ceylon,
we can only use named parameters with it (webServlet { urlPatterns =
{"/foo"}; }) due to a limitation in interop. This was discussed briefly
on IRC last week, but I'm not sure if it generated an issue on the
appropriate project.
The second case is @GET from jaxrs (but applies to any all-caps
annotation). We access it in Ceylon as `gET`, which doesn't look
great. Would it be possible to allow java annotations like this to be
called as all lowercase (`get`)? I realize that would probably mean
allowing `webservlet` as well. Or would this just be an interop wart?
## Jetty
Wars generated with this tool do not yet work in Jetty - it doesn't
seem to recognize it as a war, and instead allows you to browse the
war as a filesystem. If anyone knows of a good way to get debugging
info out of Jetty, I'm happy to dig a bit deeper.
# Give it a try
I've created a git repo with example servlet and jaxrs modules[5],
along with instructions on usage, if you want to try it out.
Like I said above: if you have any feedback at all, I'm happy to hear
it. Right now, I consider this a POC and am not strongly attached to
the current implementation.
- Toby
[1]:
https://github.com/tobias/ceylon-compiler/tree/war-tool[2]:
https://github.com/tobias/ceylon-sdk/tree/war-module[3]:
https://github.com/tobias/ceylon-compiler/blob/war-tool/src/com/redhat/ceylon/tools/war/resources/default-web.xml[4]:
https://github.com/tobias/ceylon-sdk/blob/war-module/source/ceylon/war/WarInitializer.java[5]:
https://github.com/tobias/ceylon-war-examples--
You received this message because you are subscribed to the Google Groups "ceylon-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to
ceylon-dev+...@googlegroups.com.
To post to this group, send email to
ceylo...@googlegroups.com.
Visit this group at
http://groups.google.com/group/ceylon-dev.
To view this discussion on the web visit
https://groups.google.com/d/msgid/ceylon-dev/87k338qtx4.fsf%40tcrawley.org.
For more options, visit
https://groups.google.com/d/optout.