Red Hat

Using exploded deployments and CLI attachments

In WildFly there used to be two worlds : one for developers with exploded deployments using a scanner and one for production where artifacts (wars/ears/jars) were deployed.
Now those two worlds have collided and you can have exploded deployments without a scanner.
And since this is using the management API you get remote access to the content for "free" and of course it works in domain mode.
While this new feature is really usefull for tools developers (JBoss Developer Studio will use this in a tech preview, and NetBeans should use it also), YOU can also take advantage of it.

Creating exploded deployments

There are two ways to create an exploded deployment:

  • Create an empty deployment and add content to it.

  • Deploy an artifact and explode it.

Creating an exploded deployment from scratch

Let’s create a from_scratch.war deployment:

[standalone@localhost:9990 /] /deployment=from_scratch.war:add(content=[{empty=true}])
[standalone@localhost:9990 /] /deployment=from_scratch.war:add-content(content=[
          {input-stream-index=/home/ehsavoie/NetBeansProjects/SimpleWebapp/target/SimpleWebapp/index.html,target-path=index.html},
          {input-stream-index=/home/ehsavoie/NetBeansProjects/SimpleWebapp/target/SimpleWebapp/WEB-INF/web.xml, target-path=WEB-INF/web.xml},
          {input-stream-index=/home/ehsavoie/NetBeansProjects/SimpleWebapp/target/SimpleWebapp/WEB-INF/classes/org/wildfly/sample/simplewebapp/SimpleServlet.class, target-path=WEB-INF/classes/org/wildfly/sample/simplewebapp/SimpleServlet.class}])
[standalone@localhost:9990 /] /deployment=from_scratch.war:deploy

Here we have created an empty deployment called from_scratch.war to which we have added 3 files: - a simple HTML page index.html. - A class file for a servlet. - A web.xml descriptor.

Then we have enabled the deployment and thus could access the index.html and the servlet.
If you take a look at the $JBOSS_HOME/standalone/content/dc/9567f71b186466b21fff825d60f5fbc84ae6b1/content/ you will see the exploded war content.

Since this content is managed, don’t touch it directly by copying file to it manually, always use the Management API (through the jboss-cli or the web console).

Note that the add-content can be used to replace files in a deployment but if the deployment is running this might not show until you redeploy it.
You can also delete files in a deployment using remove-content but if the deployment is running this might not show until you redeploy it.

Exploding a deployment

Let’s install and explode a to_be_exploded.war deployment:

[standalone@localhost:9990 /] /deployment=to_be_exploded.war:add(content=[{input-stream-index=/home/ehsavoie/NetBeansProjects/SimpleWebapp/target/SimpleWebapp.war}],enabled=false)
[standalone@localhost:9990 /] /deployment=to_be_exploded.war:explode
[standalone@localhost:9990 /] /deployment=to_be_exploded.war:deploy

This can be achieved via the web console using the explode operation in the menu: image::exploded_deployments/explode.png[explode]

If you take a look at the $JBOSS_HOME/standalone/content//cf/9918bad9d875ffb20da8747b5dcd15bdab16e0/content/ you will see the exploded war content (it differs from the first example hash has the war file contains META-INF files).

Since this content is managed, don’t touch it directly by copying file to it manually, always use the Management API (through the jboss-cli or the web console).

Note also that you can only explode an unexploded deployment or an archive file if the deployment is not running (aka is disabled).

Reading content from a deployment

Now you can read content from a deployment, so you can directly see what’s in your deployment. This operation can be use to browse the content of a deployment and read or download its files (even from inside an archive file).

Using the JBoss CLI

Using the browse-content operation you can have the list of files in the deployment :

