Thursday Nov 15, 2007


Tip o the day for getting JRuby/Rails running with Derby:

Your database.yml needs to look something like this:


development:
    adapter: jdbc
    driver: org.apache.derby.jdbc.ClientDriver
    username: fred
    password: password
    url: jdbc:derby://localhost:1527/ruby_dev


Make sure you copy derbyclient.jar to your Jruby install directory (e.g. ~netbeans-installdir/ruby1/jruby-1.0.2/lib)



Powered by ScribeFire.

Friday Apr 20, 2007



I thought it would be neat to have a way to persist Java objects to and from an Ldap directory. Kinda like "Hibernate lite" for directories. I saw several inquiries on the Hibernate forums discussing a custom mapper for Ldap - but nothing has ever been implemented. The concensus seems to be that it ought to be possible - but perhaps the relational model that Hibernate is based on is not a great fit for Ldap.


In any event, I created a very simple package which I have Dubbed "Slapper". The readme is presented below. If you think this has value, drop me a note and I may extend the implementation...






Slapper


What does it stand for? How about “Simple Ldap Mapper”. OK, yes it is kinda lame


In a nutshell, Slapper is a utility package that uses annotations to persist Java POJOs to and from an Ldap directory. This is a very simplistic mapper - It currently does not handle any relationship navigation. This package uses Spring Ldap.


To use Slapper, you first create a POJO (well almost a POJO, as it must implement the DirObject interface) that has the appropriate annotations. Here is an example:



@ObjectClass({"inetorgperson", "organizationalperson", "person", "top"})
public class UserAccount implements DirObject {
private Name name;
private String commonName;
private String lastName;
private byte[] userPassword;

@DirectoryAttribute("userPassword" )
public byte[] getUserPassword() {
return userPassword;
}

public void setUserPassword(byte[] userPassword) {
this.userPassword = userPassword;
}


@DirectoryAttribute(value="cn")
public String getCommonName() {
return commonName;
}

/**
*
* @param commonName
*/
public void setCommonName(String commonName) {
this.commonName = commonName;
}


.....


The next step is to configure your Spring beans.xml to create a DAO object that can read and write to this object type. Here is an
example:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="contextSource" class="org.springframework.ldap.support.LdapContextSource">
<property name="url" value="ldap://localhost:389" />
<property name="base" value="dc=example,dc=com" />
<property name="userName" value="cn=Directory Manager" />
<property name="password" value="passw0rd" />
</bean>

<bean id="ldapTemplate" class="org.springframework.ldap.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>

<bean name="userMarshaller" class="com.my2do.slapper.Marshaller">
<constructor-arg value="com.my2do.slapper.example.UserAccount"/>
</bean>

<bean id="userDAO" class="com.my2do.slapper.LdapDAOImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
<property name="marshaller" ref="userMarshaller"/>
</bean>

</beans>


The ldapTemplate is standard Spring Ldap code (see the Spring docs for the details). The only tricky bit of the above is the Marshaller. This is a class that knows how to marshal annotated POJOs
to and from directory attributes. The marshaller processes @DirectoryAttribute annotations and maps them to the appropriate Ldap attribute. In the above example, the userDAO bean is configured to read and write beans of type UserAccount.


The DAO interface is fairly simple at this point:


public interface LdapDAO {

/**
* Retrieve an ldap object from the directory
* @param dn String Dn of the object
* @return Returns an object populated with directory attributes. The
* type of the object will be that created by the underlying Marshaller
*/
public Object getObject(String dn);
/**
* Retrieve an ldap object from the directory
* @param name (ldap dn) of the object to fetch
* @return Returns an object populated with directory attributes. The
* type of the object will be that created by the underlying Marshaller
*/
public Object getObject(Name name);

/**
* Create the object in the directory. The Dn of the object
* must be set properly (getName() must return a valid Dn). The objects
* attributes will be extracted with the Marsheller instance and sent
* to the directory.
* @param obj object to create.
*
*/
public void create(DirObject obj) ;

/**
* Delete the object from the directory. The objects getName() must
* return a valid Dn.
*
* @param obj Object to delete
*/
public void delete(DirObject obj) ;

/**
* Updated the given object.
* The object will first be read back from the directory, and only
* changed attributes will be modified.
* @param obj Object to update
*/
public void update(DirObject obj) ;

/**
* Setter for marhsaller. This will normally be injected by Spring,
* but is exposed as part of the interface in case you want
* to control the Marshalling strategy
* @param m Marshaller instance that knows how to marshal directory
* attributes to/from an object
*/
public void setMarshaller(Marshaller m);

/**
* Very simple search function. Will search at the base dn and subtrees for
* objects which meet the filter criteria. Objects will be marshalled into the
* list.
*/
public List search(Name base, String filter );

}


Putting it all together, here is a sample test that shows how the API
is used (some code has been elided for brevity...)



...

public class SlapperTest {
BeanFactory factory;
LdapDAO userDAO;

public SlapperTest() {
Resource r = new FileSystemResource("test/beans.xml");
factory = new XmlBeanFactory(r);
userDAO = (LdapDAO) factory.getBean("userDAO");
}


@Test
public void crudTest() throws InvalidNameException {
UserAccount ua = new UserAccount();
Name name= new LdapName("uid=test2, ou=People");

ua.setName(name );
ua.setCommonName("Fred Flinstone");
ua.setLastName("Flinstone");
userDAO.create(ua);

// try to create the user twice - should get an error
try {
userDAO.create(ua);
fail("Expected to get an exception");
} catch(DataIntegrityViolationException ex) {
System.out.println("OK - Got expected exception" + ex);
}

UserAccount ua2 = (UserAccount)userDAO.getObject(name);

System.out.println("Got user back " + ua2 + " is equal " + ua2.equals(ua) );

assertNotNull(ua2);

// Dn compare is broken due to bug in LdapName.equals() .....
//assertEquals( ua, ua2);
// so compare some other attribute
assertEquals( ua.getCommonName(), ua2.getCommonName());

// update the user. Only the changes will get sent to the directory
ua.setMobile("555-1212");
ua.setLastName("Rubble");
userDAO.update(ua);
// read it back and make sure the mobile got set
ua2 = (UserAccount)userDAO.getObject(name);
assertEquals( ua.getMobile(), ua2.getMobile());

// delete the user
userDAO.delete(ua);
System.out.println("deleted user " + ua);
// deleting twice seems to be silentluy ignored. OK?
userDAO.delete(ua);
}

@Test
public void simpleSearchTest() throws InvalidNameException {

LdapName base = new LdapName("ou=People");

//String filter = "& (objectclass=inetorgperson) (uid=user*)";

// build a filter that matches all inetorgperson objects whose
// uid starts with user*
// These are the sample users created by the OpenDS installer
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "inetorgperson"));
filter.and(new WhitespaceWildcardsFilter("uid", "user "));
System.out.println("Using filter " + filter.encode() );

// List l will be populated with UserAccount objects
// Assumes we have some sample users in the directory
List l = userDAO.search(base, filter.encode());
assertEquals( l.get(0).getClass(), UserAccount.class );

System.out.println("Got search result =" + l);

}

}


