Skip navigation
1 2 3 Previous Next

carlavmott

96 posts

GlassFish 3.1 supports creating and managing instances on multiple hosts from a central location (the DAS).

 

GlassFish 3.1 supports creating and managing instances on multiple hosts from a central location (the DAS). The server software uses SSH to communicate to the remote systems where the instances reside and Joe's blog contains useful information on setting up SSH in a way that GlassFish can take advantage.    In this blog I talk about managing those instances when the user sets the master password to something other than the default.

It is recommended that users change the default master password for security reasons.  Since GlassFish never transmits the master password or associated file over the network, the user must take action on the remote hosts to allow the system to manage the instances from a central location.  Commands such asstart-instance do not have a mechanism that allows the user to enter the master password but they do look for a master password file in the agent directory of the node associated with that instance.  This means that each instance on that node uses the same master password.  We have updated the commandchange-master-password so that it creates themaster-password file for a node. Commands with the --savemasterpassword option will create or update the master-password file.

Let's look at an example.  In this case, I create a new domain setting the master password to 'welcome1'  and start the domain.  I create an SSH node for the remote host I plan to use for the instances.   I then create an instance on a remote node using the command create-instancewhich I run on the DAS.    Note that I can create the instance from the DAS but I can not start it unless the master password for the instance matches the master password for the DAS.  At that point I have to go to the instance machine and run the change-master-password command with the --savemasterpassword option set to true so that the master-password file is created in the node's agent directory.  Once I do that I can go to the DAS machine and manage the instance.  Since the master pasword is associated with the node I can then create additional instances from the DAS machine and start or stop them without having to go to the remote host.  I have added the commands that need to be run below.

1) Create and start a domain with the master-password set to "welcome1" using the command .  Note that I did not set a password for admin user.

asadmin create-domain --savemasterpassword true domain2
asadmin start-domain domain2


2) Create an SSH node

 asadmin create-node-ssh --nodehost glassfish1.sfbay.sun.com --installdir /export/glassfish3 node2


3) Create an instance from the DAS.  This creates the instance configuration information and the instance file system.

asadmin create-instance --node node2 ins2


4) At this point the instance is created but it can not be started by the start-instance command because there is nomaster-password file in the agent directory for that node. That file must exist and it must have the same password as the master password on the DAS. To create that file run the following command on the instance machine.  If I try to start the instance I get the following error:

asadmin start-instance ins2
remote failure: Could not start instance ins2 on node node2 (glassfish1.sfbay.sun.com).

Command failed on node node1 (glassfish1.sfbay.sun.com): The Master Password is required to start the domain.  No console, no prompting possible.  You should either create the domain with --savemasterpassword=true or provide a password file with the --passwordfile option.Command start-local-instance failed.

To complete this operation run the following command locally on host glassfish1.sfbay.sun.com from the GlassFish install location /export/glassfish3:

 asadmin  start-local-instance --node node2 --sync normal ins2
Command start-instance failed.

Go to the instance machine (glassfish1.sfbay.sun.com in this case)  and create the master password file for node2 by typing the following command.


asadmin change-master-password --savemasterpassword true --nodedir /export/glassfish3/glassfish/nodes node2


Important note: At the prompt I have to enter the old master password ('welcome1') which is what I had set when I created domain2 on the DAS. It is not the default master password 'changeit'  because the keystore was copied over when the instance was created and it is encrypted with the master password from the DAS. So the passwords are the same but sincestart-instance doesn't have an option to take the master password it looks for a file calledmaster-password in the agent directory to access the keystores. Once that file is created,start-instance can be run centrally (from the DAS).

5) Start the instance from the DAS

asadmin start-instance ins2

At this point you can create additional instances from the DAS and start them without going to the instance machine. 

A slightly different scenario is below.  In this case I will begin by creating a domain with the master password set to 'welcome1' as in the previous example, create an SSH node to point to the remote host where the instance will run but I will create the instance locally on the instance machine.  At some future time I want to manage the instance from the DAS so I still need themaster-password file created in the node's agent directory. 

On DAS machine:

1) Create  and start a domain with the master-password set to "welcome1" using the command
 

asadmin create-domain --savemasterpassword true domain2
asadmin start-domain domain2


2) Create an ssh node  pointing to the remote host where the instances will run.
 

asadmin create-node-ssh --nodehost glassfish1.sfbay.sun.com --installdir /export/glassfish3 node2

Now we move to the instance machine and create the instance locally and as long as there is no master-password file in the node we need to create one. The command create-local-instance can do that for us.

asadmin --host DASHost create-local-instance --node node2 --savemasterpassword true insL2

In this case, the master password for the keystore in the instance is 'changeit' or the default. Nothing was copied over from the DAS so the password is what is on the instance machine. Again, once the file master-password has been created with the passwordthat matches the one on the DAS, then instance insL2 can be administered from the DAS. Additional instances can be created, started and stopped from the DAS machine.

If the master password is changed on the DAS then you must go to each instance machine and run thechange-master-password command as in step 4 above to reset the master password file for each node.

 
 

One of the main features in GlassFish 3.1 is clustering and for m2 we have added support for creating and starting instances on remote hosts.  The underying GlassFish 3.1 code uses SSH to connect to the remote hosts and introduces the concept of a node which is used by the system to deterimine where the instances will be created or started. At this time the only connection type supported is SSH.  Users now have a few new commands to manage nodes. 

  • create-node-ssh  creates a node that describes the hostname where the instance will run and location of GlassFish installation.  
  • delete-node-ssh andlist-nodes are useful in deleting and listing nodes respectfully. 

Below is a simple example of creating a cluster, creating an instance and starting the instance all from the administration host or the DAS (Domain Administration System). 

