Open Source Repository

Home /open-symphony/oscore-2.2.6 | Repository Home



com/opensymphony/module/sequence/SequenceGeneratorEJB.java
/*
 * Copyright (c) 2002-2003 by OpenSymphony
 * All rights reserved.
 */
package com.opensymphony.module.sequence;

import java.util.HashMap;

/* ====================================================================
 * The OpenSymphony Software License, Version 1.1
 *
 * (this license is derived and fully compatible with the Apache Software
 * License - see http://www.apache.org/LICENSE.txt)
 *
 * Copyright (c) 2001 The OpenSymphony Group. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        OpenSymphony Group (http://www.opensymphony.com/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "OpenSymphony" and "The OpenSymphony Group"
 *    must not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact [email protected] .
 *
 * 5. Products derived from this software may not be called "OpenSymphony"
 *    or "OSCore", nor may "OpenSymphony" or "OSCore" appear in their
 *    name, without prior written permission of the OpenSymphony Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 */
import java.util.Map;

import javax.ejb.*;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;


/**
 * SequenceGenerator Session EJB implementation.
 *
 <p>Utilizes the HIGH/LOW OID strategy as described at
 * <a href="http://www.ambysoft.com/mappingObjects.html">
 *  http://www.ambysoft.com/mappingObjects.html </a>.</p>
 *
 * @ejb.bean
 *  type="Stateless"
 *  name="SequenceGenerator"
 *  view-type="remote"
 *  transaction-type="Container"
 *
 * @ejb.ejb-ref
 *  ejb-name="Sequence"
 *  view-type="local"
 *
 * @ejb.transaction type="Supports"
 * @ejb.permission unchecked="true"
 *
 @author <a href="mailto:[email protected]">Joe Walnes</a>
 @author <a href="mailto:[email protected]">Hani Suleiman</a>
 @version $Revision: 130 $
 */
public class SequenceGeneratorEJB implements SessionBean {
    //~ Instance fields ////////////////////////////////////////////////////////

    /**
     * Map of sequenceNames to appropriate MemorySequence's.
     */
    private Map sequenceStore;

    /**
     * Home for Sequence Entity
     */
    private SequenceLocalHome sequenceHome;
    private SessionContext context;

    /**
     * Increment size (size of HIGH block).
     */
    private int increment;

    /**
     * Number of times to attempt to retry.
     */
    private int retry;

    /**
     * Time (in milliseconds) to pause between retries.
     */
    private int retryPause;

    //~ Methods ////////////////////////////////////////////////////////////////

    /**
     * @ejb.interface-method
     * @ejb.transaction type="Required"
     */
    public long getCount(String sequenceName) {
        try {
            // Allow a sequence for null.
            if (sequenceName == null) {
                sequenceName = "";
            }

            // Get appropriate memorySequence
            MemorySequence memorySequence = getMemorySequence(sequenceName);

            // If LOW count has cycled through increment, get next HIGH count
            if ((memorySequence.last % increment== 0) {
                getNextHighCount(memorySequence);
            }

            // Increment LOW count and return value.
            return memorySequence.last++;
        catch (Exception e) {
            // Kick up a fuss about anything that went wrong.
            throw new EJBException(e);
        }
    }

    /**
     * Session context set. Lookup Home interface for Sequence Entity
     */
    public void setSessionContext(SessionContext context) {
        this.context = context;

        try {
            InitialContext ctx = new InitialContext();
            sequenceHome = (SequenceLocalHomePortableRemoteObject.narrow(ctx.lookup(SequenceLocalHome.COMP_NAME), SequenceLocalHome.class);
        catch (NamingException e) {
            throw new EJBException(e);
        }
    }

    public void ejbActivate() {
    }

    /**
     * Session created. Initialize.
     * @ejb.create-method
     */
    public void ejbCreate() throws CreateException {
        // Get values from env
        increment = getIntEnv("increment"10);
        retry = getIntEnv("retry"5);
        retryPause = getIntEnv("retryPause"100);

        // Initialise sequenceStore
        sequenceStore = new HashMap();
    }

    public void ejbPassivate() {
    }

    public void ejbRemove() {
    }

    /**
     * Get an env-entry value that is an int.
     *
     @param envName Name of env-entry to retrieve
     @param defaultValue Value it should have if it is not found.
     */
    private int getIntEnv(String envName, int defaultValue) {
        try {
            return ((IntegerPortableRemoteObject.narrow(new InitialContext().lookup(envName), Integer.class)).intValue();
        catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Get the MemorySequence for a given sequenceName. Try to get from
     * sequenceStore first - if that fails, find or create appropriate
     * entity and add to sequenceStore.
     *
     @param sequenceName Name of the sequence to lookup
     @exception javax.ejb.CreateException thrown if cannot create new sequence
     */
    private MemorySequence getMemorySequence(String sequenceNamethrows CreateException {
        // Get the MemorySequence for the given sequenceName from the sequenceStore
        MemorySequence memorySequence = (MemorySequencesequenceStore.get(sequenceName);

        // If the MemorySequence was not already in the sequenceStore
        if (memorySequence == null) {
            // Create a new MemorySequence
            memorySequence = new MemorySequence();

            try {
                // Lookup 'real' sequence and assign to memorySequence
                memorySequence.sequence = sequenceHome.findByPrimaryKey(sequenceName);
            catch (FinderException e) {
                // ... if 'real' sequence not found, create it.
                memorySequence.sequence = sequenceHome.create(sequenceName);
            }

            // Put memorySequence into sequenceStore
            sequenceStore.put(sequenceName, memorySequence);
        }

        return memorySequence;
    }

    /**
     * Increment the <code>last</code> field of a memoryStore to the next
     * HIGH count for the actual <code>Sequence</code>. Seeing as this is
     * quite an important operation and other Entities are dependant on it
     * working, if there is a failure it will make a few retries.
     *
     @param memorySequence MemorySequence to obtain next HIGH count for
     */
    private void getNextHighCount(MemorySequence memorySequence) {
        // Start looping and keep track of how many retryAttempts.
        for (int retryAttempt = 0true; retryAttempt++) {
            try {
                // Attempt to get next HIGH count for memorySequence
                memorySequence.last = memorySequence.sequence.getCount(increment);

                // Success! - break out of loop
                break;
            catch (Exception tre) {
                // Unsuccessful - worry not...
                if (retryAttempt < retry) {
                    // If we have still got another retry, pause then continue loop
                    try {
                        Thread.sleep(retryPause);
                    catch (InterruptedException ie) {
                    }

                    continue;
                else {
                    // If we're out of retries, give up and throw EJBException
                    throw new EJBException(tre);
                }
            }
        }
    }

    //~ Inner Classes //////////////////////////////////////////////////////////

    /**
     * This classrepresents an in-memory sequence. (The LOW bit of
     * a HIGH/LOW counter). It has a reference to the HIGH sequence
     * (that stored in the database), and a LOW in-memory count.
     */
    private class MemorySequence {
        SequenceLocal sequence;
        long last;
    }
}