Powered by ScribeFire.

Is it just me, or is LdapName.equals() brain dead? My expectation is that two LdapName's are equal if they represent the same underlying Ldap Dn. Instead, the comparison seems to be case and white space sensitive. OK, I see I'm not the only one who has this problem.

Powered by ScribeFire.

Tuesday Mar 27, 2007

Curious about SPML 2.0 ? Here is an overview presentation that nicely explains the core concepts.

Powered by ScribeFire.

Thursday Mar 01, 2007





The new security model in SEAM makes it easy to plug in different authentication mechanisms. After a couple of hours of hacking (OK, it was more like 6 hours...),  I had the sample SEAM booking demo using OpenSSO for authentication. 


The user enters their OpenSSO username and password on the Booking demo log on page, and the application uses the OpenSSO APIs to authenticate to an OpenSSO server. [If you are trying this out at home, remember that the user must be registered in both OpenSSO AND the SEAM application for this to work. If the user authenticates to OpenSSO, but is not registered in the Booking demo, they will get an error message to this effect].



This code has a long way to go to be truly useful. For example, it does not handle any of the following:

  • Single Sign On with OpenSSO. The current example is authentication only. It does not check for an existing SSO token
  • Redirection to the OpenSSO login page
  • SEAM or JEE Role support
  • Session timeouts, enforcement of URL policy, single logout, etc.


Policy Agent vs. Agentless Deployment



The easiest way to obtain the above missing features is to install an OpenSSO policy agent in the hosting container (GlassFish, in my case).   That being said, I would really love to be able to get this working without requiring the installation of a policy agent.

It would be nice to hand someone a .war file, and have it "just work" without any modification of the container.   This may turn out to be a rather difficult exercise. We will see how far I get...


Deploying the Example



If you are interested in playing with the example, you can download the NetBeans project from http://mediacast.sun.com/share/warren/seamBookingWithOpenSSO.zip

You will also need to download and install OpenSSO, and I suggest you also get the OpenSSO examples as well.

The deployed .war file must contain the amsdk.jar file. In addition, you will need to include an AMConfig.properties file that matches your environment. This configuration file is used by the client code to find and authenticate against the OpenSSO server.

The easiest way to generate a valid AMConfig file is to run the "setup" script in the OpenSSO examples, and test it using the sample "login" script.


Most of the action (no pun intended) is in the AuthenticatorAction.java code. Pasted here for your viewing pleasure:


package org.jboss.seam.example.booking;

import com.sun.identity.authentication.AuthContext;
import com.sun.identity.authentication.spi.AuthLoginException;
import java.io.IOException;
import java.util.List;
import static org.jboss.seam.ScopeType.EVENT;
import static org.jboss.seam.ScopeType.SESSION;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.core.FacesMessages;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Identity;

/**
 *
 * @author warren
 */
@Stateful
@Scope(EVENT)
@Name("authenticator")
public class AuthenticatorAction implements Authenticator {
    @In Identity identity;
   
    @PersistenceContext EntityManager em;
   
    @Out(required=false, scope = SESSION)
    private User user;
   
    @Logger private Log log;
   
    private static final String orgName = "opensso";
    private static final String moduleName = "DataStore";
   