First, some assumptions about the setup for GlassFish.  For m2, users will have to install and start GlassFish on all hosts that are part of the cluster. We do not currently support installing or starting GlassFish on a remote host and this is planned for a future release.  Second, SSH needs to be setup on both hosts as it is the underlying mechanism that is used to run commands on the remote hosts.  Currently we have only tested on UNIX (MacOs, Ubuntu, and OpenSolaris) but for m3 we will be including Windows as a tested platform.  There are many blogs that talk about setting up SSH so I won't go into all details here. I found this bloguseful.  To summarize how I set up the authentication key,  I used ssh-keygen -t dsa to create the key file in my .ssh dir.   Note: a limitation for m2 is that we don't support encrypted key files so you must  not set a passphrase when creating keys.  I then usedscp to copy the key file id_dsa.pub to the host I want to log in to.  I put it in the .ssh dir and called itauthorized_keys2.  Also I had the same username on both systems which further simplified things.  At that point I can ssh into the remote host without supplying a password.  This is a good test to see if you are set up correctly before you try the commands below.

In this example, we will create a cluster with two machines,foo and barfoo will be the DAS which has all the information about the servers running in the cluster.  Recall that in this release we have introduced a new CLI command create-node-ssh to create a node which is used to locate the host for a particular instance. 

create-node-ssh has three required parameters, 

  1. --nodehost:  the name of the host where the instance lives
  2. --nodename:  the GlassFish installation directory
  3. --name:  name of the node being created 

All other parameters will default to reasonable values.  We default the ssh port to 22. If no username is provided we default to the user running the command and we look for the key file in the home directory of that user.  All instances are now required to reference a node element which GlassFish uses to determine where the instance will be created or started.  This means that we have added a --node option to thecreate-instance command. As a convience we have a default node for localhost so if the  node option is not specified when the instance is created a reference is automatically added  to the localhost node.  The localhost node contains only a node name of localhost.  We can get the GlassFish installation directory from the server. 

 

Let's see how this works.  All commands are run on the DAS machine and as long as there is SSH access to the other host we will be able to create and start instances.

 

Install and start GlassFish 3.1 m2 on foo andbar.  

On host foo (the DAS) we run all the commands. 

$asadmin create-cluster c1

Command create-cluster executed successfully.

$asadmin create-node-ssh --nodehost=bar --nodehome=/home/cmott/glassfishv3/glassfish nodebar

Command create-node-ssh executed successfully.

$asadmin list-nodes 
localhost
nodebar

Command list-nodes executed successfully.

$asadmin create-instance --cluster=c1 --node=nodebar instance1

Command create-instance executed successfully.

$asadmin create-instance --cluster=c1 instance2

Command create-instance executed successfully.

$asadmin list-instances
instance2 not running
instance1 not running

Command list-instances executed successfully.

$asadmin start-cluster c1


Command start-cluster executed successfully.

$asadmin list-instances

instance2 running
instance1 running


Command list-instances executed successfully.

 

Notice that when creating instance2 I did not specify a node and so the default node lcoalhost is used.  In a future release of GlassFish, create-node-ssh will test if a connection can be made to the remote host when the node is created.  If not reachable the user can create the node if the--force option is set totrue.

This blog highlights some of the changes that are part of GlassFish v3 logging.  Since Prelude I have added 3 asadmin commands related to logging. I have updated the set-log-level command and changed the syntax. See below for details. The new commands are:

    * asadmin rotate-log
    * asadmin list-logger-levels
    * asadmin set-log-level
logger-name=level:logger-name=level

The first command rotates the log files immediately.  Users can now use native scheduling software such as cron to rotate the logs.  We still support rotating logs based on file size or elapsed time since the last log rotation and this new command allows more flexibility when rotating log files.  The second command lists the loggers and their current level.  This command reports on all the known loggers that are listed inlogging.properties file.  Note that in some cases the loggers may not have been created by the respective containers however they will still appear in the list. The third command,  asadmin set-log-level, sets the level for aone or more loggers.   For example, to set the log level of the web container logger level to WARNING  simply type: asadmin set-log-level javax.enterprise.system.container.web=WARNING. This command updates the logging.properties file which means that the values are persisted across server restart.  Use asadmin list-logger-levels  to get the names of the loggers.

Finally, I've added a property to logging.properties file in the domain config directory that controls  the number of message written to the server log file.  GlassFish v3 logging code writes messages to a queue instead of directly to  the server log file.  Log messages are taken off the queue and written to the server log file as cycles become available.  The property name iscom.sun.enterprise.server.logging.GFFileHandler.flushFrequency.  This propertys controls the number of messages that are taken off the queue and written to a file a time.  The actual number written is the value of this property or less depending on the number of messages in the queue.  The default value is 1.
 

I recently blogged about the changes to logging GlassFish Prelude. Building on that I wanted to show how to add custom handlers to your installation of v3. You may find that you want to log messages to a database, send them to a remote server or log messages from specific loggers to your own file. This can be done by writing a custom log handler. It is pretty straight forward and actually there are two approaches you can take to add the handler. Either you can write an HK2 service for the handler or a Java class as specified by the JDK. Either approach will work in GlassFish v3 Prelude so I have examples for both approaches in this blog. In the examples below I simple print some messages to a specific file but you can replace that code with something that interests you.

Option 1

First I'll implement the handler as a simple Java class. This requires for me to write a class that implements the Handler APIs, package it in a jar file and add the jar to the GlassFish classpath. The last step is to update the logging.propertiesfile to include the name of the class of the handler. At server startup the handler is added to the root logger automatically. Since the handler is added to root logger it is available to all loggers.

My NewHandler class implements the publish, close andflush Handler APIs. In the publish method, my handler simply writes to a file all messages that come from the web and deployment loggers as those are the ones that I'm interested in seeing. All messages are still logged to server.log so nothing is lost.

package logging;

/**
 *
 * @author cmott
 */

import java.util.logging.*;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/**
 * New Handler
 *
 */
public class NewHandler extends Handler {

    static BufferedWriter f = null;
    String webLogger = "javax.enterprise.system.container.web";
    String deployLogger = "javax.enterprise.system.tools.deployment";

    public NewHandler(){
        try {
              String userDir = System.getProperty("user.dir");
              f = new BufferedWriter(new FileWriter(userDir + "/mylogging.log"));       
            } catch (IOException e) {
                System.out.println("not able to create log file."+e);
       }
    }