[standalone@localhost:9990 /] /deployment=to_be_exploded.war:browse-content
{
    "outcome" => "success",
    "result" => [
        {
            "path" => "META-INF/",
            "directory" => true
        },
        {
            "path" => "META-INF/MANIFEST.MF",
            "directory" => false,
            "file-size" => 134L
        },
        {
            "path" => "WEB-INF/",
            "directory" => true
        },
        {
            "path" => "WEB-INF/classes/",
            "directory" => true
        },
        {
            "path" => "WEB-INF/classes/org/",
            "directory" => true
        },
        {
            "path" => "WEB-INF/classes/org/wildfly/",
            "directory" => true
        },
        {
            "path" => "WEB-INF/classes/org/wildfly/sample/",
            "directory" => true
        },
        {
            "path" => "WEB-INF/classes/org/wildfly/sample/simplewebapp/",
            "directory" => true
        },
        {
            "path" => "WEB-INF/web.xml",
            "directory" => false,
            "file-size" => 916L
        },
        {
            "path" => "WEB-INF/classes/org/wildfly/sample/simplewebapp/SimpleServlet.class",
            "directory" => false,
            "file-size" => 2302L
        },
        {
            "path" => "index.html",
            "directory" => false,
            "file-size" => 234L
        },
        {
            "path" => "META-INF/maven/",
            "directory" => true
        },
        {
            "path" => "META-INF/maven/org.wildfly.sample/",
            "directory" => true
        },
        {
            "path" => "META-INF/maven/org.wildfly.sample/SimpleWebapp/",
            "directory" => true
        },
        {
            "path" => "META-INF/maven/org.wildfly.sample/SimpleWebapp/pom.xml",
            "directory" => false,
            "file-size" => 2992L
        },
        {
            "path" => "META-INF/maven/org.wildfly.sample/SimpleWebapp/pom.properties",
            "directory" => false,
            "file-size" => 125L
        }
    ]
}

You can reduce the output by filtering using the path, depth and archive parameters. For exemple

[standalone@localhost:9990 /] /deployment=to_be_exploded.war:browse-content(path=WEB-INF/, depth=1)
{
    "outcome" => "success",
    "result" => [
        {
            "path" => "web.xml",
            "directory" => false,
            "file-size" => 916L
        },
        {
            "path" => "classes/",
            "directory" => true
        }
    ]
}

So now we can display the content of the web.xml. Using the read-content operation is not sufficient enough as it will return an attachment:

[standalone@localhost:9990 /] /deployment=to_be_exploded.war:read-content(path=WEB-INF/web.xml)
{
    "outcome" => "success",
    "result" => {"uuid" => "c778c51e-a507-4a71-a21f-d6af8b230db4"},
    "response-headers" => {"attached-streams" => [{
        "uuid" => "c778c51e-a507-4a71-a21f-d6af8b230db4",
        "mime-type" => "application/xml"
    }]}
}

So we need to combine this operation with the attachment operation like this :

[standalone@localhost:9990 /] attachment display --operation=/deployment=to_be_exploded.war:read-content(path=WEB-INF/web.xml)
ATTACHMENT 582a10e0-5159-4d2b-8d07-8d39af0df8c3:
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <servlet id="SimpleServlet">
        <servlet-name>SimpleServlet</servlet-name>
        <display-name>SimpleServlet</display-name>
        <servlet-class>org.wildfly.sample.simplewebapp.SimpleServlet</servlet-class>
        <init-param>
            <param-name>message</param-name>
            <param-value>Hello World</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>SimpleServlet</servlet-name>
        <url-pattern>/SimpleServlet</url-pattern>
    </servlet-mapping>
</web-app>

And to save this content locally we can use:

[standalone@localhost:9990 /] attachment save --operation=/deployment=to_be_exploded.war:read-content(path=WEB-INF/web.xml) --file=/home/ehsavoie/tmp/web.xml
File saved to /home/ehsavoie/tmp/web.xml

Using the web console

Navigate to 'Deployments' and select the deployment you want to browse. Then open the context menu and choose Browse Content:

browse content op

This opens a new page with the contents of the deployment. For each file, there’s a link with the full path and size of the file. Click on the link to download the file:

content

Using HAL.NEXT

The next major version of the web console (HAL.next) is currently under active development and is available as technical preview https://github.com/hal/hal.next. Follow the instruction in https://github.com/hal/hal.next#running to get started. Besides general improvements like better navigation and a revisited look and feel, HAL.next comes with many improvements for dealing with deployments:

  • Add Deployments using drag & drop.

  • New content browser using a tree view and an editor with syntax highlighting.

  • Download complete deployments or single files of a deployment.

Select deployments and just click on View to display its content:

explode next

This opens a new page which allows for a really nice way to browse and read content from a deployment:

content next

References

For the official documentation regarding deployments: Official Documentation
The example basic webapp used in this article is available here

Undertow.js 1.0.1.Final has been released

Undertow.js 1.0.1.Final has been released. This allows you to integrate JavaScript and Java EE in your applications. There have been some notable changes since I first wrote about this at http://wildfly.org/news/2015/08/10/Javascript-Support-In-Wildfly/.

Websocket Support

It is now possible to use WebSockets with Undertow.js. You can declare a websocket handler with the websocket method, which takes a path and a connection handler. The connection handler gets passed the connection object, which can be used to send messages, and can respond to messages by setting the onText, onBinary and onClose functions.

