Search

November 17, 2013

Reusing JavaScript Modules in Liferay 6.2

Liferay is a JSR-286 compliant portal server that runs on a variety of different application servers. Previously I have explained how to get Liferay working with JBoss 7.2.0.

Today I would like to focus on getting an example portlet working with Liferay which uses a custom JavaScript module, similar to what we did with the GateIn portal server earlier. Since developing that example I have learned that ninjas are not always well-behaved, so we will stick to some home-grown JavaScript. We will use JavaScript to place a quote on the screen when the user clicks on a certain area of the portlet.

First things first: Liferay does a fair amount of caching for performance reasons. We will need to deactivate the caching otherwise there will be a great deal of pulling of hair and gnashing of teeth while you wonder why your newly deployed JavaScript changes have not made it to the browser. Fortunately Liferay makes this easy by allowing you to pass the flag -Dexternal-properties=portal-developer.properties to the application server on start-up. This incorporates the properties in the /WEB-INF/classes/portal-developer.properties file from the Liferay WAR into the Liferay system properties. If, for whatever reason you require more fine-grained control, you can manipulate the same properties by placing them in your portal-ext.properties file in your Liferay home directory (typically one directory above your application server home directory).

Now that we have an environment conducive to peaceful code development, we can begin. Liferay incorporates the AlloyUI JavaScript library, which is based on the YUI library from Yahoo (specifically the 3.x series sometimes referred to as YUI3, particularly when using a search engine). AlloyUI inherits the module model from YUI and that gives us a nice scaffold to organize our JavaScript.

Unlike GateIn however, Liferay is not "module-aware". That is, we cannot declare our modules and then declare which modules a particular portlet is dependent on. So we’ll need to work around that by placing our modules in a Liferay hooks. Hooks allow us to extend or override core features of Liferay without needing to change Liferay’s source. In our case we will simply use the hook functionality to include our module’s JavaScript in the portal template.