 /**
  * Overridden method used to capture log entries   *
  * @param record The log record to be written out.
  */
 public void publish(LogRecord record)
 {
  // first see if this entry should be filtered out
  // the filter should keep anything
  if ( getFilter()!=null ) {
   if ( !getFilter().isLoggable(record) )
    return;
  }
  
  try { 
     if (webLogger.equals(record.getLoggerName()) || deployLogger.equals(record.getLoggerName()) ) {
         f.write ("NewHandler output - ");
         f.write("logger name: "+record.getLoggerName());
         f.write(" source classname: "+record.getSourceClassName());
         f.write(" message: "+record.getMessage());
         f.newLine();
         f.flush();
     }
  } catch (IOException ex){
        System.out.println("not able to write to log file."+ex);
  }
 
 }
 
 /**
  * Called to close this log handler.
  */
 public void close()
 {
        try {
         f.close();
        } catch (IOException ex){
  }
}
 
 /**
  * Called to flush any cached data 
  */
 public void flush()
 {
// not used
 }
}

Next I need to do is to package it in a jar file and copy that jar to glassfish/lib directory. By default the handler is associated with the root logger so all loggers will have messages send to the handler.

Update the property handler in thelogging.properties file to include the name of the new handler as below.

handlers= java.util.logging.ConsoleHandler, logging.NewHandler

Restarting the server with the new jar file on the classpath will include the new handler. To test this out I deployed a simple web app and looked at the log file. There you will see the messages from the new handler we just wrote.

Option 2

The other option is to create service using HK2. See Chapter 2 Writing HK2 Components of the GlassFish documentation for more information on writing an HK2 component. Again you need to implement the handler code much like the example above. I started with that example and added code to make it an HK2 service. I had to implement PostConstruct and specify that the service contract is provided by the Handler.class which is part of the JDK. I built the module and then add the jar file to the GlassFish modules directory. At server startup the module is found and the handler is added to the root logger. The handler code now looks like:

package logging;

/**
 *
 * @author cmott
 */
import org.jvnet.hk2.annotations.Inject;
import org.jvnet.hk2.annotations.Scoped;
import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.annotations.*;
import org.jvnet.hk2.component.*;


import java.util.logging.*;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/**
 * New Handler
 *
 */
//specify that the contract is provided by handler.class in the JDK
@Service
@ContractProvided(Handler.class) 
@Scoped(Singleton.class)
public class NewHandler extends Handler implements PostConstruct {
    @Inject

    Habitat habitat;
    static BufferedWriter f = null;
    String webLogger = "javax.enterprise.system.container.web";
    String deployLogger = "javax.enterprise.system.tools.deployment";
    
    public void postConstruct(){
        try {
             String userDir = System.getProperty("user.dir");
             f = new BufferedWriter(new FileWriter(userDir + "/mylogging.log"));       
        } catch (IOException e) {
            System.out.println("not able to create log file."+e);
        }
    }
    
 /**
  * Overridden method used to capture log entries   *
  * @param record The log record to be written out.
  */
 public void publish(LogRecord record)
 {
  // first see if this entry should be filtered out
  // the filter should keep anything
  if ( getFilter()!=null ) {
   if ( !getFilter().isLoggable(record) )
    return;
  }

  try {
     if (webLogger.equals(record.getLoggerName()) || deployLogger.equals(record.getLoggerName()) ) {
         f.write ("NewHandler output - ");
         f.write("logger name: "+record.getLoggerName());
         f.write(" source classname: "+record.getSourceClassName());
         f.write(" message: "+record.getMessage()); 
         f.newLine();
         f.flush();
     }
  } catch (IOException ex){
    System.out.println("not able to write to log file."+ex);
  }

 }

 /**
  * Called to close this log handler.
  */
 public void close()
 {
    try {
        f.close();
    } catch (IOException ex){
  }
}

 /**
  * Called to flush any cached data that
  * this log handler may contain.
  */
 public void flush()
 {
// not used
 }
}

I created this as its own module and included the pom files I used to build the module below. In the example I have a very simple directory structure with handler-module as the parent containing one directory, handler. I've zipped up the standalone module, handler-module.zip rather than include the pom.xml files needed to create it.

There is no need to update the logging.properties file when creating a service. All that is needed is to drop the newly created jar file in the modules directory of the v3 installation. GlassFish detects the service in the modules directory automatically. Again I deployed a simple web app to test and look at mylogging.log file to see the expected messages.

That's it.

You may be seen the announcement yesterday about the GlassFish V3 Prelude release. At the same time, we released jMaki 1.8.1. jMaki provides a framework for building Ajax applications and was fully tested on GlassFish V3 Prelude.

Highlights of the new features in jMaki include:

  • Drag and Drop widget support into HTML, PHP, Ruby pages
  • Performance enhancer
  • Widget Customizer
  • jMaki CSS Page Templates
  • Yahoo UI 2.6 Widgets
  • Dojo Dijit 1.2 Widgets
  • Scriptaculous 1.8.1 Widgets
  • jQuery UI 1.5b Widgets
  • Google 1.8.1 Widgets
  • jMaki 1.8.1 Widgets
  • jMaki Extras 1.8.1 Widgets

You can get jMaki from the download page or from the GlassFish update center. Enjoy!

Logging in GlassFish V3 has undergone some changes to leverage the logging utility in JDK. This blogs reviews where we are with the logging mechanism in GlassFish Prelude and since not all of the features are implemented in the Prelude release yet, I'll go over how to make use of the functionality that is there.

The most significant change is that for now we don't have support in the Admin GUI or the command line tool asadmin. That support will be provided in the final release of GlassFish V3. For now, developers using Prelude will have to edit thelogging.properties file found in the domain config directory where you find the domain.xml file. Let's take a look at what can be done.

Developers can update the logger level, change the log filename and set rotation of log files based on file size or a time limit. Some of these changes require a restart of the server and some don't.