Binary messages are send and received using the JavaScript ArrayBuffer object.

$undertow
    .websocket("/websocket1", ['cdi:myBean', function (myBean, connection) {
        connection.send(myBean.getConnectedMessage());
        connection.onText = function(message) {
            return "echo-" + message;
        }
        connection.onBinary = function(message) {
            return message;
        }
        connection.onClose = function (message) {
            print(message.reason + " " + message.code);
        }
    }]);

Declarative Security

Declarative security allows you to specify the roles that are allowed to use a certain handler. This uses standard Servlet authentication, so if a user is not authenticated then the container will attempt to authenticate them using whatever authentication mechanism is defined in web.xml. The special role '**' means any authenticated user.

To use this we simply need to add a roles_allowed list to a handlers options map:

$undertow
    .onGet("/rest/endpoint",
        {roles_allowed: ['admin', 'user'], headers: {"content-type": "application/json"}},
        [function ($exchange) {
            return {message: 'Hello World'};
        }])

Transaction Support

It is now possible to mark a handler as transactional, which means that a new transaction will be started when the method is invoked. If the method completes without an exception the transaction will be commited, otherwise it will be rolled back. To use this add transactional: true to the methods options map:

$undertow
    .onGet("/rest/endpoint",
        {transactional: true, headers: {"content-type": "application/json"}},
        [function ($exchange) {
            return {message: 'Hello World'};
        }])

Option map defaults

It is now possible to set defaults that will be applied to every that is declared after the option is set (i.e. setting a default will not affect any previously declared handlers, only new handlers). This can be used to set transaction or security defaults for all methods, or set a default set of response headers. It is possible to override the defaults by explicitly specifying the option in the options map.

The handler below will behave the same as the transactional one above, however the defaults will be applied to any other handlers declared after the setDefault call:

$undertow
    .setDefault('transactional', true)
    .setDefault('headers' {"content-type": "application/json"})
    .onGet("/rest/endpoint",
        [function ($exchange) {
            return {message: 'Hello World'};
        }])

Freemarker support

As well as Mustache.js we now support Freemarker as a template engine. To use Freemarker as the template engine set template_type: 'freemarker' in the methods options map. If all your templates are Freemarker templates you can use setDefault('template_type', 'freemarker').

Going forward

We would welcome any feedback or suggestions. If you want to contribute, or have any comments head to undertow-dev@lists.jboss.org.

Using Server-Side JavaScript with WildFly

The WildFly 10.0.0.Beta1 release includes support for an experimental new feature, that allows you to use JavaScript on the server side, using the Nashorn JavaScript engine that is built into the JDK. Combined with another new feature that allows you to serve web resources from outside the deployment it is possible to write server-side code with no redeploy of copy step involved.

This feature also allows Java EE resources such as CDI beans to be injected into your JavaScript handlers, allowing you to write your back end using Java, and the front end in JavaScript.

This is a new feature, and as such may change before release based on user feedback.

If you want to skip the tutorial and just want to see the code there are some examples at https://github.com/stuartwdouglas/undertow.js-examples

Getting Started

The first step is to tell WildFly where to find your JavaScript. To do this we create a file WEB-INF/undertow-scripts.conf. In this file you list your server side JavaScript files, one per line. For the sake of this post I will assume we have a single file, example.js.

Note that even though the server JavaScript files are located in the web context, the JavaScript integration will not allow them to be served. If a user requests a server side JS file a 404 will be returned instead.

Setting up hot deployment

To enable hot deployment create a file called WEB-INF/undertow-external-mounts.conf, and in this file add a single line that points to your local workspaces web content root (for example, /Users/stuart/workspace/jsexample/src/main/webapp). With this file in place resources will be served from your local workspace, instead of the deployment. This means that any changes you make to web resources or server-side JavaScript will be immediately visible as soon as you refresh your browser.

This hot deployment mechanism is independent of the server side JS integration, and should work for any web resources.

A simple HTTP endpoint

$undertow
    .onGet("/hello",
        {headers: {"content-type": "text/plain"}},
        [function ($exchange) {
            return "Hello World";
        }])

Add this to your example.js file and point your browser at /hello. You should see your Hello World message appear.

To test out hot deployment try modifying the message and refreshing your browser, the changes should be reflected immediately.

Lets walk through the things that are happening here. $undertow is the entry point for all JavaScript functionality, it contains methods to register HTTP handlers, as well as some other utility methods. The onGet method registers a handler for GET requests. This method takes three parameters, the handler path, an (optional) options map, and a handler injection list (or just a plain handler function, if no injection is required).

