nascent support for generating Ceylon war files

45 views
Skip to first unread message

Toby Crawley

unread,
Nov 6, 2014, 9:46:13 AM11/6/14
to Ceylon Dev Dev
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

Stephane Epardaud

unread,
Nov 6, 2014, 10:02:33 AM11/6/14
to ceylon-dev
Yay, that's great progress man!

On 6 November 2014 15:34, Toby Crawley <to...@tcrawley.org> wrote:
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?

That's the bit you wanted to put in the language module, right?
 
## 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.

I don't think this is very important. It's likely that if a module needs another module to be in the same war, it will depend on it. Two separate applications should be in different wars.
 
## Java annotations


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.

It has been filed and work has started. 

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?

This is just plain weird. I remember in a previous like making camel case properly translate GET into get and XMLHttpRequest into xmlHttpRequest. I know there is code for that somewhere and I just have to remember where and what for. Hopefully we can do better. 

Toby Crawley

unread,
Nov 6, 2014, 10:11:10 AM11/6/14
to ceylo...@googlegroups.com

Stephane Epardaud writes:

> On 6 November 2014 15:34, Toby Crawley <to...@tcrawley.org> wrote:
>
>> 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?
>>
>
> That's the bit you wanted to put in the language module, right?
>

Right, I briefly had it in language, not compiler. It felt wrong to have
it in the language module, because it's not really part of the language,
and doesn't need to be on the classpath for every Ceylon
project. Another option is to make the .class file a resource within the
war tool itself, which it inserts directly in to the war, but that
presents its own build complications - the compiler project would now
need to depend on the servlet API jar to build.

>
>> ## 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.
>>
>
> I don't think this is very important. It's likely that if a module needs
> another module to be in the same war, it will depend on it. Two separate
> applications should be in different wars.
>

Good deal.

>
>> ## Java annotations
>>
>> 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.
>>
>
> It has been filed and work has started.

Great!

>
> 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?
>>
>
> This is just plain weird. I remember in a previous like making camel case
> properly translate GET into get and XMLHttpRequest into xmlHttpRequest. I
> know there is code for that somewhere and I just have to remember where and
> what for. Hopefully we can do better.

Also great.

Gavin King

unread,
Nov 6, 2014, 10:37:05 AM11/6/14
to ceylo...@googlegroups.com
On Thu, Nov 6, 2014 at 3:34 PM, Toby Crawley <to...@tcrawley.org> wrote:
> 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.

It all sounds great to me. Well done!

> # Concerns
>
> ## the ceylon.war module

Does this module appear in my code? (Do I have to import it in my
module descriptor?) Or is it something that only the tool sees?

> ## multi-module support
>
> Right now, there is a 1-1 correspondence between a war and a module.

Well it depends what mental model we have here. If the mental model is
that the war is the module is the war, and the job of "ceylon war" is
simply to repackage my module as a war, then I don't see a problem.
And to me that's a totally reasonable model to have.

You can still import additional modules from your "war module", right?

> ## Java annotations

> The first case is @WebServlet (which we use from Ceylon as webServlet) -
> ... but I'm not sure if it generated an issue on the
> appropriate project.

Yes, I opened an issue.

> 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?

It should work with \iGET, right? Also not perfect, of course, but you
can also use aliases.

> # 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 do you want us to point folks on ceylon-users and G+ to it yet? I
have a feeling they will find the wrinkles more quickly than we will.

--
Gavin King
ga...@ceylon-lang.org
http://profiles.google.com/gavin.king
http://ceylon-lang.org
http://hibernate.org
http://seamframework.org

Gavin King

unread,
Nov 6, 2014, 10:39:35 AM11/6/14
to ceylo...@googlegroups.com
On Thu, Nov 6, 2014 at 4:36 PM, Gavin King <gavin...@gmail.com> wrote:

>> 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?
>
> It should work with \iGET, right? Also not perfect, of course, but you
> can also use aliases.

Oh wait, I understand. It's because this is the annotation
constructor, not the annotation type, and we decapitalize the name to
generate a name for the annotation constructor. OK. So that's
suboptimal.