The easy way to get started with a Liferay hook is to use the Liferay IDE, a set of extensions for Eclipse. But I have never been a big fan of Eclipse and I am a big fan of doing things manually at least once for better understanding. So I’ll explain how to do the "hard" way (which really isn’t all that bad anyway.

Create a WAR project (using your favorite method) to hold the hook. Add a /WEB-INF/web.xml file with an empty <web-app> tag:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" metadata-complete="true"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
           http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
</web-app>

Next we’ll need a /WEB-INF/liferay-hook.xml file telling Liferay where to find our JSP extensions. Liferay has documentation for this file and their other XML configuration files at http://docs.liferay.com/portal/6.2/definitions/. Here is the content of our /WEB-INF/liferay-hook.xml file:

<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.2.0//EN"
    "http://www.liferay.com/dtd/liferay-hook_6_2_0.dtd">
<hook>
    <custom-jsp-dir>/WEB-INF/jsp</custom-jsp-dir>
</hook>

Now we need to create the JSP extension file which Liferay will append to the standard JSP. Create /WEB-INF/jsp/html/common/themes/top_js-ext.jspf:

<script type="text/javascript" src="/js/quotify.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>

The first line is for a JavaScript file we will include in the WAR we created. The second line is an example of including a third-party JavaScript library which is hosted elsewhere.

Finally we will add our YUI JavaScript module source in /WEB-INF/jsp/js/quotify.js:

YUI().add('com.example.quotify', function(Y) {
  Y.namespace('com.example');
  Y.com.example.quotify = {
    quotes : [
"If at first you don't succeed... so much for sky-diving. -Henny Youngman",
"I intend to live forever.  So far, so good. -Steven Wright",
"A day without sunshine is like, you know, night. -Steve Martin",
"Get your facts first, then you can distort them as you please. -Mark Twain"],
    fontSizes: ['300%', '400%', '400%', '600%'],
    colors: ['black', 'black', 'blue', 'darkblue', 'darkgreen', 'darkred'],
    fontStyles: [ 'normal', 'italic', 'normal', 'oblique' ],
    fontVariants: [ 'normal', 'normal', 'normal', 'small-caps' ],
    fontWeights: [ 'normal', 'bold', 'bold', 'bolder', 'bolder' ],
    fonts: [ 'serif', 'sans-serif', 'cursive', 'monospace' ],

    random : function(array) {
      return array[Math.floor(Math.random()*array.length)];
    },

    add : function(x, y) {
      var quote = this.random(this.quotes);
      var style = "position:absolute; left:50px; top:" + (y-50) + "px;"
        + "line-height:1.1em; background-color:rgba(128,128,128,.35);"
        + "z-index:9999; width:75%; border-radius:.3em; padding: 20px;";
      var div = Y.Node.create('<div style="' + style + '"></div>');
      div.setStyle('fontSize', this.random(this.fontSizes));

      var bodyNode = Y.one(document.body);
      bodyNode.append(div);

      var me = this;
      var data = {
        words: quote.split(' ').reverse(),
        append: function() {
          var span = Y.Node.create("<span>"
            + Y.Escape.html(this.words.pop()) + " </span>");
          span.setStyle('color', me.random(me.colors));
          span.setStyle('fontFamily', me.random(me.fonts));
          span.setStyle('fontVariant', me.random(me.fontVariants));
          span.setStyle('fontWeight', me.random(me.fontWeights));
          div.append(span);
          // Let's try to give the quote presentation the cadence of Capt. Kirk
          var f = [Math.floor(2*Math.random()), Math.floor(2*Math.random())];
          var r = [300*Math.random(), 200*Math.random(), 1000];
          var when = Math.floor(r[0] + f[0]*(r[1] + f[1]*r[2]));
          if (this.words.length > 0) {
            Y.later(when, this, 'append');
          }
        }
      };
      data.append();
      Y.later(30*1000, this, function(){bodyNode.removeChild(div)});
    },

    quotesOnClick : function (node) {
      var quotify = this;
      node.on('click', function(e) {
        quotify.add(e.pageX, e.pageY);
        e.preventDefault(); // Stop the event's default behavior
        e.stopPropagation(); // Stop the event from bubbling up
      });
    }
  };
}, '1.0', {
    requires: ['node', 'escape', 'yui-later', 'event']
});

A quick primer in case you are not familiar with YUI module system: the module is defined using the YUI.add() function (an instance of YUI is obtained via the global YUI() function). The first argument gives the module name, here we have used com.example.quotify. The second argument is a function that will only be invoked when the module is required. The next argument is an unused version number for the module, then finally an array of modules the new module depends on. Within the module definition function, you are expected to set a property on the passed YUI instance containing your module. Since we are using a multilevel namespace, we use the YUI.namespace() function to ensure it exists. Then we can set the Y.com.example.quotify property. We will get to using the module soon.

That is everything we need for the Liferay hook. Of course, hooks in Liferay provide a great deal more functionality then what we have used here, but this post is going to be long enough as it is. Package your WAR and place it in the Liferay deploy directory.

Now we are ready to create our portlet. Start a new WAR project. Let’s begin with the JavaScript for the portlet which will give a chance to see how to use YUI modules. Create a file named js/portlet.js:

YUI().use(['com.example.quotify', 'node', 'event'], function(Y) {
    Y.on('domready', function() {
        Y.all('.quoteme').each(function(node, index, list) {
            Y.com.example.quotify.quotesOnClick(node);
        });
    });
});

In order to use a YUI module, we invoke the YUI.use() function. The first argument is an array of module names we would like to use. The second argument is a function accepting a YUI instance. The YUI system will pass a YUI instance that has had the requested modules initialized. This instance will be distinct to this YUI.use() invocation; if YUI.use() is used a second time to utilize the module that use will get a distinct instance of Y.com.example.quotify. (The word use now has no meaning to me after that paragraph. I’ll try not to u— include it again.)

Next we tell Liferay to insert a reference to our JavaScript file in the header for any page containing our portlet. This is done in the /WEB-INF/liferay-portlet.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE liferay-portlet-app
        PUBLIC "-//Liferay//DTD Portlet Application 6.2.0//EN"
        "http://www.liferay.com/dtd/liferay-portlet-app_6_2_0.dtd">
<liferay-portlet-app>
    <portlet>
        <!-- TODO: Use the name of your portlet from the portlet.xml here -->
        <portlet-name>name of your portlet from portlet.xml</portlet-name>
        <instanceable>true</instanceable>
        <header-portlet-javascript>/js/portlet.js</header-portlet-javascript>
    </portlet>
</liferay-portlet-app>

Here we reference our JavaScript file in the <header-portlet-javascript> tag. The <instanceable> tag indicates to Liferay whether the portlet may be included on the page once (false) or many times (true). You may need to adjust it depending on whatever portlet you decided to turn into Frankenstein’s monster by sewing my quotify module onto it.

Next make sure that your portlet output has some <span> or <div> tags with the quoteme class defined, e.g. <span class="quoteme">I’m just some innocent text</span>. The Y.on(domready,…) idiom executes the function body when the DOM is available; at that point onclick handlers are added to every DOM element with the quoteme class defined.

In summary, Liferay’s support for JavaScript modules, while not as integrated as GateIn’s, is quite serviceable. Liferay supports multiple hooks on a single server, so you can have a separate hook for each JavaScript module if you want and you can easily distribute your modules to third parties as well.

One advantage of GateIn’s approach over the approach above is that GateIn can dynamically include the necessary JavaScript files on the page on demand and omit the unnecessary. However, given the extensibility of the Liferay portal, it is not hard to imagine implementing hooks or extensions it to handle modules the same way. One would need to hook into the deployment event to scan and register the modules and then include the right <script> tags on render.

November 10, 2013

Liferay 6.2 Portal on JBoss 7.2.0

Liferay is a JSR-286 (also known as Portal 2.0) compliant portal (and a whole lot more). Since I am in the market for a portal server for an upcoming project, I figured I needed to check Liferay out. The folks at Liferay have bundled version 6.2 with a number of different open-source application servers, including JBoss 7.1.1, but what fun would it be to simply download a bundle? Liferay is also available as a war download for deployment on existing (and closed-source) application servers. So let’s see if we can get Liferay running on the JBoss 7.2.0 server we built previously.

We will need the Liferay war (liferay-portal-6.2.0-ce-ga1-20131101192857659.war) and the Liferay dependencies package (liferay-portal-dependencies-6.2.0-ce-ga1-20131101192857659.zip) available from the Additional Files tab on the Liferay download page. I will be using a PostgreSQL database as well, so I’ll need the PostgreSQL JDBC driver (postgresql-9.3-1100.jdbc4.jar) as well. Feel free to substitute the driver for your favorite DBMS.

First, Liferay will be using the directory above the JBoss installation as a installation directory, so let’s create that directory and extract JBoss:

$ mkdir liferay
$ cd liferay
$ unzip -q ../jboss-as-7.2.0.Final.zip

Liferay requires that you install the dependencies at the application server level; for JBoss that means creating a module for Liferay. The Liferay installation instructions bundle the JDBC driver with the Liferay module. That feels wrong to me, so we’ll create a separate module for the JDBC driver. In this case I am using PostgreSQL, but this can easily be adapted for any driver.

$ cd jboss-as-7.2.0.Final/modules
$ mkdir -p org/postgresql/main
$ cp ../../../postgresql-9.3-1100.jdbc4.jar org/postgresql/main/
$ mkdir -p com/liferay/portal/main
$ cd com/liferay/portal/main
$ unzip -q \
../../../../../../../liferay-portal-dependencies-6.2.0-ce-ga1-20131101192857659.zip
$ mv liferay-portal-dependencies-6.2.0-ce-ga1/* ./
$ rmdir liferay-portal-dependencies-6.2.0-ce-ga1
$ cd ../../../../../..

Next you’ll need to create the two module.xml files. First, for PostgreSQL create the file jboss-as-7.2.0.Final/modules/org/postgresql/main/module.xml with contents:

<?xml version="1.0"?>
<module xmlns="urn:jboss:module:1.0" name="org.postgresql">
    <resources>
       <resource-root path="postgresql-9.3-1100.jdbc4.jar" />
    </resources>
    <dependencies>
       <module name="javax.api" />
       <module name="javax.transaction.api" />
    </dependencies>
</module>

Next for the Liferay module, note that we will be adding a dependency on the PostgreSQL module. The file is jboss-as-7.2.0.Final/modules/com/liferay/portal/main/module.xml:

<?xml version="1.0"?>
<module xmlns="urn:jboss:module:1.0" name="com.liferay.portal">
    <resources>
       <resource-root path="hsql.jar" />
       <resource-root path="portal-service.jar" />
       <resource-root path="portlet.jar" />
    </resources>
    <dependencies>
       <module name="javax.api" />
       <module name="javax.mail.api" />
       <module name="javax.servlet.api" />
       <module name="javax.servlet.jsp.api" />
       <module name="javax.transaction.api" />
       <module name="org.postgresql" />
    </dependencies>
</module>

While we are mucking around with the modules, it seems like a good time to make the Liferay-recommended changes to the system modules of JBoss. Open up jboss-as-7.2.0.Final/modules/system/layers/base/sun/jdk/main/module.xml and add the following <path> tags under the existing <paths> tag:

<path name="com/sun/crypto" />
<path name="com/sun/org/apache/xml/internal/resolver" />
<path name="com/sun/org/apache/xml/internal/resolver/tools" />

The Liferay documentation at this point indicates there are some steps to do to work around JBPAPP6-932. However, that bug only affects environments using the IBM JDK, which I am not planning on doing. Further, I am using JBoss 7.2.0 and the patch is for 7.1.x; it is not clear if the bug exists in 7.2.0 or if the patch is appropriate for 7.2.0. The upshot is I am skipping that step but you may want to consider if you need it.

Next up are some edits to the JBoss configuration file, standalone-full.xml. (I typically configure my servers to use standalone-full.xml instead of just standalone.xml, YMMV.) We’ll start by adding some system properties following the <extensions> tag:

<system-properties>
  <property name="org.apache.catalina.connector.URI_ENCODING"
    value="UTF-8"/>
  <property
    name="org.apache.catalina.connector.USE_BODY_ENCODING_FOR_QUERY_STRING"
    value="true"/>
</system-properties>

We also need a to set a timeout for the deployment scanner by adding the deployment-timeout attribute to the existing deployment-scanner tag:

<subsystem xmlns="urn:jboss:domain:deployment-scanner:1.1">
  <deployment-scanner path="deployments" relative-to="jboss.server.base.dir"
    scan-interval="5000" deployment-timeout="240"/>
</subsystem>

Next we need to configure the Liferay login system. Add a <security-domain> tag to the existing <security-domains> tag in the existing <subsystem xmlns="urn:jboss:domain:security:1.2"> tag

<security-domain name="PortalRealm">
  <authentication>
    <login-module code="com.liferay.portal.security.jaas.PortalLoginModule"
      flag="required"/>
  </authentication>
</security-domain>

We will be replacing the standard JBoss welcome application with Liferay later, so we need to set the enable-welcome-root attribute to false on the existing <virtual-server> tag. We will also set the JSP mode to development by adding <configuration> and <jsp-configuration> tags in the web <subsystem>:

<subsystem xmlns="urn:jboss:domain:web:1.4" default-virtual-server="default-host" native="false">
  <configuration>
    <jsp-configuration development="true"/>
  </configuration>
  <connector name="http" protocol="HTTP/1.1" scheme="http"
    socket-binding="http"/>
  <virtual-server name="default-host" enable-welcome-root="false">
    <alias name="localhost"/>
    <alias name="example.com"/>
  </virtual-server>
</subsystem>

While we are here, let’s create a mail session for Liferay to use by modifying the existing <mail-session> tag in the <subsystem xmlns="urn:jboss:domain:mail:1.0"> tag:

<subsystem xmlns="urn:jboss:domain:mail:1.0">
  <mail-session
    jndi-name="java:/mail/MailSession"
    from="MAIL_ADDRESS">
    <smtp-server outbound-socket-binding-ref="mail-smtp">
      <login name="MAIL_USER" password="MAIL_PASSWORD"/>
    </smtp-server>
  </mail-session>
</subsystem>

We also need to tell JBoss that the socket binding mail-smtp should talk to the right mail server. Change the host attribute of the existing <remote-destination> tag for the mail-smtp socket binding:

<outbound-socket-binding name="mail-smtp">
  <remote-destination host="MAIL_HOST" port="25"/>
</outbound-socket-binding>

Of course, replace the MAIL_ADDRESS, MAIL_HOST, MAIL_USER and MAIL_PASSWORD tokens in the above with the proper values for your environment.

Finally we create a JDBC data source for Liferay to use in two steps. First, add a <datasource> tag to the existing <datasources> in the <subsystem xmlns="urn:jboss:domain:datasources:1.1"> subsystem:

<datasource jta="true" jndi-name="java:/jdbc/LiferayPool"
  pool-name="LiferayPool" enabled="true" use-java-context="true" use-ccm="true">
  <connection-url>jdbc:postgresql://DB_SERVER:5432/DB_DATABASE</connection-url>
  <driver>postgresql</driver>
  <pool>
    <min-pool-size>1</min-pool-size>
    <max-pool-size>20</max-pool-size>
    <prefill>true</prefill>
  </pool>
  <security>
    <user-name>DB_USERNAME</user-name>
    <password>DB_PASSWORD</password>
  </security>
    <validation>
        <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker"/>
        <validate-on-match>false</validate-on-match>
        <background-validation>false</background-validation>
    </validation>
    <statement>
        <prepared-statement-cache-size>16</prepared-statement-cache-size>
        <share-prepared-statements>true</share-prepared-statements>
    </statement>
</datasource>

Then add a <driver> tag to the existing <drivers> tag within the <datasources> tag:

<driver name="postgresql" module="org.postgresql">
  <xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
</driver>

Again, replace the DB_SERVER, DB_DATABASE, DB_USERNAME and DB_PASSWORD tokens with the correct values for your environment. If you are using a different database server, you’ll have to change the <connection-url>, the class-name for the <validation-connection-checker> and the <xa-datasource-class> as well, but you knew that already. For the database, use an empty schema and Liferay will populate it when it first starts.

Liferay requires certain system properties to be set when JBoss is started. Use your favorite mechanism to ensure the following values are set:

-Dfile.encoding=UTF-8
-Djava.net.preferIPv4Stack=true
-Djava.security.manager
-Djava.security.policy=$JBOSS_HOME\bin\server.policy
-Djboss.home.dir=$JBOSS_HOME

If you are developing on Windows, use the standalone/standalone.conf.bat file. If you have set up JBoss on CentOS per my instructions, you can use the /etc/jboss-as/jboss-as.properties file (drop the -D prefixes of course).

Don’t forget to set the set the -Xmx and -XX:MaxPermSize options while you are editing files in the bin directory. Liferay recommends -Xmx1024m -XX:MaxPermSize=256m.

The Liferay documentation suggests setting the user.timezone property to GMT as well. I encountered problems with that setting when deploying JSP changes. The Liferay deployer left the JSP timestamped with GMT time value on the file system; since these were always in the past for me the JSPs always looked older than their last compile time from JBoss’s point of view. So they did not get recompiled. So I have skipped that setting.

While we are fooling around with the JBoss start-up mechanism, it seems like a good time to create the server.policy file in jboss-as-7.2.0.Final/bin:

grant {
    permission java.security.AllPermission;
};

Next we need to define the portal-ext.properties file in the liferay directory. We tell Liferay where in the JNDI to find our data source and mail session. We also disable the mechanism where it tries to launch a browser every time it starts.

jdbc.default.jndi.name=java:/jdbc/LiferayPool
mail.session.jndi.name=java:/mail/MailSession
browser.launcher.url=

Something to note here is that the Liferay documentation omits the first slash in the JNDI names which causes issues on JBoss 7.2.0. Include the slash and make sure your names are consistent between the portal-ext.properties and standalone-full.xml files.

OK, we are to install Liferay itself. We extract the contents of the Liferay war download into a new directory under deployments. Then we need to delete the eclipselink.jar from the lib directory. Finally we create a .dodeploy file to trigger the deployment:

$ cd jboss-as-7.2.0.Final/standalone/deployments/
$ mkdir liferay-portal.war
$ cd liferay-portal.war
$ jar xf ../../../../../liferay-portal-6.2.0-ce-ga1-20131101192857659.war
$ rm WEB-INF/lib/eclipselink.jar
$ cd ..
$ touch liferay-portal.war.dodeploy

Note that we are differing from the Liferay instructions again, mainly because JBoss 7.2.0 does not have a ROOT.war deployment to clear out. Instead we use the much clearer name liferay-portal.war.

At this point you are ready to go. Fire up JBoss and enjoy your Liferay portal at http://localhost:8080/. Don’t forget that we used the standalone-full.xml file, not the default standalone.xml file when you start your application server.

November 3, 2013

Packaging GateIn JavaScript Modules in Separate WARs

I have been messing around with the GateIn Portal Server in order to evaluate it for an upcoming project. One nice aspect of the portal is the way JavaScript is handled. JavaScript in GateIn is split into modules and managed via the RequireJS library. This allows the portlet developer to keep their JavaScript isolated and only include the dependencies they require. It also allows for re-use of modules defined in one portlet in other portlets. It doesn’t take a lot of imagination to picture the disaster JavaScript could become on a portal which doesn’t provide isolation and re-use, especially if multiple organizations are providing portals.

Starting with the solid foundation provided by GateIn, we just need a couple of tweaks to make things even better. First, one potential gotcha is that the modules all live in the same namespace across the portal. So we will borrow the solution from Java and prefix our module names with the inverse of the our domain name. For the purposes of this post, I will use the prefix com.example.

The second thing is that modules are defined in portlets. This could be an issue if you create a re-usable module in portlet X, it gets used in portlet Y and then down the road portlet X is no longer needed and is removed. The solution is to package our re-usable modules in their our war files separate from the portlets.

Which leads me to the point of this post, demonstrating how to package a JavaScript module for GateIn in its own war archive. Originally inspired by the Daily WTF, I was going to create modules for adding unicorns to the portal. But after going to http://www.cornify.com to borrow the JavaScript, I discovered the much cooler http://ninjafy.com. So we will be adding ninjas to the portal instead.

Since I originally posted this entry, it has come to my attention that Google is warning users that ninjafy.com may be serving malware. So the safe thing to do would be to get your own (known safe) ninja pictures and package them with your module. Then adjust the URL parameters in the script below.

Just to demonstrate dependencies and module interaction, we will create two modules, com.example.ninjafy and com.example.module. Create a js directory under the web root of the war and add the file ninjafy.js:

// Adapted from http://ninjafy.com/js/ninjafy.js
define(function() {
    return {
        add : function() {
            var ninjafy_url = '/path/to/images/';
            var file = 'ninja_';
            var count = 7;
            file += Math.ceil(Math.random()*count) + '.gif';
            var div = document.createElement('div');
            div.style.position = 'fixed';

            var height = Math.random()*.9;
            if (typeof(window.innerHeight) == 'number') {
                height = height*window.innerHeight+'px';
            } else if(document.documentElement
                      && document.documentElement.clientHeight) {
                height = height+document.documentElement.clientHeight+'px';
            } else {
                height = height*100+'%';
            }

            div.style.top = height;
            div.style.left = Math.random()*90 + '%';
            var img = document.createElement('img');
            img.setAttribute('src',ninjafy_url+file);
            var body = document.getElementsByTagName('body')[0];
            body.appendChild(div);
            div.appendChild(img);
        }
    };
});

Here we have adapted the original ninjafy.js script into a RequireJS module. The RequireJS module takes the form of a function that returns the module. The result of the function is passed to other modules that depend on this one. We defined one function in the ninjafy module, add().

(A quick note about licensing - I couldn’t find any license information on the ninjafy site but the tone of the site certainly implies that re-using their code is encouraged.)

Next we define our second module, which will use the com.example.ninjafy modules. Create a file named example-module.js in the js directory:

define(["ninjafy"], function(ninjafy) {
    return {
        ninjasOnClick: function(element) {
            element.onclick = function() {
                ninjafy.add();
                return false;
            };
        }
    };
});

Here we can see that the com.example.ninjafy module is passed as the variable ninjafy. In this module we define a ninjasOnClick() function which adds a ninja-adding onclick handler to the given HTML element.

Next we have everybody’s favorite part of JEE development: configuring the XML files. The first two are simple. Create a web.xml that contains only a displayName element (see https://issues.jboss.org/browse/GTNPORTAL-3107). Then create an "empty" portlet.xml containing only the top-level <portlet-app> element. These xml files belong in the WEB-INF directory of the war of course.

Next we define the gatein-resources.xml file, also in the WEB-INF directory. This file defines the modules and manages their dependencies:

<?xml version="1.0" encoding="UTF-8"?>
<gatein-resources
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_resources_1_3
            http://www.gatein.org/xml/ns/gatein_resources_1_3"
        xmlns="http://www.gatein.org/xml/ns/gatein_resources_1_3">
    <module>
        <name>com.example.ninjafy</name>
        <script>
            <path>/js/ninjafy.js</path>
        </script>
    </module>
    <module>
        <name>com.example.module</name>
        <script>
            <path>/js/example-module.js</path>
        </script>
        <depends>
            <module>com.example.ninjafy</module>
            <as>ninjafy</as>
        </depends>
    </module>
</gatein-resources>

For the most part this file is self-explanatory. Note in the <depends> tag for the com.example.module module we map the com.example.ninjafy module to the name ninjafy, as expected by the example-module.js script.

Now if you build your war file and deploy it to your GateIn server, you will have a nifty new JavaScript module to play with. But what fun is a new library if we don’t actually use it? Assuming you have a working portlet to start with, I’ll show you how to use the new modules.

When creating JavaScript for portlets in GateIn, the recommended way is to define a module for the portlet. This will not be a re-usable module as before, but a module just for the portlet. Create a file named js/portlet.js (remember we are working inside the war for your portlet now, not the same war we created above to hold the JavaScript modules):

define(["example","$"], function(example, $) {
    $(document).ready(function(){
        $(".ninjame").each(function(index, element) {
            example.ninjasOnClick(element);
        });
    });
});

In this case we are expecting the com.example.module module as example and jQuery as $. GateIn supplies a jQuery module for you. (You can also use a different version of jQuery if you are picky about it, see the GateIn JavaScript documentation for details.) Our module walks the DOM and invokes our ninjaOnClick() function for each HTML element with the class ninjame.

Next you need to configure this module in the gatein-resources.xml for your portlet:

<?xml version="1.0" encoding="UTF-8"?>
<gatein-resources
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_resources_1_3
            http://www.gatein.org/xml/ns/gatein_resources_1_3"
        xmlns="http://www.gatein.org/xml/ns/gatein_resources_1_3">
    <portlet>
        <!-- TODO: Replace the following with the actual name. -->
        <name>Use the name from your portlet.xml</name>
        <module>
            <script>
                <path>/js/portlet.js</path>
            </script>
            <depends>
                <module>com.example.module</module>
                <as>example</as>
            </depends>
            <depends>
                <module>jquery</module>
                <as>$</as>
            </depends>
        </module>
    </portlet>
</gatein-resources>

Then you just need to add the CSS class ninjame to some elements in your portlet HTML. Fire up GateIn and your browser and add ninjas to your heart’s content.

A couple of final notes: I have found it is sometimes necessary to restart GateIn for changes to modules to take effect. And it is always necessary to tell your browser to reload the JavaScript after making changes (shift-reload in Firefox). Finally, GateIn by default will bundle up the shared JavaScript into one merged file which has been minimized. Adding the flag -Dexo.product.developing=true to the arguments when starting GateIn will suppress this and lead to a much better JavaScript debugging experience.

October 27, 2013

Building GateIn 3.6.3

GateIn is a JSR-286 (also known as Portal 2.0) compatible portal based on the JBoss application server (soon to be known as WildFly). The latest version of GateIn with a provided build is GateIn 3.6.0 and is bundled with JBoss 7.1.1. (There is also a beta build of Red Hat JBoss Portal 6.1.0 which bundles GateIn with JBoss EAP and thus carries a more restrictive license.) Note that GateIn is bundled with the application server and is not an add-on to an existing application server.

In the spirit of do it yourself, I figured why not try bundle GateIn with a later version of JBoss community? I started down the road of using JBoss 7.2.0, but unfortunately GateIn is currently incompatible with that version. So we will have to settle for JBoss 7.1.3. Since there is no download-able build for JBoss 7.1.3 we will need to build it ourselves following the model we used for 7.2.0. And since we are compiling things ourselves, why not grab the latest GateIn release, 3.6.3, and compile it as well?

I will be working on a CentOS 6.4 machine created using the minimal DVD. I will skip the preliminaries which you can follow at my instructions for the JBoss 7.2.0 build. Below we download JBoss and GateIn and build JBoss.

[jboss@wxyz ~]$ wget -q -O JBoss-7.1.3.Final.tar-src.tar.gz \
                https://github.com/jbossas/jboss-as/archive/7.1.3.Final.tar.gz
[jboss@wxyz ~]$ wget -q -O GateIn-3.6.3.Final-src.tar.gz \
                https://github.com/gatein/gatein-portal/archive/3.6.3.Final.tar.gz
[jboss@wxyz ~]$ cd src
[jboss@wxyz src]$ tar zxf ../JBoss-7.1.3.Final-src.tar.gz
[jboss@wxyz src]$ tar zxf ../GateIn-3.6.3.Final-src.tar.gz
[jboss@wxyz src]$ ls -1
gatein-portal-3.6.3.Final
jboss-as-7.1.3.Final
[jboss@wxyz src]$ cd jbo ss-as -7.1.3.Final/
[jboss@wxyz jboss-as-7.1.3.Final]$ ./build.sh -DskipTests -Drelease=true

If you are adapting these instructions for different versions, you can get the download URL for different JBoss versions at https://github.com/jbossas/jboss-as/tags (although in the future you’ll need to use https://github.com/wildfly/wildfly/tags). The GateIn tags are at https://github.com/gatein/gatein-portal/tags.

Next we need to set up for the GateIn build. Unlike JBoss, GateIn doesn’t package maven with the source tarball. I hoped to piggyback on the maven from the JBoss 7.1.3 tarball but unfortunately it is version 3.0.2 and the GateIn build requires at least 3.0.4 (so close!). So we need to install maven:

[root@wxyz ~]# wget http://supergsego.com/apache/maven/maven-3/3.1.1/binaries/apache-maven-3.1.1-bin.tar.gz
[root@wxyz ~]# cd /opt
[root@wxyz opt]# mkdir maven
[root@wxyz opt]# cd maven
[root@wxyz maven]# tar zxf ~/apache-maven-3.1.1-bin.tar.gz
[root@wxyz maven]# ln -s apache-maven-3.1.1/    latest

As the final touch for maven, we create the file /etc/prof ile.d/maven.sh so that maven is available automatically for all users (if you use another shell, create the corresponding script, e.g. /etc/prof ile.d/maven.csh):

# Maven environment for bash, sh
#
M2_HOME=/opt/maven/latest
PATH=$PATH:$M2_HOME/bin

OK, now that that brief detour is over, we can get back to building GateIn. We need to set up a directory where the build will pull the JBoss servers that it will package GateIn with. Then we can do the build.

[jboss@wxyz src]$ pwd
/opt/jboss/src
[jboss@wxyz src]$ mkdir servers
[jboss@wxyz src]$ cd servers
[jboss@wxyz servers]$ tar zxf ../jboss-as-7.1.3.Final/dist/target/jboss-as-7.1.3 .Final.tar.gz
[jboss@wxyz servers]$ export SERVERS_DIR=$(pwd)
[jboss@wxyz servers]$ cd ../gatein-portal-3.6.3.Final/
[jboss@wxyz gatein-portal-3.6.3.Final]$ \
    mvn  install -DskipTests -Dservers.dir=$SERVERS_DIR -Dgatein.dev=jbossas713
[jboss@wxyz ~]$ cd
[jboss@wxyz ~]$ mkdir jboss-as-7.1.3.Final-gatein-3.6.3. Final
[jboss@wxyz ~]$ cd jboss-as-7.1.3.Final-gatein-3.6.3.Final/
[jboss@wxyz jboss-as-7.1.3.Final-gatein-3.6.3.Final]$ cp -r \
    ~/src/gatein-portal-3.6.3.Final/packaging/jboss-as7/pkg/target/jboss/* .

Above we have copied the result of the build from packaging/jboss-as7/pkg/target/jboss into a directory under ~jboss. Now we can follow the remaining steps from my 7.2.0 build to install this version of JBoss with GateIn as a service in CentOS.

October 20, 2013

Build and Install JBoss 7.2.0 on CentOS 6.4

As you may or may not be aware, a great many changes have been happening to the JBoss application server since being purchased by Red Hat. Most importantly, the application server is being rebranded as WildFly for version 8. ("Why?" is the first entry on the WildFly FAQ if you are curious.) But since WildFly is not quite ready as of this writing (but looks real close), we are going to deal with the latest community release, JBoss AS 7.2.0.

Before the name change to WildFly, there was a split in JBoss into a 7.x community edition and a 6.x EAP edition. The community edition remains the open source version free to use as before. The EAP edition requires a subscription to use for production purposes (but is free for development). The EAP edition is based on the community edition, typically taking the "Final" version of the community edition as the "Alpha" of the corresponding EAP edition (although keep in mind that the community edition versions are 7.x while the EAP is 6.x). This appears to be a split in the mold of Red Hat Linux begetting Fedora and Red Hat Enterprise.

Whew, that is a lot of change and I haven’t even mentioned that JBoss AS 7 appears to be a complete rewrite. The configuration files and tools are completely different, so if are coming from JBoss 5 or 6, be prepared to invest a little time to get familiar with the new JBoss, er, WildFly.

OK, we are getting closer to the good stuff. In addition to all the changes mentioned above, Red Hat (or JBoss.org or WildFly.org whoever is responsible for the community editions of JBoss AS 7) has stopped providing builds of the JBoss AS 7 line after 7.1.1. Since then there have been versions 7.1.2, 7.1.3 and 7.2.0 but no corresponding build on the JBoss AS download page. Fortunately it is easy to get the source and build JBoss yourself.

We will be using a new CentOS 6.4 box created using the CentOS minimal DVD. We’ll start by installing Java; instead of going to Oracle, I decided to try the OpenJDK from the CentOS repositories to see how that goes:

[root@wxyz ~]# yum install java-1.7.0-openjdk java-1.7.0-openjdk-devel wget

You’ll notice I threw in wget since we will need it shortly.

Next we create a jboss user on the server to build and run JBoss as. (You may want to call the user jboss-as since the JBoss scripts reference this user by default. I already have a convention of using the user jboss and I want to stick with it.)

[root@wxyz ~]# useradd --system --comment JBossAS --create-home \
--home /opt/jboss --user-group jboss

Now we are ready to download the JBoss source. Fortunately for us, github provides tarballs of tags so we can easily download the JBoss source for 7.2.0:

[jboss@wxyz ~]$ wget https://github.com/jbossas/jboss-as/archive/7.2.0.Final.tar.gz
[jboss@wxyz ~]$ mv 7.2.0.Final /opt/jboss/JBoss-7.2. 0.Final.tar.gz
[jboss@wxyz ~]$ mkdir src
[jboss@wxyz ~]$ cd src
[jboss@wxyz src]$ tar xzf JBoss-7.2.0.Final.tar.gz
[jboss@wxyz src]$ cd jboss-as-7.2.0.Final/
[jboss@wxyz jboss-as-7.2.0.Final]$ # This next command kicks off the build
[jboss@wxyz jboss-as-7.2.0.Final]$ ./build.sh -DskipTests -Drelease=true

If you take a look at the README.md file in the download there are some basic instructions on how to do the build. One pleasant surprise is that there is no need to install maven. We do want a couple of tweaks to the command line they suggest however in order to skip the unit tests and get a release tarball. After running the command above you will find the tarball in the dist/target directory.

[jboss@wxyz jboss-as-7.2.0.Final]$ ls dist/target
archive-tmp  jboss-as-7.2.0.Final-src.tar.gz  jboss-as-7.2.0.Final-src.zip
jboss-as-7.2.0.Final.tar.gz  jboss-as-7.2.0.Final.zip
[jboss@wxyz jboss-as-7.2.0.Final]$ cd
[jboss@wxyz ~]$ tar zxvf src/jboss-as-7.2.0.Final/dist/target/jboss-as-7.2.0.Final.tar.gz

Now that we have a build of JBoss AS 7.2.0 on the system, we need to configure CentOS to treat it like a service. JBoss comes with some scripts in the bin/init.d directory that will help with this. I am using the standalone server, but it is not difficult to modify the instructions for the domain server. First up is the /etc/jboss-as/jboss-as.conf file.

[root@wxyz ~]# cd /etc
[root@wxyz etc]# mkdir jboss-as
[root@wxyz etc]# cd jboss-as/
[root@wxyz jboss-as]# cp /opt/jboss/jboss-as-7.2.0.Final/bin/init.d/jboss-as.conf ./
[root@wxyz jboss-as]# # We'll just create an empty properties file for now
[root@wxyz jboss-as]# touch jboss-as.properties

Next edit the file. We need to define a few environment variables:

# General configuration for the init.d scripts,
# not necessarily for JBoss AS itself.

# The username who should own the process.
#
JBOSS_USER=jboss

# The amount of time to wait for startup
#
# STARTUP_WAIT=30

# The amount of time to wait for shutdown
#
# SHUTDOWN_WAIT=30

# Location to keep the console log
#
JBOSS_CONSOLE_LOG=/var/log/jboss-as/console.log

# JBoss installation directory (default is /usr/share/jboss)
JBOSS_HOME=/opt/jboss/jboss-as-7.2.0.Final

# Server configuration file, using full JEE 6 profile
JBOSS_CONFIG=standalone-full.xml

# Need to modify the init script to account for these
JBOSS_OPTS="--properties=/etc/jboss-as/jboss-as.properties"

Just in case you are confused on the two files, jboss-as.conf is a bash script that is sourced by the JBoss init script; use it to set environment variables for the scripts. As for jboss-as.properties, it is a Java properties file that is read by JBoss on start up. Use it to set JBoss system properties (we will get to an example shortly).

Next up we will deposit the init script in /etc/init.d and set the server to start on boot:

[root@wxyz jboss-as]# cd /etc/ini t.d/
[root@wxyz init.d]# cp /opt/jboss/jboss-as-7.2.0.Final/bin/init.d/jboss-as-standalone.sh ./
[root@wxyz init.d]# mv jboss-as-standalone.sh jboss-as
[root@wxyz init.d]# chkconfig --add jboss-as
[root@wxyz init.d]# chkconfig --level 345 jboss-as on
[root@wxyz init.d]# chkconfig --list jboss-as
jboss-as        0:off   1:off   2:off   3:on    4:on    5:on    6:off

As-is the scripts do not allow passing any additional options to the JBoss process, so we need to edit the /etc/init.d/jboss-as script to add the $JBOSS_OPTS variable whenever JBoss is starting. Here is the edited snippet (I’ve added some line breaks for readability):

  if [ ! -z "$JBOSS_USER" ]; then
    if [ -r /etc/rc.d/init.d/functions ]; then
      daemon --user $JBOSS_USER LAUNCH_JBOSS_IN_BACKGROUND=1 \
        JBOSS_PIDFILE=$JBOSS_PIDFILE $JBOSS_SCRIPT -c $JBOSS_CONFIG \
        $JBOSS_OPTS 2>&1 > $JBOSS_CONSOLE_LOG &
    else
      su - $JBOSS_USER -c "LAUNCH_JBOSS_IN_BACKGROUND=1 \
        JBOSS_PIDFILE=$JBOSS_PIDFILE $JBOSS_SCRIPT -c $JBOSS_CONFIG \
        $JBOSS_OPTS" 2>&1 > $JBOSS_CONSOLE_LOG &
    fi
  fi

Next up we create the log directories and redirect the JBoss log directory to /var/log/jboss-as and the tmp directory to /tmp/jboss-as

[root@wxyz ~]# mkdir /var/log/jboss-as
[root@wxyz ~]# chown jboss:jboss /var/log/jboss-as/

To get JBoss to use these directories, we set the system properties for them in /etc/jboss-as/jboss-as.properties:

# System properties for jboss-as
jboss.server.log.dir=/var/log/jboss-as
jboss.server.temp.dir=/tmp/jboss-as

At this point you can start the server using the command service jboss-as start. You won’t be able to do much however, unless you like browsing via wget since the server is bound to the localhost interface only (and we are working on a headless server, i.e. no GUI web browser). So we’ll need to bind JBoss to all interfaces and open up the CentOS firewall to allow browsing to JBoss and the management console. Keep in mind that this may not be exactly what you want in a production environment or in environment where unknown users might access your server (like shared hosting).

First we’ll edit the /etc/jboss-as/jboss-as.properties file:

# System properties for jboss-as
jboss.bind.address=0.0.0.0
jboss.bind.address.management=0.0.0.0
jboss.server.log.dir=/var/log/jboss-as
jboss.server.temp.dir=/tmp/jboss-as

Depending on how your local network is setup you may need further configuration for JBoss to be completely happy binding to all interfaces. If your DNS recognizes the name of the CentOS server you are working on, congratulations, you are all set. On the other hand, if DNS is not configured for the server, you will need to add an entry to the /etc/hosts file pointing the name of the server to the external IP.

Next we need to open the firewall for ports 8080 (regular JBoss) and 9990 (the management console). Be careful when adjusting the firewall. The commands below use hard-coded line numbers that may not be appropriate for whatever system you are using. If you think you have screwed up your firewall use service iptables restart to reset it. Of course, this won’t work after you issue service iptables save so be extra sure before you save the rules. You will also need to replace 10.0.0.0 with your subnet below.

[root@wxyz ~]# # 8080 for JBoss applications
[root@wxyz ~]# iptables --insert INPUT 5 \
         --match state \
         --state NEW \
         --protocol tcp \
         --destination-port 8080 \
         --source 10.0.0.0/24 \
         --jump ACCEPT
[root@wxyz ~]# # 9990 for JBoss management
[root@wxyz ~]# iptables --insert INPUT 6 \
         --match state \
         --state NEW \
         --protocol tcp \
         --destination-port 9990 \
         --source 10.0.0.0/24 \
         --jump ACCEPT
[root@wxyz ~]# # Verify the state of the firewall
[root@wxyz ~]# iptables -L --line-numbers
[root@wxyz ~]# # Go and test it out before doing the following
[root@wxyz ~]# service iptables save

At this point you should be ready to go. Start up JBoss and try accessing it from your desktop.