First let's talk about setting log levels on loggers. Changes to logger levels are dynamic and don't require a server restart. At the end of the logging.properties file you will find a list of loggers in GlassFish. I have included the most common loggers so developers can easily find what they want. Below is an excerpt from that file.

#Tools Logger
#javax.enterprise.system.tools.level=FINE
# EJB Logger
#javax.enterprise.system.container.ejb.level=FINE
#JAVAMAIL_LOGGER
#javax.enterprise.resource.javamail.level=FINE
#JMS_LOGGER
#javax.enterprise.resource.jms.level=FINE
#WEB LOGGER
#javax.enterprise.system.container.web.level=FINE
#CMP_LOGGER
#javax.enterprise.system.container.cmp.level=FINE

By default all the logger levels are set to INFO because the root level is set to INFO. If the log level is not set on a logger then it inherits the level of its parent going up the chain until it finds a logger with the level set or it reaches the root logger. To set the level on a specific logger you simply uncomment the line in logging.properties corresponding to the logger and set the level appropriately. Upon saving the file the logging mechanism is notified of a change to the file and resets the log levels for all known loggers based on the information in the file. Note that you can create your own logger, specify the level in the logging.properties file and it will be updated too.

The log level values are SEVERE, WARNING, INFO, CONFIG, FINE, FINER, and FINEST. Each level includes messages of the level above it so if you set a logger to level INFO you will also get the SEVERE and WARNING messages in addition to the INFO.

One last note about log levels, it is valid to set the level of the loggers at any point in the logger name. For example, say you want all the loggers under javax.enterprise.system.container to print at the FINE level. You simply create a line as follows and save the file.

javax.enterprise.system.container.level=FINE

Once set, the logger level must be explicitly reset to change the level.

Setting file rotation is also done by changing properties inlogging.properties. Changing these properties does require a server restart to take affect. There are two ways to set file rotation in Prelude. You can request files be rotated based on a file size or based on a time limit. The properties associated with file rotation are below.

# 0 for no rotation based on time
com.sun.enterprise.server.logging.FileandSyslogHandler.rotationTimelimitInMinutes=0
# rotation limit in bytes, 0 means no rotation, 500000 is the minimum
com.sun.enterprise.server.logging.FileandSyslogHandler.rotationLimitInBytes=0

By default both are set to 0 or no file rotation. If both are set then rotation will be based on time. You can specify the max size of the log file reached to rotate or create the next log file or the max elapsed time reached to rotate the log file.

For now developers will need to edit thelogging.properties file and it should be pretty straight forward. But remember that the team is working on providing tool support for GlassFish V3 final so it will be easier soon.

Last spring we added performance enhancements to jMaki based on well known guidelines for improving page load times. The guidelines are from Yahoo and we have automated several of them. Now developers can combine JavaScript code and place it at the end of the page, place combine CSS code and place it at the beginning of the page, use minified resources and set the max age header by simply setting some properties in their web app. For more information about the performance enhancements and what is happening under the hood, see the jMaki performance page. This blog details how to take advantage of those performance enhancements in your application.

I used Netbeans to create the web application but you can use Eclispe or a plain editor. In this example, I have the jMaki menu, tag cloud and blocklist widgets. You can use any jMaki wrapped widgets or custom jMaki widgets that you have created to take advantage of this feature. The page looks like:

<%@ taglib prefix="a" uri="http://jmaki/v1.0/jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>
    <head>
        <link rel="stylesheet" href="jmaki-2column-footer.css" type="text/css"></link>
        <title>Page Title</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <a:page>
    </head>
    <body>
        <div id="border">
            
            <div id="header">
                <div id="banner">Application Name</div>
                
                <div id="subheader">
                    
                    <div>                      
<a:widget name="jmaki.menu"
      value="{menu : [
       {label: 'Links',
            menu: [
                { label : 'Sun.com',
                  href : 'http://www.sun.com'},
                { label : 'jMaki.com',
                  href : 'http://www.jmaki.com'}
                ]
       },
       {label: 'Actions',
            menu: [
                { label : 'Select',
                  action :{topic: '/foo/select',
                         message: { targetId : 'bar'}}
                },
                { label :'Set Content',
                  action :{topic: '/foo/setContent',
                         message: { value : 'test.jsp'}}
                }
                ]}
                ]
       }" />


                    </div>
                    
                </div> <!-- sub-header -->
            </div> <!-- header -->

            <div id="main">
                <div id="rightColumn" style="height:400px">

<a:widget name="jmaki.blockList" value="[
        {title : 'jMaki Project Home', link : 'https://ajax.dev.java.net', description : 'Where to go for the latest jMaki.' },
        {title : 'jMaki Widgets Home', link : 'https://widgets.dev.java.net', description : 'The source for the latest jMaki widgets.' },
        {title : 'jMaki-Charting Home', link : 'https://jmaki-charting.dev.java.net', description : 'Enables complex charts rendered on the client in any modern browser.' }
    ]"  />


                </div> <!-- end leftColumn -->

                <div id="leftColumn" style="height:400px">
<a:widget name="jmaki.tagcloud" value="{
  items : [
      { label : 'jMaki', weight : 70},
      { label : 'Web2.0', weight : 150},
      { label : 'JSON', weight : 80},
      { label : 'JavaScript', weight : 145},
      { label : 'Java', weight : 100},
      { label : 'RSS', weight : 85},
      { label : 'Autocomplete', weight : 75},
      { label : 'Sun', weight : 65, href : 'http://www.sun.com'},
      { label : 'jMaki', weight : 150},
      { label : 'Web3.0', weight : 70},
      { label : 'Phobos', weight : 105},
      { label : 'Glassfish', weight : 120},
      { label : 'RSS2.0', weight : 75},
      { label : 'Web1.0', weight : 50},
      { label : 'JavaEE', weight : 75},
      { label : 'Jersey', weight : 115},
      { label : 'Roller', weight : 150},
      { label : 'CSS', weight : 105},
      { label : 'DHTML', weight : 65},
      { label : 'Netbeans', weight : 115, href : 'http://www.netbeans.com'}
  ]
}" />

                </div> <!-- leftColumn -->

            </div> <!-- main -->
            <div id="footer">Footer</div>
        </div> <!-- border -->
        </a:page>
    </body>
