5 Replies Latest reply on Jun 16, 2004 2:40 PM by davidchen

    how can I keep a kind of "global" object (Singleton) in JBos

    davidchen

      Hi, there:

      here is my problem:
      1. we want to keep a copy of a huge database view in JBoss side (because, the view is huge, so, only one copy will be created and maintained);
      2. when request comes from client, we should check if that copy is outdated or not (less than 10 mins since last update), if outdated, then update the copy from database, otherwise, just send the copy to clients.
      3. this component is not used to update database behind (read only).


      We are using Jboss2.4.8, oracle9i. Currently, I think to use RMI Service may be an option for this issue. So, thanks in advance if anyone have any clue for it.

      Thanks
      David

        • 1. Re: how can I keep a kind of
          jamesstrachan

          David,

          I would suggest that you use an Entity Bean that contains the contents of the view.

          The Entity Bean should have an artificial key, as shown below, so that one and only one instance ever exists.

          /**
          * This static string will be used to define the key for the Singleton Role Set.
          */
          public static final String SINGLETON_NAME = "default";


          The Bean can then contain an arrayList containing the rows of the view, which can be refreshed from the underlying DB as required, and supplied to the clients.

          I was going to attach a sample of code that uses this technique, this facility seems to have been removed !

          James

          • 2. Re: how can I keep a kind of
            davidchen

            Thank you very much James for your reply. By the way, could you please send me the sample code you mentioned above. My email address is : dchen@digital-dispatch.com
            However, questions for your approach are:
            1. To make sure only 1 instance of entity bean exists, does that mean, we should configure cache and pool configuration in jboss? like set cache time, cache maxinstance, etc. ? any idea how?
            2. However, when several requests come from client at the same time, in JBoss, several ejb object will be activated, so, how can you make sure only 1 entity bean object is activated at that time?

            Thanks a lot and could you please or anyone please give me those answers.

            David

            • 3. Re: how can I keep a kind of
              jae77

               

              "jamesstrachan" wrote:

              I was going to attach a sample of code that uses this technique, this facility seems to have been removed !


              just cut and paste it into a code block ([ code ] [ / code ] minus the spaces). others may want this in the future, or better yet, create a wiki entry for it.

              • 4. Re: how can I keep a kind of
                jamesstrachan

                Brace yourselves, here is the complete code :-

                //Package: Security Objects
                //Class: SecurityRoleSetBean
                //Version: 1.0
                //Copyright: Copyright (c) 2002
                //Author: James Strachan
                //Company: Milton Software Components
                //Description: Entity Bean holds a set of all roles known in the application.
                //Amendment History:-
                //Version Date Author Modification
                //0.a 23/05/2002 JAS Conception
                //0.b 02/01/2003 JAS Convert to Log4J Error Logger
                //1.0 18/02/2003 JAS Release (no changes).
                
                package org.milton.services.security;
                
                import java.util.ArrayList;
                import java.util.Collection;
                import java.util.Iterator;
                
                import javax.naming.Context;
                import javax.naming.NamingException;
                import javax.naming.InitialContext;
                
                import java.rmi.RemoteException;
                import javax.rmi.PortableRemoteObject;
                
                import javax.ejb.*;
                
                import java.sql.Connection;
                import java.sql.PreparedStatement;
                import java.sql.ResultSet;
                import javax.sql.DataSource;
                import java.sql.SQLException;
                
                import org.apache.log4j.Logger;
                
                import org.milton.services.common.EJBNameFactory;
                
                /**
                 * This class is a singleton Entity Bean that stores the master set of Roles.
                 * <p>
                 * We enforce the singleton restriction by checking on the key used.
                 * <p>
                 * Calling applications should use the static String <code>SINGLETON_NAME</code>
                 * as the key for the Entity.
                 * @author J.A.Strachan
                 * @version 1.0
                 */
                public class SecurityRoleSetBean implements EntityBean {
                
                 private static final String DATASTORE_ADDRESS = EJBNameFactory.MILTON_DATASTORE_ADDRESS;
                
                 private EntityContext ctx;
                
                 /**
                 * Log4J Logger for error logging.
                 */
                 private static Logger logger;
                
                 /**
                 * The identifier for a Role Set. Not of much value as this bean is a singleton.
                 * Is required by EJB, and must be public to be available for use by the EJB container.
                 */
                 public String defaultName;
                
                 /**
                 * Set of Roles loaded from the database.
                 */
                 private ArrayList roles = new ArrayList();
                
                 /**
                 * Flag to say whether roles have changed.
                 */
                 private boolean rolesChanged = false;
                
                 /**
                 * This static string will be used to define the key for the Singleton Role Set.
                 */
                 public static final String SINGLETON_NAME = "default";
                
                 /**
                 * No argument constructor - initialise Logger.
                 */
                 public SecurityRoleSetBean() {
                
                 if ( logger == null ) {
                 logger = Logger.getLogger( this.getClass() );
                 }
                
                 }
                
                 /**
                 * Get the set of Roles.
                 * @return ArrayList - the master set of Roles.
                 */
                 public ArrayList getRoles() {
                
                 return roles;
                
                 }
                
                 /**
                 * Set the master set of Roles.
                 * Check the roles to see if any have changed, and set the roles flag if so.
                 * @param roles
                 */
                 public void setRoles( ArrayList roles ) {
                
                 rolesChanged = !this.roles.equals(roles);
                 this.roles = roles;
                
                 }
                
                 /**
                 * Standard create method.
                 * <p>
                 * This method should <b>NEVER</b> be called unless the underlying database
                 * has first been emptied of data.
                 * @param defaultName
                 * @param roles
                 * @return String defaultName - primary key of created object.
                 * @throws RemoteException
                 * @throws CreateException
                 */
                 public String ejbCreate( String defaultName, ArrayList roles )
                 throws RemoteException, CreateException {
                
                 Connection conn = null;
                 PreparedStatement statement = null;
                 InitialContext context = null;
                 DataSource dataSource;
                 int rowCount = 0;
                
                 logger.info( "Create called : " + defaultName );
                
                 // Check that the caller is using the only legal name.
                 if ( !defaultName.equals(SINGLETON_NAME) ) {
                 throw new CreateException( "Must use name " + SINGLETON_NAME );
                 }
                
                 // Set the primary key.
                 this.defaultName = defaultName;
                 this.roles = roles;
                
                 String sqlStatement = "INSERT into SecurityRoleSet ( rolename ) " +
                 "values ( ? )";
                
                 try {
                
                 // Get a database connection.
                 context = new InitialContext();
                 dataSource = (DataSource) context.lookup( DATASTORE_ADDRESS );
                 conn = dataSource.getConnection();
                
                 // Prepare a statement and set parameters.
                 statement = conn.prepareStatement( sqlStatement );
                
                 Iterator i = roles.iterator();
                 while ( i.hasNext() ) {
                 statement.setString( 1, (String) i.next() );
                 // Execute the statement.
                 statement.execute();
                 rowCount++;
                 }
                 logger.debug( "Inserted " + rowCount + " rows" );
                 rolesChanged = false; // Set roles changed flag to false.
                 }
                 // Error in lookup ?
                 catch( NamingException ne ) {
                 logger.error( ne.getMessage(), ne );
                 throw new CreateException( ne.getMessage() );
                 }
                 // Error in SQL statement ?
                 catch( SQLException sqe ) {
                 logger.error( sqe.getMessage(), sqe );
                 throw new CreateException( sqe.getMessage() );
                 }
                 finally {
                 try {
                 if ( statement != null ) {
                 statement.close();
                 }
                 if ( conn != null ) {
                 conn.close();
                 }
                 }
                 catch ( SQLException e1 ) {}
                 }
                
                 return this.defaultName;
                
                 }
                
                 /**
                 * No work required here - but skeleton is required by EJB.
                 * @param defaultName
                 * @param roles
                 */
                 public void ejbPostCreate( String defaultName, ArrayList roles ) {}
                
                 /**
                 * This standard EJB method locates a RoleSet using the Primary Key.
                 * <p>
                 * Note that Role Set is a singleton - so this method is stripped of all code.
                 * @param defaultName - the Key of the requested Security Object.
                 * @return String defaultName - the Identifier if the RoleSet was located.
                 * @throws RemoteException
                 * @throws ObjectNotFoundException
                 * @throws FinderException
                 */
                 public String ejbFindByPrimaryKey( String defaultName ) throws RemoteException,
                 ObjectNotFoundException {
                
                 if ( !defaultName.equals(SINGLETON_NAME) ) {
                 throw new ObjectNotFoundException();
                 }
                
                 this.defaultName = defaultName;
                 return defaultName;
                
                 }
                
                 /**
                 * This standard EJB method loads the Role Set. To fit EJB standards, the
                 * primary key - defaultName - is read from the Context.
                 * <p>
                 * @throws RemoteException
                 */
                 public void ejbLoad() throws RemoteException {
                
                 Connection conn = null;
                 PreparedStatement statement = null;
                 InitialContext context = null;
                 DataSource dataSource;
                 ResultSet rs;
                
                 String sqlStatement = "SELECT rolename " +
                 "from SecurityRoleSet order by rolename";
                
                 defaultName = (String) ctx.getPrimaryKey();
                
                 logger.info( "Load called : " + defaultName );
                
                 try {
                
                 // Get a database connection.
                 context = new InitialContext();
                 dataSource = (DataSource) context.lookup( DATASTORE_ADDRESS );
                 conn = dataSource.getConnection();
                
                 // Prepare and execute the statement.
                 statement = conn.prepareStatement( sqlStatement );
                 statement.execute();
                
                 // Loop through the SQL result set, building the ArrayList to be returned.
                 // Trim the strings.
                 roles.clear();
                 rs = statement.getResultSet();
                 while( rs.next() ) {
                 roles.add( rs.getString(1).trim() );
                 }
                 rolesChanged = false; // Set flag - roles not changed since load.
                 }
                 // Lookup failed ?
                 catch( NamingException ne ) {
                 logger.error( ne.getMessage(), ne );
                 throw new RemoteException( ne.getMessage() );
                 }
                 // Something nasty in the woodshed ?
                 catch( SQLException sqe ) {
                 logger.error( sqe.getMessage(), sqe );
                 throw new RemoteException( sqe.getMessage() );
                 }
                 finally {
                 try {
                 if ( statement != null ) {
                 statement.close();
                 }
                 if ( conn != null ) {
                 conn.close();
                 }
                 }
                 catch ( SQLException e1 ) {}
                 }
                
                 }
                
                 /**
                 * This standard EJB method physically deletes a Role Set from the database.
                 * <p>
                 * This should <b>NEVER</b> be called.
                 * <p>
                 * Thinks - why not delete the code so it doesn't matter if it is ?
                 * @throws RemoteException
                 * @throws RemoveException
                 */
                 public void ejbRemove() throws RemoteException, RemoveException {
                
                 Connection conn = null;
                 PreparedStatement statement = null;
                 InitialContext context = null;
                 DataSource dataSource;
                
                 String sqlStatement = "DELETE from SecurityRoleSet";
                 int rowCount;
                
                 defaultName = (String) ctx.getPrimaryKey();
                
                 logger.info( "Remove called : " + defaultName );
                
                 try {
                
                 context = new InitialContext();
                 dataSource = (DataSource) context.lookup( DATASTORE_ADDRESS );
                 conn = dataSource.getConnection();
                
                 statement = conn.prepareStatement( sqlStatement );
                 rowCount = statement.executeUpdate();
                 logger.debug( "Removed " + rowCount + " rows" );
                 }
                 catch( NamingException ne ) {
                 logger.error( ne.getMessage(), ne );
                 throw new RemoveException( ne.getMessage() );
                 }
                 catch( SQLException sqe ) {
                 logger.error( sqe.getMessage(), sqe );
                 throw new RemoveException( sqe.getMessage() );
                 }
                 finally {
                 try {
                 if ( statement != null ) {
                 statement.close();
                 }
                 if ( conn != null ) {
                 conn.close();
                 }
                 }
                 catch ( SQLException e1 ) {}
                 }
                
                 }
                
                 /**
                 * Standard EJB method to store a Role Set in persistent data.
                 * <p>
                 * For simplicity, this just calls the ejbRemove and ejbCreate methods.
                 * <p>
                 * This is simpler, and probably quicker, than working through the old and new
                 * sets of Roles, identifying changes, and inserting or deleting individual rows.
                 * @throws RemoteException
                 */
                 public void ejbStore() throws RemoteException {
                
                 try {
                 if ( rolesChanged ) {
                 ejbRemove();
                 ejbCreate( this.defaultName, this.roles );
                 }
                 }
                 catch ( Exception e ) {
                 logger.error( e.getMessage(), e );
                 throw new RemoteException( "Error in store", e );
                 }
                
                 }
                
                 // Standard EJB methods below here.
                
                 public void setEntityContext(EntityContext ctx) throws RemoteException {
                
                 this.ctx = ctx;
                
                 }
                
                 public void unsetEntityContext() throws RemoteException {
                
                 ctx = null;
                
                 }
                
                 public void ejbActivate() throws RemoteException {
                 }
                
                 public void ejbPassivate() throws RemoteException {
                 }
                
                
                }
                


                Answering David's questions :-

                There should be one and only one instance of an Entity Bean with a given identifier within the server. We don't need to configure the server for this - it should be guaranteed. I use an artificial, published identifier to ensure that clients always look up the same Entity Bean.

                If several clients attempt to access the Entity bean, then JBoss locks the Entity Bean on behalf of the first user, and stalls other requests until the first access completes.

                This may become a problem with David's application. The method call that returns data to the client may take some time to serialise the list and to pass the list across the network to the client. Depending on the number of clients, the Entity Bean may be overloaded and become a bottleneck.

                The solution to that problem would be to use a pool of Stateless Session Beans around the Entity Bean. The client accesses the Session Bean. The Session Bean gets a reference to the list from the Entity Bean (which is very quick), releases the Entity Bean, and can then spend as long as it likes passing the data to the client.

                In that approach, you would need to build a new list when reloading data from the database - to avoid overwriting the list that Session Beans mayu be processing.

                James



                • 5. Re: how can I keep a kind of
                  davidchen

                  Thanks a lot James for your help and very clearly sample codes.

                  David