    /**
     *
     * @return
     * @throws com.sun.identity.authentication.spi.AuthLoginException
     */
    protected AuthContext getAuthContext() throws AuthLoginException {
        AuthContext lc = new AuthContext(orgName);
        AuthContext.IndexType indexType = AuthContext.IndexType.MODULE_INSTANCE;
        lc.login(indexType, moduleName);
        return lc;
    }
   
    /**
     *
     * @param lc
     * @return
     * @throws UnsupportedCallbackException
     */
    protected boolean login(AuthContext lc) throws UnsupportedCallbackException {
        boolean succeed = false;
        Callback[] callbacks = null;
       
        // get information requested from module
        while (lc.hasMoreRequirements()) {
            callbacks = lc.getRequirements();
            if (callbacks != null) {
                addLoginCallbackMessage(callbacks);
                lc.submitRequirements(callbacks);
            }
        }
       
        if (lc.getStatus() == AuthContext.Status.SUCCESS) {
            log.info("Login succeeded.");
            succeed = true;
        } else if (lc.getStatus() == AuthContext.Status.FAILED) {
            log.error("Login failed.");
        } else {
            log.error("Unknown status: " + lc.getStatus());
        }

        return succeed;
    }
   
    private void addLoginCallbackMessage(Callback[] callbacks)
            throws UnsupportedCallbackException {
        int i = 0;
        try {
            for (i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof TextOutputCallback) {
                } else if (callbacks[i] instanceof NameCallback) {
                    handleNameCallback((NameCallback)callbacks[i]);
                } else if (callbacks[i] instanceof PasswordCallback) {
                    handlePasswordCallback((PasswordCallback)callbacks[i]);
                } else
                    throw new UnsupportedCallbackException(callbacks[i]);
            }
        } catch (IOException e) {
            log.error("Login Failed", e);
            throw new UnsupportedCallbackException(callbacks[i],e.getMessage());
        }
    }
   
    private void handleNameCallback(NameCallback nc) throws IOException {
        nc.setName(identity.getUsername());
    }
   
    private void handlePasswordCallback(PasswordCallback pc) throws IOException {
        String passwd = identity.getPassword();
        pc.setPassword(passwd.toCharArray());
    }
   
    /**
     *
     * @return true if the user was succesfully authenticated, false otherwise
     */
    public boolean authenticate() {
        log.trace("Username=" + identity.getUsername() +
                " password=" + identity.getPassword());
        try     {
            User user = null;
            AuthContext lc = getAuthContext();
            log.debug("Got Authcontext=" + lc);
           
            if (login(lc)) {
                log.info("OpenSSO login for user #0 succeeded", identity.getUsername());
               
                if( ! fetchDBUser(identity.getUsername())) {
                    String msg = "OpenSSO login succeded, but there is no user with this name in the datbase";
                    log.error(msg);
                    FacesMessages.instance().add(msg);
                    return false;
                }
               
                return true;
            }
            return false;
        } catch (AuthLoginException ex) {
            log.error("AuthLogin problem", ex);
            return false;
        } catch(UnsupportedCallbackException ex2) {
            log.error("Callback problem", ex2);
            return false;
        }
    }
   
    private boolean fetchDBUser(String username) {
       
        List results = em.createQuery(
                "select u from User u where u.username=:username")
                .setParameter("username", username)
                .getResultList(); 
        if ( results.size()==0 ) {
            return false;
        } else {
            user = (User) results.get(0);
            return true;
        }
    }
   
   
    @Remove @Destroy
    public void destroy() {}
}




powered by performancing firefox

Wednesday Feb 21, 2007



After a little hacking, I have the new JBoss Seam booking demo running under GlassFish. This is based on Seam 1.1.6GA and uses the new Seam security model (which looks very cool).

