ejb@glassfish.java.net

Singleton instance

From: Daniel Cavalcanti <dhcavalcanti_at_gmail.com>
Date: Fri, 4 Aug 2006 12:34:45 -0400

Hello,

I have a stateless, session bean. All client code uses this bean to place
some entries in a history object (just a POJO to store some history
information).
I know that a statefull, session bean wouldn't solve the problem because
users will access this across sessions...
I implement this history object as a singleton, but every time a new bean is
created, it seems to get a new history object:
Does someone know what going on? I can attach the project if necessary.

thanks,
Daniel.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

package com.localmatters.flexitext.ejb;

import com.localmatters.flexitext.support.HistoryTable;
import com.localmatters.flexitext.support.UserHistory;
import java.sql.Date;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

/**
 * The history WS-EJB. The actual history is managed by
 * {_at_link HistoryTable HistoryTable}, so all operations are delegated to it.
 *
 * @author Daniel Cavalcanti
 */
@Stateless()
@WebService()
public class HistoryBean implements HistoryRemote, HistoryLocal {

    //
**********************************************************************
    // Instance variables
    //
**********************************************************************

    /**
     * All users' history.
     */
    private HistoryTable historyTable;

    //
**********************************************************************
    // Constructors
    //
**********************************************************************

    /**
     * Creates a new instance of HistoryBean.
     */
    public HistoryBean() {
    }

    //
**********************************************************************
    // Bean life-cycle methods
    //
**********************************************************************

    /**
     * Gets reference to history table resource.
     */
    @PostConstruct()
    public void postConstruct() {
        historyTable = historyTable.getInstance();
    }

    /**
     * Periodic clean up method
     */
    @Timeout()
    public void clean(Timer timer) {
        // TODO clean users temporary history
        System.out.println("current time: " + new Date(
System.currentTimeMillis()));
    }

    //
**********************************************************************
    // Bean methods
    //
**********************************************************************

    @WebMethod()
    public Set<String> users() {
        return historyTable.users();
    }

    @WebMethod()
    public void clear() {
        historyTable.clear();
    }

    @WebMethod()
    public void clearOld(@WebParam() int age) {
        historyTable.clear(age);
    }

    @WebMethod()
    public void create(@WebParam() String user)
    throws NullPointerException {
        historyTable.create(user);
    }

    @WebMethod()
    public UserHistory get(@WebParam() String user)
    throws NullPointerException {
        return historyTable.get(user);
    }

    @WebMethod()
    public UserHistory remove(@WebParam() String user)
    throws NullPointerException {
        return historyTable.remove(user);
    }

}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

package com.localmatters.flexitext.support;

import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * All users' history. This class is a thread-safe singleton.
 *
 * @author Daniel Cavalcanti
 */
public class HistoryTable implements Serializable {

    //
**********************************************************************
    // Singleton variable
    //
**********************************************************************

    /**
     * The users' history singleton instance.
     */
    private static HistoryTable singleton;

    //
**********************************************************************
    // Instance variables
    //
**********************************************************************

    /**
     * The users' history.
     */
    private Map<String, UserHistory> history;

    //
**********************************************************************
    // Constructor
    //
**********************************************************************

    /**
     * Creates a new instance of HistoryTable.
     */
    private HistoryTable() {
        history = Collections.synchronizedMap(new TreeMap<String,
UserHistory>());
    }

    /**
     * Returns the the users' history instance.
     * HistoryTable factory method.
     *
     * @return The the users' history instance.
     */
    public static HistoryTable getInstance() {
        synchronized (HistoryTable.class) {
            if (singleton == null)
                singleton = new HistoryTable();
            return singleton;
        }
    }

    //
**********************************************************************
    // History table methods
    //
**********************************************************************

    /**
     * Returns a copy of the users set.
     *
     * @return A copy of the users set.
     */
    public Set<String> users() {
        return new TreeSet(history.keySet());
    }

    /**
     * Clears the history table. This methods removes all user to
user-history
     * entries.
     */
    public void clear() {

        synchronized (history) {
            for (UserHistory userHistory : history.values())
                userHistory.clear();

            history.clear();
        }

    }

    /**
     * Clears the history older than age milliseconds. The user to
user-history
     * entry is maintained. Only the actual history entries that are old are
     * removed.
     *
     * @param age The age threshold in milliseconds.
     */
    public void clear(int age) {

        long threshold = System.currentTimeMillis() - age;

        synchronized (history) {
            for (UserHistory userHistory : history.values()) {
                userHistory.clear(threshold);
            }
        }

    }

    /**
     * Create a history entry for a user.
     *
     * @param user The user.
     * @throws java.lang.NullPointerException see {_at_link
UserHistory#UserHistory() UserHistory}.
     * @see UserHistory#UserHistory()
     */
    public void create(String user)
    throws NullPointerException {
        history.put(user, new UserHistory());
    }

    /**
     * Returns the user's history. If user does not have a history,
compulsively
     * create one.
     *
     * @param user The user.
     * @throws java.lang.NullPointerException If user is null.
     * @return The user history.
     */
    public UserHistory get(String user)
    throws NullPointerException {

        // attempt to get user's history
        UserHistory entry = history.get(user);

        // user does not have history -- create one
        if (entry == null)
            create(user);

        // return user's history
        return entry;

    }

    /**
     * Removes a uer's history.
     *
     * @param user The user.
     * @throws java.lang.NullPointerException If user is null.
     * @return The user's history if it exists, null otherwise.
     */
    public UserHistory remove(String user)
    throws NullPointerException {
        return history.remove(user);
    }

}