This function returns a string, which will be sent to the user as an HTTP response.

Now lets try a simple JSON REST endpoint:

$undertow
    .onGet("/rest/endpoint",
        {headers: {"content-type": "application/json"}},
        [function ($exchange) {
            return {message: 'Hello World'};
        }])

` This is basically the same, the only difference is that a map is returned instead of a string. Any object other than simple strings that are returned from a handler function will be converted into JSON and sent to the client.

Now lets try out an injection, so our handler can interact with the app server. We will start by injecting the default datasource:

$undertow
    .onGet("/rest/members",
        {headers: {"content-type": "application/json"}},
        ['jndi:java:jboss/datasources/ExampleDS', function ($exchange, db) {
            return db.select("select * from members");
        }])

JavaScript injection has the format 'type:name'. It uses angular.js style injection, where you pass in a list of injection specifiers, with the function to inject as the last item in the list. These injections will be resolved at runtime, and passed into the handler function in the order they are specified. The following injection types are supported out of the box:

jndi

This injection is looked up from JNDI, any item that can be looked up in JNDI can be injected into your JavaScript function.

cdi

This allows you to inject CDI beans into a handler. The beans are resolved by name, and as such must have the @Named annotation for them to be resolvable.

$entity

This allows the request entity to be injected directly into your function (for request types that have an entity, such as POST and PUT). By default this is injected as a string, however the following types are also supported.

  • $entity:json injects parsed JSON.

  • $entity:form injects parsed form or multipart encoded data.

Undertow.js will also automatically wrap some objects (such as datasources) in a JavaScript friendly API. The raw JDBC API is not particularly script friendly, so in the example above the DataSource that is injected is actually a wrapper that supports the select, selectOne and query methods. With any wrapped object you can access the underlying Java object using the objects $underlying property.

Going back to our example above, there are two issues with it:

  • We have not set up any tables or added any data to the database.

  • Writing the full JNDI name every time is cumbersome and error prone.

Lets address those issues:

$undertow
    .alias('db', 'jndi:java:jboss/datasources/ExampleDS')
    .onGet("/rest/members",
        {headers: {"content-type": "application/json"}},
        ['db', function ($exchange, db) {
            return db.select("select * from member");
        }]);


var db = $undertow.resolve("db");
try {
    db.query("create table member (id serial primary key not null, name varchar(100), email varchar(100) unique, phone_number varchar(100))");
    db.query("insert into member (id, name, email, phone_number) values (0, 'John Smith', 'john.smith@mailinator.jsp.com', '2125551212')");
    db.query("insert into member (id, name, email, phone_number) values (1, 'Stuart Douglas', 'stuart@notmyrealaddress.com', '0487694837')");
} catch(e) {
    print("DB create failed")
}

We have added two things to this handler, the first is a call to alias, that allows us to alias commonly used names to shorter versions, and the second is some code to create the database. This DB setup code is mostly for example purposes, and is not a recommended approach, as every time this file is modified it will attempt to re-setup the database (and fail, as the table already exists).

Templates

There is also support for templates, at the moment Undertow.js supports Mustache, with plans to support more in the future.

To use a template simply specify the template name in the parameter map, the template will be rendered using the return value of your function as the data. An example is shown below:

$undertow
    .onGet("/hello",
        {template: 'hello.txt', headers: {"content-type": "text/plain"}},
        [function ($exchange) {
            return {name: 'Stuart'};
        }]);

And in hello.txt:

Hello {{name}}

Handling POST requests

POST (and other requests that contain a body) can be handled using entity injection. The body can be injected as a string, or one of the built in parsers can be used to parse JSON or form encoded data (including multipart data).

An example of all three approaches is shown below:

$undertow
    .onPost("/string",
        {headers: {"content-type": "text/plain"}},
        ['$entity', function ($exchange, entity) {
            return "You posted: " + entity;
        }])
    .onPost("/json",
        {headers: {"content-type": "text/plain"}},
        ['$entity:json', function ($exchange, entity) {
                return "You posted: " + entity['name'];
        }])
    .onPost("/form",
        {headers: {"content-type": "text/plain"}},
        ['$entity:form', function ($exchange, entity) {
            return "You posted: " + entity.get('name');
        }])

Going forward

At the moment the following additional features are planned:

  • Support for more template engines

  • Support for declarative security

This feature is very new, and will evolve over the coming months based on user feedback. If you want to contribute, or have any suggestions/comments head to undertow-dev@lists.jboss.org.

back to top