blob: 31964bee10a7474b32d96007862cdace27e96d1c [file] [log] [blame]
// Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.vlib.ejb.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.tapestry.contrib.ejb.XEJBException;
/**
* Implementation of the {@link org.apache.tapestry.vlib.ejb.IKeyAllocator}
* stateless session bean.
*
* <p>We're cheating a little; they KeyAllocator does have
* state, it just doesn't get persisted ever. Since the
* operation on it is atomic ("gimme a key") it doesn't
* need to have conversational state with its clients.
*
* <p>The KeyAllocator records in the database the "next key
* to allocate". When it needs a key, it allocates a block
* of keys (by advancing the next key by a some number).
*
* <p>If the KeyAllocator instance is purged from the pool,
* then some number of keys that it has allocated will
* be lost. Big deal.
*
* @version $Id$
* @author Howard Lewis Ship
*
**/
public class KeyAllocatorBean implements SessionBean
{
private static final String PROPERTY_NAME = "next-key";
private SessionContext context;
/**
* List of Integer instances; these are keys
* acquired from the database.
*
**/
private LinkedList keys;
/**
* Number of keys to allocate from the database
* at a time. Set from the ENC property "blockSize".
*
**/
private int blockSize = 0;
/**
* Data source, retrieved from the ENC property
* "jdbc/dataSource".
*
**/
private DataSource dataSource;
/**
* Activates the bean. Gets the block size
* and DataSource from the environment.
*
**/
public void ejbCreate()
{
Context initial;
Context environment;
Integer blockSizeProperty;
try
{
initial = new InitialContext();
environment = (Context) initial.lookup("java:comp/env");
}
catch (NamingException ex)
{
throw new XEJBException("Could not lookup environment.", ex);
}
try
{
blockSizeProperty = (Integer) environment.lookup("blockSize");
}
catch (NamingException ex)
{
throw new XEJBException("Could not lookup blockSize property.", ex);
}
blockSize = blockSizeProperty.intValue();
try
{
dataSource = (DataSource) environment.lookup("jdbc/dataSource");
}
catch (NamingException ex)
{
throw new XEJBException("Could not lookup data source.", ex);
}
if (keys == null)
keys = new LinkedList();
}
/**
* Does nothing, not invoked in stateless session beans.
**/
public void ejbPassivate()
{
}
public void setSessionContext(SessionContext value)
{
context = value;
}
/**
* Does nothing, not invoked in stateless session beans.
*
**/
public void ejbActivate()
{
}
/**
* Does nothing. This is invoked when the bean moves from
* the method ready pool to the "does not exist" state.
* The EJB container will lost its reference to the
* bean, and the garbage collector will take it
* (including any keys it has cached from the database).
*
**/
public void ejbRemove()
{
// Does nothing.
}
/**
* Allocates a single key, going to the database
* only if it has no keys in its internal cache.
*
**/
public Integer allocateKey()
{
if (keys.isEmpty())
allocateBlock(1);
return (Integer) keys.removeFirst();
}
/**
* Allocates a block of keys, going to the database
* if there are insufficient keys in its internal
* cache.
*
**/
public Integer[] allocateKeys(int count)
{
Integer[] result;
int i;
if (keys.size() < count)
allocateBlock(count);
result = new Integer[count];
for (i = 0; i < count; i++)
{
result[i] = (Integer) keys.removeFirst();
}
return result;
}
/**
* Allocates a block of keys from the database.
* Allocates count keys, or the configured block size,
* whichever is greater.
*
* <p>It is assumed that this operation takes place
* within a transaction.
*
**/
protected void allocateBlock(int count)
{
Connection connection = null;
PreparedStatement statement = null;
ResultSet set = null;
int nextKey;
int allocationCount;
int i;
allocationCount = Math.max(count, blockSize);
try
{
connection = getConnection();
statement = connection.prepareStatement("select PROP_VALUE from PROP where NAME = ?");
statement.setString(1, PROPERTY_NAME);
set = statement.executeQuery();
// Advance to the first row.
set.next();
nextKey = set.getInt(1);
set.close();
set = null;
statement.close();
statement = null;
// Now, take those keys and advance nextKey
for (i = 0; i < allocationCount; i++)
keys.add(new Integer(nextKey++));
// Update nextKey back to the database.
statement =
connection.prepareStatement("update PROP\n" + "set PROP_VALUE = ?\n" + "where NAME = ?");
statement.setInt(1, nextKey);
statement.setString(2, PROPERTY_NAME);
statement.executeUpdate();
}
catch (SQLException ex)
{
ex.printStackTrace();
throw new XEJBException("Unable to allocate keys from the database.", ex);
}
finally
{
if (set != null)
{
try
{
set.close();
}
catch (SQLException ex)
{
}
}
if (statement != null)
{
try
{
statement.close();
}
catch (SQLException ex)
{
}
}
if (connection != null)
{
try
{
connection.close();
}
catch (SQLException ex)
{
}
}
}
}
/**
* Gets a database connection from the pool.
*
* @throws EJBException if a {@link SQLException}
* is thrown.
*
**/
protected Connection getConnection()
{
try
{
return dataSource.getConnection();
}
catch (SQLException ex)
{
throw new XEJBException("Unable to get database connection from pool.", ex);
}
}
}