</html>

This is a typical jMaki page expect I added 2 new tags. The <a:page> tag at the beginning tells the jMaki performance code to drop the CSS code and should be in the header. The closing </a:page> tag tells jMaki where to drop the combined JavaScript code and should be the last line in the body typically.

One other change required is to the web.xml file. You will need to set some properties to true and to configure the servlet that does the work. The following needs to be added to the web.xml file.

   <context-param>
        <param-name>jmaki-combinescripts</param-name>
        <param-value>true</param-value>
    </context-param>
    <context-param>
        <param-name>jmaki-combinestyles</param-name>
        <param-value>true</param-value>
    </context-param>

    <servlet>
        <servlet-name>Combined Resource Servlet</servlet-name>
        <servlet-class>jmaki.runtime.CombinedResourceServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
        </servlet>
        
    <servlet-mapping>
        <servlet-name>Combined Resource Servlet</servlet-name>
        <url-pattern>/cr</url-pattern>
    </servlet-mapping>

That's it. Look at the page source of the deployed application and you will see that things have been moved around. One application reported a 30% improvement in page load time by simply setting these properties.

Try it out and let us know what improvements you see.

I was happy to see that the NetBeans birthday celebration page uses the jMaki revolver widget. Go to netbeans.org/birthday and you will find the revolver used to easily access an interview with James Gosling, pages pointing to community members, a contest and more. It's a great use of the widget.

Happy Birthday NetBeans!

I got to help out with the EJB 3.1 keynote demo for JavaOne. Although the demo shows how simple it is to perform CRUD operations using EJB 3.1, this blog focuses on the communication between the client side jMaki components and server side servlet.

For more information on the new EJB features in GlassFish v3 see Mahesh's blog. The demo uses some of the latest features in EJB 3.1 and MySQL for a backend data store. I will discuss the client side code for passing data to the server and getting data from the server and displaying it in a widget. The demo shows how simple it is to perform CRUD operations using EJB 3.1.

One requirement was to use the Dojo's Fisheye widget since it looks cool and so I created a set of pages, one each for the operations create, update and delete. The appropriate page is selected using the fisheye widget so the user could provide the necessary data. Once the user submitted the data I could then send the request to the server to update the database.

In this demo, I used the Dojo fisheye widget for navigation, html forms for collecting data from the user, Yahoo dialog for displaying errors and a Dojo table to display the data. The server side code is specific to EJB 3.1 and so must run on GlassFish V3 with the EJB module installed. MySQL data base is used for storing data.

A screen shot of the demo: crudapp.jpeg

Let's look at some of the details. The code for the index.jsp page follows. Notice that the table is getting data from the servlet "/AuthorServlet" which is returning data in JSON format. The data base is prepopulated with data so the table is rendered with some rows of data. I dropped the Yahoo dialogs in the page and made them not visible. The fisheye widget is used for navigation and there are three other pages in the application which are loaded by the dcontainer based on the selection by the fisheye. More details on that below.

<%@ taglib prefix="a" uri="http://jmaki/v1.0/jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>
    <head>
        <link rel="stylesheet" href="jmaki-2column-footer.css" type="text/css"></link>
        <title>Page Title</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div id="border">
            
            <div id="header">
                <div id="banner">CRUD operations using EJB 3.1 </div>
                               
            </div> <!-- header -->

            <div id="main">
                <div id="rightColumn" style="height:600px">
                    <div id="rightColumnTop" style="height:180px">
                        <div id="rightColunmTopTable" style="height:180px">

             <a:widget
                      name="jmaki.dcontainer"
                      subscribe="/jmaki/forms"
                      value="{
                            items : [
                              { id : 'create',include : 'create.jsp', overflow : 'hidden'}, 
                              { id : 'update', include : 'update.jsp', overflow : 'hidden', lazyLoad : true},
                              { id : 'delete', include : 'delete.jsp', overflow : 'hidden', lazyLoad : true}
                             ]
                          }"/>  
                  </div>
                  <div id="rightColumnTopImages" style="height:180px">
                      Powered by:
                      <div>
                      <img src="images/glassfish-logo.jpg">
                  </div>
                  <div>
                      <img src="images/jmaki-seal.png">
                  </div>
                  </div>
                </div> <!-- end leftColumn -->
                <div id="rightColumnBottom" style="height:415px">
             
             <!--<center>Author Table </center>-->
             
             <a:widget name="dojo.table"
            service="/AuthorServlet" />
                    
                </div> <!-- end leftColumn -->
                                 
                </div> <!-- end leftColumn -->

                <div id="leftColumn" style="height:600px">
                    <a:widget name="dojo.fisheye" args="{orientation:'vertical'}" value="[ 
     {iconSrc:'images/document-new.png',
      label : 'Create',
      action : { topic : '/jmaki/forms/select', message : {targetId : 'create'}}
     },
     {iconSrc:'images/edit-find-replace.png',
      label : 'Update',
      action : { topic : '/jmaki/forms/select', message : {targetId : 'update'}}
      },
      {iconSrc:'images/edit-delete.png',
      label : 'Delete',
      action : { topic : '/jmaki/forms/select', message : {targetId : 'delete'}}
      }
     ]"/>

        <a:widget id="dialog1" name="yahoo.simpledlg"args="{header : 'Error Found', visible: false, text: 'Duplicate Author Id entry found.  Provide a unique Author Id'}"
                    value="{buttons: [ 
        { label:'ok', isDefault:true }
        ] }"/>

        <a:widget id="dialog2" name="yahoo.simpledlg"args="{header : 'Error Found', visible: false, text:  'Author Id entry not found.  Provide a Author Id'}"
                    value="{buttons: [ 
        { label:'ok', isDefault:true }
        ] }"/>
          
                </div> <!-- leftColumn -->

            </div> <!-- main -->

        </div> <!-- border -->
    </body>
