/*=============================================================================*
* 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.ws.resource.impl;
import commonj.timers.Timer;
import commonj.timers.TimerManager;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.addressing.EndpointReference;
import org.apache.ws.addressing.XmlBeansEndpointReference;
import org.apache.ws.resource.InvalidResourceKeyException;
import org.apache.ws.resource.JndiConstants;
import org.apache.ws.resource.PersistenceCallback;
import org.apache.ws.resource.Resource;
import org.apache.ws.resource.ResourceCreationEvent;
import org.apache.ws.resource.ResourceCreationListener;
import org.apache.ws.resource.ResourceDestructionEvent;
import org.apache.ws.resource.ResourceDestructionListener;
import org.apache.ws.resource.ResourceException;
import org.apache.ws.resource.ResourceHome;
import org.apache.ws.resource.ResourceKey;
import org.apache.ws.resource.ResourceUnknownException;
import org.apache.ws.resource.i18n.Keys;
import org.apache.ws.resource.i18n.MessagesImpl;
import org.apache.ws.resource.lifetime.ScheduledResourceTerminationResource;
import org.apache.ws.util.Cache;
import org.apache.ws.util.i18n.Messages;
import org.apache.ws.util.jndi.Initializable;
import org.apache.ws.util.jndi.JNDIUtils;
import org.apache.ws.util.lock.Lock;
import org.apache.ws.util.lock.LockManager;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* LOG-DONE An implementation of the <code>ResourceHome</code> interface. This implementation was designed to work with
* resources that implement the {@link PersistenceCallback PersistenceCallback} interface as well as memory resident
* resources. If the resource class implements the {@link PersistenceCallback PersistenceCallback} interface
* <code>SoftReference</code>s will be used to recycle resource objects. The resource class implementation is
* responsible for saving its state to disk. This implementation will <b>not</b> call {@link PersistenceCallback#store()
* PersistenceCallback.store()}. The resource implementation must have a default constructor. <br><br> Configuration
* options: <ul> <li> sweeperDelay - configures how often the resource sweeper runs in msec. By default the resource
* sweeper runs every minute. For example:
* <pre>
* <parameter>
* <name>sweeperDelay</name>
* <value>60000</value>
* </parameter>
* </pre>
* <li> resourceClass - configures the name of the resource class. For example:
* <pre>
* <parameter>
* <name>resourceClass</name>
* <value>org.globus.wsrf.samples.counter.PersistentCounter</value>
* </parameter>
* </pre>
* <li> resourceKeyType - configures the key type class. By default <code>java.lang.String</code> is used. For example:
* <pre>
* <parameter>
* <name>resourceKeyType</name>
* <value>java.lang.Integer</value>
* </parameter>
* </pre>
* <li> resourceKeyName - configures the key name. For example:
* <pre>
* <parameter>
* <name>resourceKeyName</name>
* <value>{http://counter.com}CounterKey</value>
* </parameter>
* </pre>
* </ul> <br> <b>Note:</b> Must be deployed with <code>org.globus.wsrf.jndi.BeanFactory</code> in JNDI or user must
* first call {@link #init() initialize()} method. Also when overriding the {@link #init() initialize()} method make
* sure to call <code>super.initialize();</code>.
*
* @author Globus, Ian Springer
*/
public abstract class AbstractResourceHome
implements ResourceHome,
Initializable
{
private static final int DEFAULT_SWEEPER_DELAY = 60000;
private static final Log LOG = LogFactory.getLog( AbstractResourceHome.class );
public static final Messages MSG = MessagesImpl.getInstance();
/**
* DOCUMENT_ME
*/
protected Map m_resources;
/**
* DOCUMENT_ME
*/
protected String m_resourceClassName;
private boolean m_resourceIsPersistent;
private String m_resourceKeyClassName;
private String m_resourceKeyName;
private String m_wsdlTargetNamespace;
private String m_serviceClassName;
/**
* DOCUMENT_ME
*/
protected LockManager m_lockManager;
private String m_cacheLocation;
private Cache m_cache;
private long m_sweeperDelay = DEFAULT_SWEEPER_DELAY;
private Sweeper m_sweeper;
private boolean m_initialized;
private Class m_resourceClass;
private Class m_serviceClass;
private Class m_resourceKeyClass;
private List m_creationListeners = new ArrayList();
private List m_destructionListeners = new ArrayList();
/**
* DOCUMENT_ME
*
* @param jndiLocation DOCUMENT_ME
*/
public void setCacheLocation( String jndiLocation )
{
m_cacheLocation = jndiLocation;
}
/**
* DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public String getCacheLocation()
{
return m_cacheLocation;
}
/**
* DOCUMENT_ME
*
* @param resourceClass DOCUMENT_ME
*/
public void setResourceClassName( String resourceClass )
{
m_resourceClassName = resourceClass;
}
/**
* DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public String getResourceClassName()
{
return m_resourceClassName;
}
/**
* DOCUMENT_ME
*
* @param keyClass DOCUMENT_ME
*/
public void setResourceKeyClassName( String keyClass )
{
m_resourceKeyClassName = keyClass;
}
/**
* DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public String getResourceKeyClassName()
{
return m_resourceKeyClassName;
}
/**
* DOCUMENT_ME
*
* @param keyName DOCUMENT_ME
*/
public void setResourceKeyName( String keyName )
{
m_resourceKeyName = keyName;
}
/**
* DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public String getResourceKeyName()
{
return m_resourceKeyName;
}
/**
* DOCUMENT_ME
*
* @param serviceClass DOCUMENT_ME
*/
public void setServiceClass( String serviceClass )
{
m_serviceClassName = serviceClass;
}
/**
* DOCUMENT_ME
*
* @param className DOCUMENT_ME
*/
public void setServiceClassName( String className )
{
m_serviceClassName = className;
}
/**
* DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public String getServiceClassName()
{
return m_serviceClassName;
}
/**
* DOCUMENT_ME
*
* @param delay DOCUMENT_ME
*/
public void setSweeperDelay( long delay )
{
m_sweeperDelay = delay;
}
/**
* DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public long getSweeperDelay()
{
return m_sweeperDelay;
}
/**
* DOCUMENT_ME
*
* @param targetNamespace DOCUMENT_ME
*/
public void setWsdlTargetNamespace( String targetNamespace )
{
m_wsdlTargetNamespace = targetNamespace;
}
/**
* DOCUMENT_ME
*
* @return DOCUMENT_ME
*/
public String getWsdlTargetNamespace()
{
return m_wsdlTargetNamespace;
}
/**
* DOCUMENT_ME
*
* @param key DOCUMENT_ME
*
* @return DOCUMENT_ME
*
* @throws ResourceException if
* @throws InvalidResourceKeyException DOCUMENT_ME
*/
public Resource find( ResourceKey key )
throws ResourceException
{
LOG.debug( MSG.getMessage( Keys.FINDING_RESOURCE_WITH_KEY, String.valueOf( key ) ) );
Resource resource = null;
Lock lock = getLock( key );
try
{
lock.acquire();
}
catch ( InterruptedException ie )
{
throw new ResourceException( ie );
}
try
{
resource = get( key );
if ( m_cache != null )
{
m_cache.update( resource );
}
}
finally
{
lock.release();
}
return resource;
}
private Lock getLock( ResourceKey key )
{
Lock lock;
Object lockKey = getLookupKey( key );
lock = m_lockManager.getLock( lockKey );
return lock;
}
/**
* DOCUMENT_ME
*
* @throws Exception DOCUMENT_ME
*/
public synchronized void init()
throws Exception
{
LOG.debug( MSG.getMessage( Keys.INIT_HOME ) );
if ( m_initialized )
{
return;
}
/*if ( m_resourceKeyClassName == null )
{
m_resourceKeyClassName = String.class;
}
*/
if ( m_resourceClassName == null )
{
throw new Exception( MSG.getMessage( Keys.RESOURCE_CLASSNAME_NULL ) );
}
/* if ( !Resource.class.isAssignableFrom( m_resourceClassName ) )
{
throw new Exception( "invalidResourceType: " + m_resourceClassName.getName() );
}
if ( PersistenceCallback.class.isAssignableFrom( m_resourceClassName ) )
{
m_resourceIsPersistent = true;
}*/
Context initialContext = new InitialContext();
initResourceMap( initialContext );
m_lockManager = new LockManager();
if ( ScheduledResourceTerminationResource.class.isAssignableFrom( getResourceClass() ) )
{
initSweeper( initialContext );
}
m_initialized = true;
}
/**
* DOCUMENT_ME
*
* @param key DOCUMENT_ME
*
* @throws ResourceException DOCUMENT_ME
* @throws InvalidResourceKeyException DOCUMENT_ME
*/
public void remove( ResourceKey key )
throws ResourceException
{
Resource resource = null;
Lock lock = getLock( key );
try
{
lock.acquire();
}
catch ( InterruptedException ie )
{
throw new ResourceException( ie );
}
try
{
resource = get( key );
try
{
resource.destroy();
}
catch ( RuntimeException re )
{
throw new ResourceException( MSG.getMessage( Keys.FAILED_TO_DESTROY_RESOURCE, resource, re ) );
}
m_resources.remove( getLookupKey( key ) );
notifyResourceDeletedListeners( resource.getEndpointReference() );
LOG.debug( MSG.getMessage( Keys.REMOVED_RESOURCE_WITH_KEY, resource.getClass().getName(),
String.valueOf( key ) ) );
if ( m_cache != null )
{
m_cache.remove( resource );
}
}
finally
{
lock.release();
}
}
/**
* DOCUMENT_ME
*
* @param key DOCUMENT_ME
* @param resource DOCUMENT_ME
*/
protected void add( ResourceKey key,
Resource resource )
{
// TODO: why do we need add() in addition to addResource()?
addResource( key, resource );
if ( m_cache != null )
{
m_cache.update( resource );
}
}
/**
* This method uses reflection to create an instance of a Resource which contains an empty constructor. It will not
* work with Resources which do not have an empty constructor.
* <p/>
* Note: the returned Resource will not have an EndpointReference associated with it. The caller should next call
* getEndpointReference and then use a setter on their resource impl.
*
* @param key The resource key for this resource.
*
* @return The resource type which is associated with this home.
*
* @throws ResourceException
* @throws IllegalStateException
*/
protected Resource createInstance( ResourceKey key )
throws ResourceException
{
LOG.debug( MSG.getMessage( Keys.CREATING_INSTANCE_WITH_KEY, String.valueOf( key ) ) );
Resource resource;
try
{
resource = (Resource) getResourceClass().newInstance();
}
catch ( Exception e )
{
throw new ResourceException( e );
}
resource.setID( key != null ? key.getValue() : null );
try
{
LOG.debug( MSG.getMessage( Keys.INIT_RESOURCE_LIFECYCLE_INSTANCE, resource.getClass().getName() ) );
resource.init();
}
catch ( RuntimeException re )
{
throw new ResourceException( MSG.getMessage( Keys.FAILED_TO_INIT_RESOURCE, resource, re ), re );
}
return resource;
}
/**
* DOCUMENT_ME
*
* @param key DOCUMENT_ME
*
* @return DOCUMENT_ME
*
* @throws ResourceException DOCUMENT_ME
*/
protected Resource createNewInstanceAndLoad( ResourceKey key )
throws ResourceException
{
Resource resource = createInstance( key );
LOG.debug( MSG.getMessage( Keys.LOADING_RESOURCE_FROM_PERSISTENCE, String.valueOf( key ) ) );
( (PersistenceCallback) resource ).load( key );
if ( ResourceSweeper.isExpired( resource ) )
{
throw new ResourceUnknownException( getLookupKey( key ), getServicePortName() );
}
return resource;
}
private Class getResourceClass()
throws ResourceException
{
if ( m_resourceClass == null )
{
if ( m_resourceClassName == null )
{
throw new IllegalStateException( MSG.getMessage( Keys.RESOURCE_CLASSNAME_NULL ) );
}
try
{
m_resourceClass = Class.forName( m_resourceClassName );
}
catch ( ClassNotFoundException cnfe )
{
throw new ResourceException( MSG.getMessage( Keys.RESOURCE_CLASS_NOT_FOUND, m_resourceClassName ) );
}
}
return m_resourceClass;
}
private void addResource( ResourceKey key,
Resource resource )
{
LOG.debug( MSG.getMessage( Keys.ADDING_RESOURCE_FOR_KEY, String.valueOf( key ) ) );
m_resources.put( getLookupKey( key ), resource );
// schedule sweeper task if needed
if ( m_sweeper != null )
{
m_sweeper.schedule();
}
notifyResourceCreatedListeners( resource.getEndpointReference() );
}
private Resource get( ResourceKey key )
throws ResourceException
{
LOG.debug( MSG.getMessage( Keys.GET_RESOURCE_FOR_KEY, String.valueOf( key ) ) );
Object lookupKey = getLookupKey( key );
Resource resource = (Resource) m_resources.get( lookupKey );
if ( resource == null )
{
if ( !m_resourceIsPersistent )
{
throw new ResourceUnknownException( lookupKey, getServicePortName() );
}
addResource( key, createNewInstanceAndLoad( key ) );
}
return resource;
}
private Object getLookupKey( ResourceKey key )
{
Object lookupKey = key != null ? key : (Object) this;
return lookupKey;
}
private void initCachePolicy( Context initialContext )
throws NamingException
{
if ( m_cacheLocation != null )
{
m_cache = (Cache) JNDIUtils.lookup( initialContext, m_cacheLocation, Cache.class );
}
}
private void initResourceMap( Context initialContext )
throws NamingException
{
if ( m_resourceIsPersistent )
{
m_resources = new ReferenceMap( ReferenceMap.HARD, ReferenceMap.SOFT, true );
initCachePolicy( initialContext );
}
else
{
m_resources = new HashMap();
}
m_resources = Collections.synchronizedMap( m_resources );
}
private void initSweeper( Context initialContext )
throws NamingException
{
LOG.debug( MSG.getMessage( Keys.TIMER_LOOKUP_WITH_JNDI_NAME, JndiConstants.KEY_NAME_DEFAULT_TIMER ) );
TimerManager timerManager = (TimerManager) initialContext.lookup( JndiConstants.KEY_NAME_DEFAULT_TIMER );
// TimerManager timerManager = new TimerManagerImpl();
m_sweeper = new Sweeper( this, m_resources, timerManager, m_sweeperDelay );
}
/**
* This ResourceSweeper implementation just returns the resources currently stored in the map. The reason is that
* the sweeper doesn't have to reactivate/reload a persistent resource if the resource object was reclaimed. So
* lifetime checks are not done on reclained resources. Lifetime checks have to be done on resource load.
*/
private static class Sweeper
extends ResourceSweeper
{
private TimerManager m_timerManager;
private Timer m_timer;
private long m_delay;
/**
* Creates a new {@link Sweeper} object.
*
* @param home DOCUMENT_ME
* @param resources DOCUMENT_ME
* @param timerManager DOCUMENT_ME
* @param delay DOCUMENT_ME
*/
public Sweeper( ResourceHome home,
Map resources,
TimerManager timerManager,
long delay )
{
super( home, resources );
m_timerManager = timerManager;
m_delay = delay;
}
/**
* DOCUMENT_ME
*
* @param timer DOCUMENT_ME
*/
public void timerExpired( Timer timer )
{
super.timerExpired( timer );
cancel();
if ( !m_resources.isEmpty() )
{
schedule();
}
}
/**
* DOCUMENT_ME
*
* @param key DOCUMENT_ME
*
* @return DOCUMENT_ME
*
* @throws ResourceException DOCUMENT_ME
*/
protected Resource getResource( ResourceKey key )
throws ResourceException
{
LOG.debug( MSG.getMessage( Keys.GET_RESOURCE_FOR_KEY, key.getValue() ) );
return (Resource) m_resources.get( key );
}
/**
* Schedules this resource sweeper.
*/
synchronized void schedule()
{
if ( m_timer == null )
{
LOG.debug( MSG.getMessage( Keys.SCHEDULE_RESOURCE_SWEEPER ) );
m_timer = m_timerManager.schedule( this, m_delay );
}
}
/**
* Cancels this resource sweeper.
*/
private synchronized void cancel()
{
if ( m_timer != null )
{
LOG.debug( MSG.getMessage( Keys.CANCEL_RESOURCE_SWEEPER ) );
m_timer = null;
}
}
}
/**
* Adds a listener for ResourceCreationEvents
*
* @param listener
*/
public void addResourceCreationListener( ResourceCreationListener listener )
{
m_creationListeners.add( listener );
}
/**
* Adds a listener for ResourceDestructionEvents
*
* @param listener
*/
public void addResourceDestructionListener( ResourceDestructionListener listener )
{
m_destructionListeners.add( listener );
}
/**
* Removes a listener for ResourceCreationEvents
*
* @param listener
*
* @return true if the listener was removed, else false
*/
public boolean removeResourceCreationListener( ResourceCreationListener listener )
{
return m_creationListeners.remove( listener );
}
/**
* Removes a listener for ResourceDestructionEvents
*
* @param listener
*
* @return true if the listener was removed, else false
*/
public boolean removeResourceDestructionListener( ResourceDestructionListener listener )
{
return m_destructionListeners.remove( listener );
}
/**
* This method is used to notify listeners a resource has been created.
*
* @param epr The EndpointReference for the Resource which was created
*/
private void notifyResourceCreatedListeners( EndpointReference epr )
{
for ( int i = 0; i < m_creationListeners.size(); i++ )
{
ResourceCreationListener resourceCreationListener = (ResourceCreationListener) m_creationListeners.get( i );
resourceCreationListener.creationOccurred( new ResourceCreationEvent( epr ) );
}
}
/**
* This method is used to notify listeners a resource has been deleted.
*
* @param epr The EndpointReference for the Resource which was created
*/
private void notifyResourceDeletedListeners( EndpointReference epr )
{
for ( int i = 0; i < m_destructionListeners.size(); i++ )
{
ResourceDestructionListener resourceDestructionListener = (ResourceDestructionListener) m_destructionListeners.get(
i );
resourceDestructionListener.destructionOccurred( new ResourceDestructionEvent( epr ) );
}
}
/**
* Returns the EndpointReference associated with this Resource. Only the required fields will be filled in (i.e.
* Address) AND the ReferenceProperty for the ResourceKey (if not a singleton)
* <p/>
* If the resourceKeyis not equal to null ( not a singleton ), The reference properties will contain the key.
*
* @param endpointAddress The endpoint url for the service
* @param key The resourceKey for the Resource or null if singleton
* @param wsAddressingURI The WS-Addressing URI
*
* @return The Resource's EndpointReference
*/
public EndpointReference getEndpointReference( String endpointAddress, ResourceKey key, String wsAddressingURI )
{
XmlBeansEndpointReference xmlBeansEndpointReference = new XmlBeansEndpointReference( endpointAddress,
wsAddressingURI );
xmlBeansEndpointReference.setResourceKey( key );
xmlBeansEndpointReference.setPortTypeQName( getPortType() );
xmlBeansEndpointReference.setServicePortName( getServicePortName() );
xmlBeansEndpointReference.setServiceQName( getServiceName() );
return xmlBeansEndpointReference;
}
public abstract QName getServiceName();
public abstract QName getPortType();
public abstract String getServicePortName();
public abstract QName getResourceKeyNameQName();
}