The sample source code that ships with the Seam 1.1.6 glassfish demo is missing some crucial settings:
  • Your web.xml needs a ejb-ref definition to the new Authenticator bean
  • Seam security requires a drools securtyRules.drl config file
  • You need to include the associated drools libraries (even if you don't use drools)
  • components.xml needs to declare the location of the drools securtyRules.drl file


I also reworked the demo to make it open as a Netbeans Enterprise project.  If you want to run this, you will need:

  • GlassFish. I used the latest 9.0 UR1 build
  • Netbeans. I used Netbeans 5.5.1 (it has some fixes to integrate with later GlassFish builds)
  • The Seam 1.1.6GA distribution

You can download the NetBeans project file at  http://mediacast.sun.com/share/warren/seamBooking.zip


You will need to define a few Libraries using the NetBeans library manager.  I wish there was a nice way of exporting library definitions in NetBeans - but until then I have resorted to the hack of packing up my library definitions from ~/.netbeans/....  as a zip file. If you are brave, you can try dropping these into your own private NetBeans library folder (after editing the library paths), or failing that, have a look at the xml defs and it should be fairly obvious which jar files you need to include. 

Here are the Netbeans library xml definitions: http://mediacast.sun.com/share/warren/SEAM-Libraries.zip


Enjoy





powered by performancing firefox


A few folks asked for an electronic copy of my presentation (Identity Management Lessons learned) given at the reboot Security and Privacy conference.
You can download the OpenOffice format from here

Monday Feb 12, 2007



Those crafty ThoughtWorkers stole my idea! 

Buildix  provides a turnkey vmware image with all the goodies to kick start a development project, including subversion, a project wiki, a bug tracker,  and Cruise Control. 

In my humble experience, many organizations lack a basic project support infrastructure - and you often spend a couple of weeks pulling this stuff together. Buildix is going to be a huge time saver.   This is exactly the sweet spot for virtualization.



[PS: I'd love to see this running as a Xen image on OpenSolaris!]






powered by performancing firefox

Sunday Feb 11, 2007



A nice introduction to i-Names can be found here http://www.xdi.org/i-names-explained.html


In a nutshell, an i-Name adds a level of indirection to an address. This is certainly an idea whose time has come. I have often wondered why the postal service has never adopted such a scheme?




powered by performancing firefox

Saturday Jan 06, 2007



Eric Bautsch has kindly given me permission to post his latest updates for running slimserver on Solaris. He has made a few improvements over my previous scripts, so you might want to use these instead.


Here is a shell script used to build the slimserver distribution:

cd /usr/local
gunzip -c /sw/src/audio/slimserver/SlimServer_v6.5.0.tar.gz | tar xfv - \
        >> ${LOG} 2>&1
mv SlimServer_v6.5.0 slimserver >> ${LOG} 2>&1
mkdir /usr/local/slimserver/cachedir >> ${LOG} 2>&1
chown -R slimsrv:slimsrv slimserver >> ${LOG} 2>&1
cd slimserver/Bin >> ${LOG} 2>&1
export PATH=${PATH}:/opt/SUNWspro/bin:/usr/sfw/sbin
echo "/usr/bin/perl\n/usr/local/slimserver\n/tmp" | \
        ./build-perl-modules.pl >> ${LOG} 2>&1
cd /tmp/XML-Parser* >> ${LOG} 2>&1
perl Makefile.PL EXPATLIBPATH=/usr/sfw/lib EXPATINCPATH=/usr/sfw/include \
        >> ${LOG} 2>&1
cd /usr/local/slimserver/Bin >> ${LOG} 2>&1
echo "/usr/bin/perl\n/usr/local/slimserver\n/tmp" | \
        ./build-perl-modules.pl >> ${LOG} 2>&1
cp -p /sw/src/audio/slimserver/my.tt /usr/local/slimserver/MySQL >> ${LOG} 2>&1
cp -p /sw/src/audio/slimserver/slimrun /usr/local/slimserver >> ${LOG} 2>&1

svccfg import /sw/src/audio/slimserver/slimserver_manifest >> ${LOG} 2>&1
svcadm enable slimserver >> ${LOG} 2>&1

Note: The above double call of the build-perl-modules.pl with the manual build of XML-Parser was neccessary because XML parser requires expat but appears to be unable to pick up that it's in /usr/sfw, despite set PATH and LD_LIBRARY_PATH.

my.tt is simply modifed in that mysql provided in /usr/sfw does not like innodb, default-character-set and default-collation, so I have hashed these out.

slimrun is attached and simply sets the PATH to find mysqld prior to calling slimserver.



Here is the slimrun script:


#!/bin/sh

PATH=/usr/sbin:/usr/bin:/usr/ccs/bin:/usr/openwin/bin:/usr/dt/bin:/usr/platform/i86pc/sbin:/usr/sfw/bin:/usr/sfw/sbin
export PATH

exec /usr/local/slimserver/slimserver.pl --cachedir=/usr/local/slimserver/cachedir --user=slimsrv --group=slimsrv --audiodir=/apps/jukebox/NEW/xmcd --prefsfile=/usr/local/slimserver/cachedir/slimserver.pref --pidfile=/usr/local/slimserver/cachedir/slimserver.pid $*





Here is the updated manifest

<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type='manifest' name='slimserver'>
  <service name='application/slimserver' type='service' version='1'>
        <create_default_instance enabled='false'/>
        <single_instance/>
<dependency name='name-services' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/milestone/name-services' />
</dependency>
<dependency name='network' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/milestone/network' />
</dependency>
<dependency name='local-filesystems' grouping='require_all' type='service' restart_on='none'>
<service_fmri value='svc:/system/filesystem/local' />
</dependency>
        <exec_method type='method' name='start'
                exec='/usr/local/slimserver/slimrun --daemon'
                timeout_seconds='30' />
        <exec_method type='method' name='stop'
         exec=':kill'
                timeout_seconds='30' />
        <stability value='Unstable' />
        <template>
               <common_name>
                        <loctext xml:lang='C'>Slim Server</loctext>
                </common_name>
                <documentation>
                        <doc_link name='SlimDevices Home' uri='http://www.slimdevices.com/'/>
                </documentation>
        </template>
  </service>
</service_bundle>





powered by performancing firefox

Thursday Oct 12, 2006

Jini Made Easier - Writing a Jini Service for Seven

I gave a presentation to the local Calgary JUG entitled, “Making Jini Easy (or at least easier)”. For those that want to follow along, I have posted the sample Netbeans project at http://mediacast.sun.com/share/warren/ShippingService.zip

This tutorial will show you how to build and deploy a simple Jini service to the Seven container. Seven is a an implementation of the Jini Service Container (JSC) specification. Both Seven and the JSC spec have been developed as part of the cheiron project. As with the Jini platform, Seven is offered under the Apache 2 license.


The JSC specification is a promising direction towards the goal of making Jini easier to use. The basic idea is to let the container handle the heavy lifting with respect to developing and deploying a Jini Service.

Prerequisites

To build and deploy this tutorial you will need the following components:

  • Netbeans (5.5 or 6.0 builds should be fine)

  • JDK 1.5 (note that Seven has an incompatibility with JDK 1.6 – so best to stay on 1.5 for now)

  • A build of the seven suite ( I used 0.1.1 for this tutorial)

Before opening the Netbeans project create a “JSC” library containing the relevant Seven jar files. Here is what mine looks like (you will need to change this to suit your environment)






The Shipping Service Example

The tutorial presents a simple service that calculates shipping costs for packages based on their weight and the destination postal code. The business interface (this is what the client sees) to the service is simple:

package demo;

import java.rmi.RemoteException;

/**
 *
 * @author warren
 */
public interface ShippingService {
    public float calculateShippingCosts(String postalcode, float packageWeight ) throws RemoteException;
}


For simplicity, our shipping service only takes a destination postal code – but you get the idea behind the interface.

Remote Interface

The remote interface (what the services provides) is as follows:


package demo;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Map;

/**
 * Remote interface for the shipping service. This is
 * the "private" backend protocol between the proxy 
 * and the service.
 * @author warren
 */
public interface ShippingServiceRemote extends Remote {
    public Map getShippingTables() throws RemoteException;
}


If you have not used Jini before, the above may look rather odd., in that the client's view of the service is not the same as what the service actually provides! The missing link here is the “smart proxy”:, which bridges the client to the service. Let's have a look:



package demo;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.Map;

/**
 * proxy that gets downloaded to the client.
 * The proxy implements caching of rate tables
 * vended by the backend service. The proxy
 * provides local lookup of shipping rates.
 * 
 * @author warren
 */
public class ShippingServiceProxy implements ShippingService, Serializable {
    
    private Map shippingTables;
    private final ShippingServiceRemote server;
    
    /** Creates a new instance of ShippingServiceProxy */
    public ShippingServiceProxy(Object connectorStub) {
        server = (ShippingServiceRemote)connectorStub;
    }
    
    public float calculateShippingCosts(String postalcode, float packageWeight) throws RemoteException {
        // Do we need the fetch the rate tables from the service?
        if( shippingTables == null )
            shippingTables = server.getShippingTables();
        
        Float costFactor = (Float) shippingTables.get(postalcode);
        if( costFactor == null )
            throw new IllegalArgumentException("Bad Postal code");
        
        return packageWeight * costFactor.floatValue();
    }
    
    private static final long serialVersionUID = -2719045781828648539L;
}


The smart proxy is dynamically downloaded to our client when it looks up our service in the Jini LUS. This is all part of the magic of Jini – which we really don't have time to cover here, but please read Jan Newmarch's excellent tutorials if you are curious.


Smart proxies are one of things that makes Jini so powerful. In the above example, we can see that our proxy is performing local caching of shipping rate tables that it receives from the service. This turns most shipping rate calculations into a local lookup in a rate table instead of a remote procedure call. This is a good example of a service which is impractical to implement as a Web Service, but works fine in Jini (imagine the performance penalty of trying to look up several hundred shipping costs per second using a remote ShippingService interface). Now of course, as a service provider we could advertise the ShippingServiceRemote interface - but this exposes a lot implementation detail to our clients. If the algorithm changes we will have to ask our clients to update their code. The use of smart proxies gives us a clean interface along with a flexible implementation strategy.


The only remaining piece is the actual service code. This is where Seven helps us by providing a container that handles much of the service registration and configuration code. Here is our rather simple service:


package demo;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import net.jini.core.entry.Entry;
import net.jini.lookup.entry.Name;
import org.cheiron.jsc.JSCException;
import org.cheiron.jsc.JSCFailure;
import org.cheiron.jsc.JiniService;
import org.cheiron.jsc.ServiceContext;
import org.cheiron.jsc.ServiceState;

/**
 * The backend Shipping Service provider.
 * The private protocol between the provider and proxy is
 * an exchange of shipping rate tables. This demonstrates
 * how a proxy can improve performance through caching. 
 * 
 * @author warren
 */
public class ShippingServiceProvider implements JiniService {
    private ServiceContext context;
    
    public void failureDetected(JSCFailure failure) {
    }
    
    public Entry[] getAttributes(Entry[] attributes) throws JSCException {
        return new Entry[]{ new Name("Shipping Cost Calculator Service") };
    }
    
    public Remote[] getServiceProviders() throws JSCException {
        return new Remote[]{ new ProviderImpl() };
    }
    public Object getServiceProxy(Object connectorStub) throws JSCException {
        return new ShippingServiceProxy(connectorStub);
    }
    public void init(ServiceContext context) throws JSCException {
        this.context = context;
    }
    public void stateChanged(ServiceState newState, ServiceState oldState) {
    }
    
    /**
     * The Implementation class - builds and returns
     *  a map of the shipping costs from various postal codes
     */
    private class ProviderImpl implements ShippingServiceRemote {
        public Map getShippingTables() throws RemoteException {
            HashMap map = new HashMap();
            map.put("T2N3L2", new Float(1.21));
            map.put("123456", new Float(1.97));
            return map;
        }
    }    
}


In the above example, the Remote interface is implemented as an inner class. There are a few required life cycle methods – most of which we leave empty for this example.


By implementing the JiniService interface, our service can delegate many tasks to the Jini Service Container. These include:

  • Service Registration with the LUS (Lookup Service)

  • Lease management of our registration with the LUS

  • Life Cycle Management. The container manages creating, starting, stopping and destroying our service

  • Exporting our service proxy

  • Service Configuration

Seven provides other features that I am not going to cover here (for example, server side lease management and service state persistence ).

Building and Deploying

To deploy our service to Seven, we need to create a deployment descriptor. You will find this under the config/ directory in the sample Netbeans project:


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jsc-descriptor
    xmlns="http://www.cheiron.org/jsc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.cheiron.org/jsc http://www.cheiron.org/schema/jsc.xsd">
    
    <service-group-def oid="2.3.6.1.4.12543.2.3" version="0.1.1">
        
        <description-group>
            <description>Provides Shipping cost calculations for packages</description>
            <display-name>Shipping Cost calculator</display-name>
        </description-group>
        
        <grant-def>
            <permission
                class="net.jini.discovery.DiscoveryPermission" id="perm.discovery">*</permission>
        </grant-def>
    </service-group-def>
    
    <service-def class="demo.ShippingServiceProvider"/>
    
</jsc-descriptor>

The above descriptor declares a service group which is a mechanism used to manage like services as a unit. IANA Object ID's are used to uniquely define service groups (disclaimer: the above OID is not a valid number – it's one I made up!). I'm not sure I am crazy about the use of OIDs as they seems a bit cumbersome to me. My preference would be to use java style package names (reverse dns).


Any permissions required by the service are declared in the deployment descriptor. Seven takes care of standard permissions that are needed to export your proxy, so these do not need to be declared.


Finally, we have our ant build.xml that creates our service archive (.sar) that is deployed via the seven Admin UI:


<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<project name="jsc-demo" default="default" basedir=".">
    <description>Builds, tests, and runs the project</description>
    <import file="nbproject/build-impl.xml"/>
    <property file='build.properties'/>
    
    <taskdef resource="cheirontasks.properties">
        <classpath>
            <fileset dir="${seven.home}/tools/jini/lib" includes="*.jar"/>
        </classpath>
    </taskdef>
     
    <path id="jsc">
        <pathelement location="build/classes"/>
        <fileset
        dir="${seven.lib}"
        includes="cheiron-util.jar,jsc-platform.jar,jsc-platform-lib.jar,serviceui.jar"/>  
    </path>
    
    <path id="jsc-test">
        <path refid="jsc"/>
        <pathelement location="build/test/classes"/>
        <path path="lib/jsk-platform.jar"/>
    </path>
    
    <target name="create-dl" depends="compile">
        <!-- Create the Service jar file -->
        <dljar
            destfile="build/services.jar"
            platform="jsc"
            inroot="demo">              
            <classpath refid="jsc"/>  
            <class name="demo.ShippingServiceProvider"/>         
            <rootdir name="${build.dir}/classes"/>
        </dljar>
        <!-- Create the codebase download jar file.  -->
        <dljar destfile="build/services-dl.jar" platform="jsc-dl">
            
            <classpath refid="jsc"/>
            <rootdir name="demo"/>
            <apiclass name="net.jini.lookup.entry.Name"/>   
            <proxyclass name="demo.ShippingServiceProxy"/>
        </dljar>
    </target>
    
    <target name="create-sar" depends="create-dl">
        <sar
            destfile="dist/${ant.project.name}-${version}.sar"
            jscxml="config/shippingservice-jsc.xml"
            version="0.1.1">
            
            <lib dir="${build.dir}" includes="services.jar"/>      
            <libdl dir="${build.dir}" includes="services-dl.jar"/>
        </sar>
    </target>
    
    <target name="run-seven">
        <exec executable="${seven.cmd}" />
    </target>
    
    <target name="jini-servicebrowser">
        <exec executable="${incax-browser}" spawn="true"/>
    </target>  
</project>


Seven provides two Ant tasks (dljar and sar) that are used to create the service jars and the service archive. The dljar task (based on the ClassDep code found in the Jini Starter kit) makes it easier to generate download jar files with the correct classes and preferred list. Codebase is beyond the scope of this blog entry, but suffice to say that the service-dl.jar file created in the above example contains the classes that must be dynamically downloaded to our client. When we deploy the .sar file to seven, it will ensure that the codebase is set to correctly annotate our service proxies. The Seven JSC runs it's own internal HTTP server for the codebase jar files.


If you have successfully built the project (run the create-sar task), you should be able to bring up the seven admin UI and start your service. Make sure Seven is running (run seven.bat from the install directory, or you can try the included ant task).


In order to access the Seven UI you will need a Jini service browser that supports the ServiceUI specification. In a nutshell, ServiceUI is way of attaching a graphical interface to a service. A ServiceUI client can dynamically load this UI to interact with and manage a remote service instance. You can use the browser included in the Jini Starter kit, or another nice alternative is the free service browser available from Inca X. The following screen shot shows the seven Admin ServiceUI running under IncaX:





To deploy your service, install the sar file that has been created under your dist/ directory. From the above menu, choose ServiceArchive, browse your .sar file, and upload the archive to Seven. Once uploaded, your service group should show up in the JSC container panel on the right hand side. Right click on your service group, and choose “Add Service”. This should prompt you for service and group instance names.


Right click over the new service entry and select "Start" from the context menu. You should see your service interface (ShippingService) show up in left panel of the browser. This confirms that your service has been deployed and is now registered with the Jini Lookup Service (LUS):






Let's wrap up by showing you one last neato feature of the Inca X browser. If you expand the interface tree for your service, you can see the methods that your service provides. Inca X lets you invoke service methods via the browser. For simple testing, this saves your from writing a test client. Click on your method (in our case calculateShippingCosts), enter parameter values for the call, and press the invoke button:






You should see the result of invoking the service (note that our sample service only accepts two postal codes!).

Now that wasn't so hard, was it?

Thursday Oct 05, 2006

Summary of Jim Waldo's Keynote at the 10th JCM


[Disclaimer: This is my summary of Jim's keynote from the last JCM. It is not a transcript; Any errors are probably mine! Thanks to Jerome Bernard for video taping Jim's presentation! The presentations and videos from the JCM can be found here]


Jim is thinking about things that cause a quantum jump in complexity. You are OK, and then something changes in a big way.


The basic jumps in complexity occur at these points (I add abbreviations here to refer to them later on)


  • Sequential (SEQ)– life is good, life is easy.

  • Multi-threaded (MT) – takes retooling and a competent programmer to think about MT

  • Multi-Process (MP) – For everyone other than kernel developers, this came before MT

  • Multiple Machines (MM) on the same network. Not the same as multi-process, but some people think it is

  • Multiple Untrusted Machines (MMU) – Essentially the web


All of the above cause discontinuities in the programming model. As you move through each stage, you lose something:


  • seq -.-> MT – you lose ordering (multiple things can happen at once) This is hard – as we naturally think sequentially.


  • Move to multiple processes – lose single context (i.e. A shared context that we can rely on). Global state is used all the time in development (think anything static).


  • Multiple Processes to Multiple Machines – state gets lost. Global State of your “system” is a fiction. There is no consistent state in an interesting distributed system (Jim references Lamport's work on this). Distributed OS projects attempt to introduce global state – they have largely failed.

  • Move to untrusted machines. You lose trust. In the difficult position of not knowing who you can trust.

But you also gain some things as you move through the discontinuities (otherwise why would you do it?)


  • Seq-> MT – you gain parallelism

  • MT – MP – you gain isolation (gives you safety)

  • MP to MM gives you independent failure (parts of your system can survive if things fail)

  • MM to MMU – gives you scale (web scale, Internet scale). Use someone else's resources (or allow someone to use ours).

The Platform


The platform – allows us to do the work. The model the developer sees:


  • SEQ – A batch OS is fine.

  • MT -language extensions needed for correctness (ensure system wont re-order things underneath you)

  • MP – need communication mechanism between the processes.

  • MM – Not clear what the platform is? Attempts include RPC (invented at PARC), CORBA, Jini, XML/SOAP. Not clear we have figured this out. We know what it is not. Grids trying to be the platform.


Most grids are an attempt to do batch on a large scale (scientific). Scheduling jobs. Use individual OS in any way you wish – not a platform, but a way of aggregating platforms. How do you give a programming abstraction. Jini is a good attempt – but only the beginning. What is outside the grid, vs inside. Outside – untrusted, inside is trusted – mutual trust


Two discontinuities – inside/outside – we are conflating them – trying to solve all the problems at once won't work. We need two solutions.


Jini 1.0 – built for the 1st discontinuities – MM but assumes full trust. Built on mobile code.

Ad-hoc organization – changes over time. When a service enters into the system, you could trust it.

Failure handled – but not a failure of trust.

Types – the way which you identity things


Jini 2.0 – Multiple Untrusted Machines. Adds security. This is hard to do with mobile code. Adds a lot of complexity (e.g. proxy verification). Perhaps too hard.

Configuration – deployment control. Deployment errors cause a lot of failures in distributed systems. A lot of the complexity is making services reliable for all possible deployments. Maybe we could communicate the reliability needs in the language. Needs to be part of the platform.


Program vs. deploy – we are trained to keep them separate – but this probably makes life harder. Famous disclaimers “We will put management in later” (just like “we will put security in later”).


Services – the things we assume are always there (but sometime they might not be!) Example: persistence. A file system may not always be there in all environments. What would a good persistence service look like?


Containers – is a function converting a type to another type. Must be able to import things, give bindings, yields another container, Containers are type functions.


Virtual machines. JVM is an example. VM is the important thing – not necessarily the JVM. Universal binary allows us to move code and data. Jini is Java VM thing. Other Vms might be interesting (e.g. ones that fix classloader problems?) The abstraction is the important thing.


Feeling of Deja Vu for Jim. Back when all kinds of different O/S existed there were lots of debates “Why mine is better than yours”. What arose was UNIX and the “other” (i.e. There was a lot of convergence about what an O/S looks like). Model of the platform happened after lots of experimentation and discussion. Are we at the same stage as 30 years ago with O/S – a breakthrough may be close.


Thursday Aug 03, 2006



Good news for fellow glassfisher's who want to play with SEAM.

According to this thread the JSF problems that impacted SEAM have now been fixed. You will want to be running Glassfish V1 B04 or later, or Glassfish V2 B10+.

Monday Jul 17, 2006

I created a couple of flash demos of JCAPS 5.1 for a customer presentation. I thought I would share these demos here in case anyone is interested. Note these are not comprehensive (there are many features not covered), nor do I claim that they are action packed or thrilling (unless you like this sort of thing :-) )


The first flash recording is a demo of eDesigner using a fictional application called Duke's bank. It is approx 16 minutes in length.


The second is a demo of eManager and shows how to monitor the application (approx. 6 minutes).

Wednesday Jul 05, 2006

For those of you interested in playing with JBoss SEAM on Glassfish, here are some tips which will hopefully get you going.


Disclaimer: I got this working, but I don‘t claim that my hacks are the right solution. There seem to be a few quirks/bugs in the booking demo that are problematic on Glassfish.


Before proceeding you will want to read Brian and Roger's blogs on the subject.


Here are the modifications that I made to the demo (note, no modifications to SEAM 1.0.1. are required):


Create a Netbeans enterprise project

You might want to use Brian‘s Netbeans project as a starting point, or alternatively (the route I chose) – create a Netbeans enterprise project and import the SEAM booking demo code into the project.

Update ejb-jar.xml

The ejb-jar from the example contained the binding declaration, but was missing the interceptor definition (I gather JBoss App Server does not require this). This is what my modified ejb-jar.xml looks like:

<ejb-jar>
<assembly-descriptor>
<interceptors>
<interceptor>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
<!-- use other elements as per your requirement-->
</interceptor>
<!-- define other interceptors here-->
</interceptors>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>

</assembly-descriptor>
</ejb-jar>

Update web.xml

The web.xml setup is esentially as per the SEAM documentation with the caveat that Glassfish requires you to declare local ref‘s to your session beans (There is probably a good reason for this – but JBoss seems to get away without these declarations). Here is the modified web.xml (note that I named my project “reservit” and this is reflected in the ejb local ref‘s. You will need to update this to match your project deployment descriptor).

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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_2_5.xsd">

<!-- Seam -->
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>

<!-- Propagate conversations across redirects -->
<filter>
<filter-name>Seam Redirect Filter</filter-name>
<filter-class>org.jboss.seam.servlet.SeamRedirectFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>Seam Redirect Filter</filter-name>
<url-pattern>*.seam</url-pattern>
</filter-mapping>

<!-- JSF -->

<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>

<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>

<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>true</param-value>
</context-param>



<!-- Todo: this is already provided in the init code - not sure if it is needed
-->
<context-param>
<param-name>org.jboss.seam.core.init.jndiPattern</param-name>
<param-value>java:comp/env/reservit/#{ejbName}/local</param-value>
</context-param>

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<!-- Faces Servlet Mapping -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.seam</url-pattern>
</servlet-mapping>

<ejb-local-ref>
<ejb-ref-name>reservit/BookingListAction/local</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.BookingList</local>
<ejb-link>BookingListAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>reservit/RegisterAction/local</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.Register</local>
<ejb-link>RegisterAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>reservit/ChangePasswordAction/local</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.ChangePassword</local>
<ejb-link>ChangePasswordAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>reservit/HotelBookingAction/local</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.HotelBooking</local>
<ejb-link>HotelBookingAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>reservit/HotelSearchingAction/local</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.HotelSearching</local>
<ejb-link>HotelSearchingAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>reservit/LoginAction/local</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.Login</local>
<ejb-link>LoginAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>reservit/LogoutAction/local</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.Logout</local>
<ejb-link>LogoutAction</ejb-link>
</ejb-local-ref>
</web-app>

Update the JPQL queries.

I found the booking example queries would not parse correctly with the Glassfish/Toplink persistence manager [note: you need to update the persistence.xml file to use Glassfish/Toplink]. I presume this is a standard‘s thing – but I don‘t know which implementation is correct. You will need to change queries of the form:


bookings = em.createQuery("from Booking b where b.user.username = :username order by b.checkinDate")

To this:

bookings = em.createQuery("select b from Booking b where b.user.username = :username order by b.checkinDate")

Fix the s:link tags

I found that the linkStyle=“button” attribute of the SEAM link tag causes XML parsing errors. This could be a facelets and/or JSF 1.2 bug? (suggestions welcome). I removed this attribute – which fixes the XML parsing error at the expense of not rendering the link as a button. Change this:

<s:link value="Cancel" action="login"  linkStyle="button" buttonClass="button"/>

To this:

<s:link value="Cancel" action="login" buttonClass="button"/>

Fix XML parsing wierdness.

This falls into the not-really-sure-what-I-am-doing category. I found a lot of parsing errors with the facelet .xhtml files. In particular:

  1. I found the DOCTYPE header on most of the .xhtml files caused problems. This appears to be related to the facelets templating mechanism. Getting rid of the header made this problem go away.

  2. Non breaking spaces such as amp;nbsp; or amp;#160; caused parsing problems. Once again – this looks like some kind of templating problem.

  3. Beware of comments. While experimenting I would often put suspect code in XML comments. That is, until I realized the comments are parsed by the facelets template engine, causing runtime parsing errors.

If you have suggestions please leave a comment!

This blog copyright 2009 by warren