It makes me wonder if we've taken the wrong route here and should have
done something else like prefixing with _, for example, _GET,
_WebServlet.

Tom Bentley

unread,
Nov 6, 2014, 11:01:03 AM11/6/14
to ceylon-dev

Oh wait, I understand. It's because this is the annotation
constructor, not the annotation type, and we decapitalize the name to
generate a name for the annotation constructor. OK. So that's
suboptimal.

It makes me wonder if we've taken the wrong route here and should have
done something else like prefixing with _, for example, _GET,
_WebServlet.


You should still be able to use an import alias though so gET only appears once in the compilation unit.

Stephane Epardaud

unread,
Nov 6, 2014, 11:03:20 AM11/6/14
to ceylon-dev
Hah, it's in AbstractModelLoader.getJavaBeanName() That's what we use to map getURLDecoder() to the urlDecoder attribute. Shouldn't we use that for the annotation constructor name?

--
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/CAMd5Ysz2yTMXYT2vuRDz%3DsQOP8OqOBfh35TxXf0ufniQW_L5Ew%40mail.gmail.com.

For more options, visit https://groups.google.com/d/optout.



--
Stéphane Épardaud

Gavin King

unread,
Nov 6, 2014, 11:20:50 AM11/6/14
to ceylo...@googlegroups.com

Toby Crawley

unread,
Nov 6, 2014, 12:42:00 PM11/6/14
to ceylo...@googlegroups.com

Gavin King writes:

> On Thu, Nov 6, 2014 at 3:34 PM, Toby Crawley <to...@tcrawley.org> wrote:
>>
>> ## the ceylon.war module
>
> Does this module appear in my code? (Do I have to import it in my
> module descriptor?) Or is it something that only the tool sees?

That module doesn't appear in user code, the tool loads it just to get
it listed among the dependencies. See:
https://github.com/tobias/ceylon-compiler/blob/war-tool/src/com/redhat/ceylon/tools/war/CeylonWarTool.java#L74

>
>> ## multi-module support
>>
>> Right now, there is a 1-1 correspondence between a war and a module.
>
> Well it depends what mental model we have here. If the mental model is
> that the war is the module is the war, and the job of "ceylon war" is
> simply to repackage my module as a war, then I don't see a problem.
> And to me that's a totally reasonable model to have.
>
> You can still import additional modules from your "war module", right?

Correct, the base module can import any other modules without any
additional issues.

>
> Toby do you want us to point folks on ceylon-users and G+ to it yet? I
> have a feeling they will find the wrinkles more quickly than we will.
>

Sure, point away! I'd be happy to send an email to ceylon-users@, but
would leave the G+ post up to you guys. I'll send something to the user
list now.

- Toby

Toby Crawley

unread,
Nov 6, 2014, 12:42:40 PM11/6/14
to ceylo...@googlegroups.com
Tom: good point. I've updated the example app to do just that.

- Toby

Gavin King

unread,
Nov 6, 2014, 1:05:54 PM11/6/14
to ceylo...@googlegroups.com
Then I guess I don't care much :)

Sent from my iPhone
> --
> 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/87h9ycql98.fsf%40tcrawley.org.

Tomáš Hradec

unread,
Nov 6, 2014, 2:27:41 PM11/6/14
to ceylo...@googlegroups.com
That module doesn't appear in user code, the tool loads it just to get
it listed among the dependencies. 

If it is not public API of SDK, then it should be under `com.redhat.ceylon`, similar as `com.redhat.ceylon.testjvm`. 

Gavin King

unread,
Nov 6, 2014, 2:31:15 PM11/6/14
to ceylo...@googlegroups.com
Yes, right, good point.

Sent from my iPhone
--
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.

Toby Crawley

unread,
Nov 6, 2014, 3:38:34 PM11/6/14
to ceylo...@googlegroups.com
Thanks, I've changed `ceylon.war` to `com.redhat.ceylon.war`.

- Toby
Reply all
Reply to author
Forward
0 new messages