</html>

Now lets look at how to create an entry in the data which will be reflected in the table. First let's look at the page create.jsp.

 Enter Data to Create Author Entry: <br><br>
  
  
                  <form name="create"  >
  Author Id: <input type="text" id="authorId" name=authorId/> <br>
  Author: <input type="text" id="authorName" name=authorName/><br>
  Organization: <input type="text" id="organization" name=organization/><br>
  <br><br> 
   <input type="hidden"  id="hidden" name="create" value="create">
   <input type="button" onclick="doCreate()" name="submit" value="create">
  </form>

As you can see this is a simple form which calls the doCreate function to extract the data. The doCreate function is in glue.js.

var Service="/crud/AuthorServlet";
var rowId;

function doCreate(){
    var opName = "create";
    
    var authorName = document.getElementById("authorName").value;
    var authorId = document.getElementById("authorId").value;
    var orgName = document.getElementById("organization").value;
       
      jmaki.doAjax({method: "POST",
        url: "/crud/AuthorServlet",
        content :  { authorName: authorName, authorId : authorId, orgName : orgName, opName: opName }, 
        callback: function(_req) {
            var tmp = _req.responseText;
        if(!tmp)  {
                 var a = jmaki.getWidget("dialog1");
                 a.setVisible(true);
            }  else {
           var obj = eval("(" + tmp + ")");
                if ( opName == "create")
                     jmaki.publish('/table/addRow', obj);
        else {
            jmaki.publish('/table/updateRow', obj);
           }
       }
        },
        onerror: function() {
                 var a = jmaki.getWidget("dialog1");
                 a.setVisible(true);            
                    }
     });
     document.forms.create.reset();

}

After extracting the data from the form I use jmaki.doAjax to send the data to the server. I use the property "content" with the values I extracted because this property will pass the data to the server correctly regardless of the platform. In other words the right thing happens if the server is Java or PHP. The callback function is called if there were no errors on the server and if so the data received is passed to the table using the standard jMaki publish and subscribe mechanism. If there was an error on the server the onerror function is called and in this case I set the error dialog to visible.

Here is a screen shot of the application with the error dialog:cruderror.jpeg

Finally I wanted to describe how the fisheye is used to display the correct form. Looking at the fisheye widget tag you see that each icon will publish to the "/jmaki/forms/select" and the payload contains a string used to identify the operation selected. The dcontainer subscribes the the "/jmaki/forms" topic. The dcontainer contains a select handler which looks up the id that it was passed, in this case 'create' and the includes the page create.jsp.

I have posted the war file on the documents and files demo area. I also have the processRequest method from the AuthorServlet code below so it is easier to see what the server is doing. There are several helper classes that update the data base or convert Java objects into JSON that are not shown.

@PersistenceUnit(name="myAuthorEMF", unitName = "WebEjbJpaPU")
public class AuthorServlet extends HttpServlet {
   
    @EJB private AuthorBean authorRef;
    
    //private AuthorHelper authorRef = new AuthorHelper();
    
    /**         
    * Processes requests for both HTTP GET and POST methods.
    * @param request servlet request
    * @param response servlet response
    */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        try {
            String opName = request.getParameter("opName");
            
            int matchID = -1;
            
            Author author = null;
            
            if (opName == null) {
                opName = "list";
            } else {
                author = new Author();
                author.setAuthorId(
                        Integer.valueOf(request.getParameter("authorId")));
                author.setName(request.getParameter("authorName"));
                author.setOrganisation(request.getParameter("orgName"));
            }
            
            if ("update".equals(opName)) {                
                authorRef.updateAuthor(author);
            } else  if ("create".equals(opName)) {
                authorRef.createAuthor(author);
            } else if ("delete".equals(opName)) {
                authorRef.deleteAuthor(author);
            }
            
            sendOutData(author, opName, out);
        } finally { 
            out.close();
        }
    }
    

That's it.

I found that TravelMuseis a great site to help plan vacations and I was excited to learn that they use jMaki to build the site.

Daniel Ziaoure is the lead Web developer for TravelMuse and uses jMaki extensively. We were lucky enough to have him join us during our Community One day presentation on jMaki. He recently started a series of articles explaining how he uses the jMaki widget model and communication mechanism to build the useful and impressive site. His first article Building an Ajax Login with jMaki shows how he used jMaki to build an Ajax enable login widget.

Last week at Community One day, the jMaki session included a couple of speakers from the community. It is great to see jMaki used in the real world.

We had four speakers at our session and I'll admit it was a lot given we only had an hour. But I was excited to include both Jennifer Myers and Daniel Ziaoure since they are both using jMaki in their projects.

The session was divided as follows. I gave a high level overview of jMaki and talked about the framework, widgets and pub/sub bus. It was pretty quick since I wanted to give others time but I was able to squeeze in a short demo.

Jennifer who also works for Sun talked about project Miso and the interface that they built using jMaki. Project Miso, provides deep, fast and broad search and indexing services and will initially be integrated into the Communication Suite. Currently the project is focussed on email, calendar and Instant Messaging products from Sun. Jennifer used the Yahoo table to display the list of items found in the search and the dcontainer to display the image that was found. Jennifer mentioned that she had no prior Ajax experience prior to starting on the project and found jMaki a great tool for quickly developing the user interface. Jenn-greg.jpg

Daniel Z, an employee of TravleMuse Inc., covered the architecture of his project and showed how jMaki is used in their site. TravelMuse helps travelers save time and make better decisions at every step of the travel planning process by providing the Web

To prepare for our JavaOne session, GWT and jMaki: Expanding the GWT Universe, I decided I should add a jMaki Yahoo widget to a GWT application. Here is what I did.

I wanted to start with an existing application and I chose the Java PetStore demo application which Greg had rewritten using jMaki widgets and GWT. The code for that application is in the jmaki-store project along with a README.txt containing all the build and install instructions. First I checked out the jmaki-store workspace, built and deployed the application to make sure I had a working copy of the app. Follow the instructions in README.txt file to build and deploy but basically you need to go to the jmaki-store/code/gwt/webapp dir and type ant. There also is a target to deploy the application or you can copy the war file to the auto deploy directory. In my case I chose to deploy to GlassFish V3 and deployment was extremely fast.

I decided to add the Calendar Yahoo widget to the application because I thought it would be pretty straight forward. The following are steps needed to add the widget to the application. First, I had to add an additional class (Calendar, java) in jmaki-store/code/gwt/widgets/sc/java/jmaki/client. Calendar.java contains the following:

  
package jmaki.client;

import jmaki.client.JMakiWidget;

/**
 * Calendar Widget.
 */
public class Calendar extends JMakiWidget {
   
  public Calendar(int width, int height) {
      super(width,height);   
  }     
    
  public String getWidgetName(){
      return "yahoo.calendar";
  }
    
}

This class extends JMakiWidget which is a wrapper for the jMaki widgets. The Calendar widget has 2 methods, a constructor and the getWidgetName method which returns the name of the widget. Following the jMaki convention the name of the widget is the toolkit name dot widget name.

Next I updated the Widgets.gwt.xml file to list all the dependencies for this widget. It turns out that the list is already in the widgets.json file as that information is needed by the jMaki plugins so I started there. I had to update the paths and reorder the list because here order matters. The Yahoo toolkit CSS files need to appear before jMaki CSS files then the Yahoo toolkit library dependencies and then the jmaki component.js file. jMaki framework will take care of this automatically but in GWT land the list needs to be specified in order. The Widgets.gwt.xml file looks like:

 <?xml version="1.0" encoding="UTF-8"?>
<module>
 <inherits name="com.google.gwt.core.Core"/>
 <source path="client"/>
 <public path="public"/> 
 <script src="resources/jmaki.js"/>
 
 <stylesheet src="resources/jmaki/ibrowser/component.css"/>
 <script src="resources/jmaki/ibrowser/component.js"/>
 
 <stylesheet src="resources/jmaki/resources/styles/themes/charcol/theme.css"/>
 <stylesheet src="resources/jmaki/accordionMenu/component.css"/>
 <script src="resources/jmaki/accordionMenu/component.js"/>

  <stylesheet src="resources/jmaki/cart/component.css"/>
 <script src="resources/jmaki/cart/component.js"/>
 
 <stylesheet src="resources/jmaki/feedreader/component.css"/>
 <script src="resources/jmaki/feedreader/component.js"/>

 <stylesheet src="resources/yahoo/resources/libs/yahoo/v2.5.1/calendar/assets/skins/sam/calendar.css"/>
 <stylesheet src="resources/yahoo/calendar/component.css"/>
 <script src="resources/yahoo/resources/libs/yahoo/v2.5.1/yahoo-dom-event/yahoo-dom-event.js"/>
 <script src="resources/yahoo/resources/libs/yahoo/v2.5.1/element/element-beta-min.js"/>
 <script src="resources/yahoo/resources/libs/yahoo/v2.5.1/container/container_core-min.js"/>
<script src="resources/yahoo/resources/libs/yahoo/v2.5.1/menu/menu-min.js"/>
 <script src="resources/yahoo/resources/libs/yahoo/v2.5.1/button/button-min.js"/>
 <script src="resources/yahoo/resources/libs/yahoo/v2.5.1/datasource/datasource-beta-min.js"/>
<script src="resources/yahoo/resources/libs/yahoo/v2.5.1/calendar/calendar-min.js"/>
<stylesheet src="resources/yahoo/resources/libs/yahoo/v2.5.1/menu/assets/skins/sam/menu.css"/>
<stylesheet src="resources/yahoo/resources/libs/yahoo/v2.5.1/button/assets/skins/sam/button.css"/>
  <script src="resources/yahoo/calendar/component.js"/>
</module>

So far I have listed the resources needed for the widget and now I need to copy those files into the web app. All the resources are in the jMaki bundle so I just need to get what is on the list and add it to my application. Here I cheated a little. I created a project using NetBeans and put a single widget (Yahoo calendar) in the page. Doing so copied in the appropriate resources and so I had the directory I needed to copy for my GWT app. The resources/yahoo/calendar/* directory under the new project was copied to the jmaki-store/code/shared/widgets/resource/yahoo directory in the jmaki-store project.

The last thing I need to do is to add the new widget to the Main window of my app. Edit jmaki-store/code/gwt/webapp/src/java/jmaki/store/client/MainEntryPoint.java to look as follows:

 
package jmaki.store.client;

import com.google.gwt.core.client.EntryPoint;

import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.Widget;

import jmaki.client.IBrowse;
import jmaki.client.AccordionMenu;
import jmaki.client.Cart;
import jmaki.client.Calendar;


public class MainEntryPoint implements EntryPoint {
    
    /** Creates a new instance of MainEntryPoint */
    public MainEntryPoint() {
    }
    
    /**
     * The entry point method, called automatically by loading a module
     * that declares an implementing class as an entry-point
     */
    public void onModuleLoad() {
        DockPanel dp = new DockPanel();
 
        AccordionMenu am = new AccordionMenu("menu.json", 200,400);
         dp.add(am, DockPanel.WEST);
         
       
        IBrowse ib = new IBrowse(400,400);
        RootPanel.get().add(ib);
        dp.add(ib, DockPanel.CENTER);
    
        Cart cart = new Cart(250, 400);
        dp.add(cart, DockPanel.EAST);
       
        Calendar cal = new Calendar(150, 150);
        dp.add(cal, DockPanel.EAST);
       
        RootPanel.get().add(dp);
    }
    
}

Now when you build and deploy the app again you will see the calendar widget in the page. Note that you do not need to change the build files.

It really is pretty easy once you figure out the order that the JavaScript dependencies should be in. Using jMaki wrapped widgets means that 3rd party components can easily be added to GWT.

carlavmott

jMaki at JavaOne Blog

Posted by carlavmott Apr 28, 2008

Community One day and JavaOne are just around the corner. Community One day is a free event so we hope you will come out and learn about jMaki, GlassFish and NetBeans. Come to Moscone Center on Monday May 5 for many interesting sessions. Greg and I will be speaking along with some folks from the community.

  • Session: jMaki: The power of Ajax made easy.
    Time: 12:25- 1:20
    Room : Hall E, 133

JavaOne runs Tuesday May 6 - May 9 also at Moscone Center. There's lots of ways to learn about jMaki this year. There are sessions and Hands on labs to help you learn more. Also look for jMaki in the general session on Tuesday.

  • Session: jMaki- Ajax with Java

I've gotten a few questions about how to get datainto jMaki widgets. This blogs describes the different ways in jMaki to load data into widgets.

Let's look at the JSP tag for including a jMaki accordionMenu widget in your web application.

 <a:widget name="jmaki.accordionMenu"
      value="{menu : [
       {label: 'Links',
            menu: [
                { label : 'Sun.com',
                  href : 'http://www.sun.com'},
                { label : 'jMaki.com',
                  href : 'http://www.jmaki.com'}
                ]
       },
       {label: 'Actions',
            menu: [
                { label : 'Select',
                  action :{topic: '/foo/select',
                         message: { targetId : 'bar'}}
                },
                { label :'Set Content',
                  action :{topic: '/foo/setContent',
                         message: { value : 'test.jsp'}}
                }
                ]}
                ]
       }" /> 

jMaki uses the attributes value and service to pass data to the widgets wrappers. The value attribute is used when the data is located in the page itself and therefore static. We use this feature in the code snippet that is dropped into a page when using an IDE so we don't have to provide a server side component just to get the widget to render correctly. The code snipet allows users to start with a working example which can be easily modified to learn more about the widget and the data model for that widget.

In the tag above the value attribute is set equal to the JSON representation of the data described in the data models for that widget. The data model pages describe the format for the different widgets in jMaki. All the code snipets provided for the IDE follow the data models for the supported platforms, Java, PHP and Ruby.

The value attribute can also be passed a client side value expression. I described how to use the value expression in my last blog, How to load blog feeds into jMaki widgets. Basically, the data is loaded into a local JavaScript variable which is assigned to thevalue attribute using the "@" character.

Often you will want to load data that is not statically located in the page and in jMaki this is done through the serviceattribute. The next example shows how to get data from a server side component in your application. The tag above now becomes:

 <a:widget name="jmaki.accordionMenu"
      service="data.jsp" />   

As a quick example I will put the data I want to load into the file data.jsp as follows:

 {menu : [
       {label: 'Links',
            menu: [
                { label : 'Sun.com',
                  href : 'http://www.sun.com'},
                { label : 'jMaki.com',
                  href : 'http://www.jmaki.com'}
                ]
       },
       {label: 'Actions',
            menu: [
                { label : 'Select',
                  action :{topic: '/foo/select',
                         message: { targetId : 'bar'}}
                },
                { label :'Set Content',
                  action :{topic: '/foo/setContent',
                         message: { value : 'test.jsp'}}
                }
                ]}
                ]
       }

What really is happening here is when data.jsp is processed non-JSP commands are echoed out as they appear in the file. As a result the data I need is returned in JSON format which is what the widget is expecting. This is just an easy way to see how to use theservice attribute without having to write a server side component. In reality, you will have to collect the data, format in JSON and print it out. The jMaki 1.1 release contains Java helper classes to create JSON object literals. You can also take a look atJennifer's blog. The main point is that getting data from a server side component is done using the service attribute.

In PHP the same thing can be done as follows:

 <?php  addWidget( array("name" => "jmaki.accordionMenu",
    "service" => "data.php"
        ));
?>

The data is in a file called data.php (notice it looks just like data.jsp).

{menu : [
       {label: 'Links',
            menu: [
                { label : 'Sun.com',
                  href : 'http://www.sun.com'},
                { label : 'jMaki.com',
                  href : 'http://www.jmaki.com'}
                ]
       },
       {label: 'Actions',
            menu: [
                { label : 'Select',
                  action :{topic: '/foo/select',
                         message: { targetId : 'bar'}}
                },
                { label :'Set Content',
                  action :{topic: '/foo/setContent',
                         message: { value : 'test.php'}}
                }
                ]}
                ]
       }

The jMaki runtime passes the attributes from the tag to the widget constructors. In the case of the service attribute, the widgets make a jmaki.doAjax call to retrieve the data when the widget is initialized. To update the data once the widget has been initialized, you can write a handler which makes additional jmaki.doAjax calls and sends that data to the widget. See my blog on How to implement data pagination in jMaki tables for an example.

jMaki also provides an XMLHttpProxy so you can safely and easily access RESTful Web Services outside the application domain. To access a service through the proxy you use the service attribute too. In this case the tag looks like:

<a:widget name="jmaki.accordionMenu"
      service="/xhp?id=rss" />

By design you will need to use a token key to identify the service to access. The token key is specified in a file called xhp.json in the resources directory. An entry in the xhp.json file looks like:

 {"id": "rss",
      "url":"http://weblogs.java.net/blog/ludo/index.rdf",
      "xslStyleSheet": "rss.xsl"
     } 

The id is "rss" and this entry specifies the name of the style sheet to apply to the returned data by the proxy. In this example, the data is coming back as XML so we want to convert it to JSON. The current style sheet recognizes RSS 1, RSS2, ATOM 1 and ATOM 2 and converts the XML data to JSON. The style sheet is called rss.xsl and is called by the proxy server before the data is handed to the widget. See Arun's blog jMaki - Accessing External Services for more details on how to access external services using jMaki.

I've noticed a few web sites are using jMaki in their web apps and thought that there are probably a lot more. If you are using jMaki I'd love to here about what you are using and alittle about your application. Let me know if I can post a link to your site on the jMaki site.

Don't forget to send me ( carla dot mott @ sun dot com) your address so I can send the t-shirt. Thanks for using jMaki!