gov.nih.nci.cagrid.introduce.servicetools.ReflectionResource.java Source code

Java tutorial

Introduction

Here is the source code for gov.nih.nci.cagrid.introduce.servicetools.ReflectionResource.java

Source

package gov.nih.nci.cagrid.introduce.servicetools;

/*
 * Portions of this file Copyright 1999-2005 University of Chicago Portions of
 * this file Copyright 1999-2005 The University of Southern California. This
 * file or a portion of this file is licensed under the terms of the Globus
 * Toolkit Public License, found at
 * http://www.globus.org/toolkit/download/license.html. If you redistribute this
 * file, with or without modifications, you must include this notice in the
 * file.
 */

import java.lang.reflect.Method;
import java.util.Calendar;

import javax.xml.namespace.QName;

import org.apache.axis.description.ElementDesc;
import org.apache.axis.description.FieldDesc;
import org.apache.axis.description.TypeDesc;
import org.apache.axis.utils.cache.MethodCache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.globus.util.I18n;
import org.globus.wsrf.Resource;
import org.globus.wsrf.ResourceException;
import org.globus.wsrf.ResourceIdentifier;
import org.globus.wsrf.ResourceLifetime;
import org.globus.wsrf.ResourceProperties;
import org.globus.wsrf.ResourceProperty;
import org.globus.wsrf.ResourcePropertyMetaData;
import org.globus.wsrf.ResourcePropertySet;
import org.globus.wsrf.WSRFConstants;
import org.globus.wsrf.impl.ReflectionResourceProperty;
import org.globus.wsrf.impl.ResourcePropertyTopic;
import org.globus.wsrf.impl.SimpleResourcePropertyMetaData;
import org.globus.wsrf.impl.SimpleResourcePropertySet;
import org.globus.wsrf.impl.SimpleTopicList;
import org.globus.wsrf.utils.Resources;

/**
 * An implementation of {@link ResourceProperties ResourceProperties} and
 * {@link ResourceIdentifier ResourceIdentifier} which frees the developer from
 * having to write a getter and possibly a setter to implement every resource
 * property (RP).
 * <p>
 * This class uses an Axis-generated JavaBean class based on the Schema
 * definition of the RP set used by the service port type in WSDL. Since the
 * generated class contain a JavaBean property as well as QName and Schema type
 * metadata (such as occurence cardinality) for each RP, it is possible to
 * automatically create
 * {@link org.globus.wsrf.impl.ReflectionResourceProperty ReflectionResourceProperty}
 * objects to provide an implementation of the RPs. This is what
 * ReflectionResource does.
 * <p>
 * Advantages of using this class:
 * <ul>
 * <li>Less errors due to forgetting to implement a resource property.</li>
 * <li>Less code to write, so implementing a resource is faster.</li>
 * <li>Easier maintenance when the WSDL/Schema definition changes.</li>
 * <li>Less errors due to namespace mismatch between WSDL and code.</li>
 * <li>Less errors thanks to the handling of a few special cases.</li>
 * </ul>
 * <p>
 * Usage:
 * <p>
 * The specialized resource home class should do the following when creating a
 * resource:
 * <ol>
 * <li>create an object corresponding to the Schema type or element of the
 * resource property set.</li>
 * <li>initialize the object by calling its setters.</li>
 * <li>construct the specialized ReflectionResource-based resource object.</li>
 * <li>call initialize on the resource, passing it the implementation bean
 * created in 1).</li>
 * </ol>
 * <p>
 * Reuse approach:
 * <ul>
 * <li>If no specialized behavior is required from the resource implementation,
 * there is no need for a specialized class. The Home class can just create
 * objects of type ReflectionResource.</li>
 * <li>If specialized behavior is required from the resource implementation, it
 * is easy to reuse this class (via inheritance or delegation) and thus achieve
 * a clean separation of the specialized code from the RP setup code.</li>
 * </ul>
 * <p>
 * Typical goals when extending the class:
 * <ul>
 * <li>to customize the resource property creation behavior for special cases
 * not handled by this class.</li>
 * <li>to add domain-specific behavior, public or not, to the Resource objects.</li>
 * </ul>
 * Extending ReflectionResource:
 * <p>
 * Classes directly or indirectly extending ReflectionResource property must be
 * allow instanciation without parameter. If a parameterless constructor is
 * present it must not do any initialization whatsoever. The reason is to
 * decouple object creation from initialization so as to match the requirements
 * of Resource Home classes such as ResourceHomeImpl, which, when materializing
 * a previously stored persistent resource, creates the resource object first
 * using a parameterless constructor, and then initializes the object. Code that
 * creates the key of the resource automatically (for instance a UUID) - in
 * domain-specific cases where it makes sense - should not be put inside a
 * parameterless constructor but in an a overriding version of the
 * {@link #initialize(Object, QName, Object) initialize()} method, or inside the
 * Resource Home class. In this way a Persistent Resource object can be brought
 * back from a passivated state and be given the ID it used to have in activated
 * mode, as opposed to a newly generated ID.
 * <p>
 * Known limitations:
 * <ul>
 * <li>does not set the 'nillable' JavaBean property on the ResourceProperty
 * objects (Axis does not generate this metadatum).</li>
 * </ul>
 * <p>
 * This class does not provide a getter to the implementation JavaBean, because
 * it is good practice to access the value of a resource property by obtaining
 * the {@link ResourceProperty ResourceProperty} object first, as opposed to
 * calling the getters and setters of its implementation Bean.
 * <p>
 * In fact:
 * <ul>
 * <li>some resource properties are implemented using other objects (for
 * instance wsrl:CurrentTime is implemented via
 * {@link #getCurrentTime() getCurrentTime}, and the resource properties from
 * wsnt:NotificationProducer are implemented directly or indirectly by a
 * {@link SimpleTopicList SimpleTopicList}). </li>
 * <li>the {@link ResourceProperty ResourceProperty} object generic accessors
 * may do extra processing such as firing change notification events (for
 * instance if it is a {@link ResourcePropertyTopic ResourcePropertyTopic}).
 * </li>
 * </ul>
 */
public class ReflectionResource implements Resource, ResourceProperties, ResourceIdentifier, ResourceLifetime {

    private static Log logger = LogFactory.getLog(ReflectionResource.class.getName());

    private static I18n i18n = I18n.getI18n(Resources.class.getName());

    private static final Class[] SET_TERM_TIME_PARAM = new Class[] { Calendar.class };

    private static MethodCache methodCache = MethodCache.getInstance();

    private ResourcePropertySet resourcePropertySet;

    private Object ID;

    private Object resourceBean;

    private Method setTerminationTimeMethod;
    private Method getTerminationTimeMethod;

    /**
     * This should be called before any other resource property addition is made
     * as it will create resource properties object based on the resource
     * properties defined in the schema. It is possible to override the
     * implementation of certain resources properties by adding a new
     * ResourceProperty object to the resource property set after this method
     * has been called. This method should be called in the constructor of the
     * concrete resource class, or shortly after the parameterless constructor
     * has been called.
     * 
     * @param resourceBean
     *            Object An instance of the Axis-generated class corresponding
     *            to the resource property set global Schema type or element
     *            (with local type) used by the port type definition. That class
     *            has JavaBean properties matching all the resource properties
     *            of the port type. The ReflectionResource constructor creates
     *            the ResourceProperty objects based on the resource bean, which
     *            JavaBean properties SHOULD thus have been initialized
     *            beforehand. This object provides an easy way to initialize the
     *            values of the resource properties.
     *            <p>
     * @param resourceElementQName
     *            QName The QName of the resource properties element used by the
     *            port type. This corresponds to the value of the
     *            'wsrp:ResourceProperties' attribute. If the type of this
     *            element is anonymous (i.e. inlined in the element declaration)
     *            then this parameter is optional. If it is non-null though, its
     *            value takes precedence over Axis-generated metadata.
     *            <p>
     * @param key
     *            Object The resource key object for this resource. This is used
     *            to set the ID property of this object as a ResourceIdentifier,
     *            if the resource class doesn't create it automatically.
     *            <p>
     */
    public void initialize(Object resourceBean, QName resourceElementQName, Object key) throws ResourceException {
        if (key == null) {
            throw new IllegalArgumentException(i18n.getMessage("nullArgument", "key"));
        }
        this.ID = key;
        if (logger.isDebugEnabled()) {
            logger.debug("class of key passed to resource:" + key.getClass().getName());
            logger.debug("class of implementation bean passed to resource:" + resourceBean.getClass().getName());
        }
        initializeResourceProperties(resourceBean, resourceElementQName);
    }

    private void initializeResourceProperties(Object resourceBean, QName resourceElementQName)
            throws ResourceException {

        if (resourceBean == null) {
            throw new IllegalArgumentException(i18n.getMessage("nullArgument", "resourceBean"));
        }

        this.resourceBean = resourceBean;

        Class resourceBeanClazz = resourceBean.getClass();

        TypeDesc typeDesc = TypeDesc.getTypeDescForClass(resourceBeanClazz);
        if (typeDesc == null) {
            throw new ResourceException(i18n.getMessage("noTypeDesc", resourceBeanClazz));
        }

        // is type anonymous?
        String localTypeName = typeDesc.getXmlType().getLocalPart();
        if (localTypeName.startsWith(">")) {
            String globalElementName = localTypeName.substring(1); // this
            // is Axis-dependent of course...

            if (resourceElementQName == null) {
                resourceElementQName = new QName(typeDesc.getXmlType().getNamespaceURI(), globalElementName);
            }

        }
        if (logger.isDebugEnabled()) {
            logger.debug("QName of global element for resource properties is:" + resourceElementQName);
        }

        // get the resource properties
        FieldDesc[] fields = typeDesc.getFields();

        // Create resource properties per se
        this.resourcePropertySet = new SimpleResourcePropertySet(resourceElementQName);

        ResourceProperty prop = null;

        if (fields != null) {
            try {
                for (int i = 0; i < fields.length; i++) {
                    QName rpQName = fields[i].getXmlName();
                    if (logger.isDebugEnabled()) {
                        logger.debug("creating new resource property \"" + rpQName.toString() + "\"");
                    }
                    if (!(fields[i] instanceof ElementDesc)) {
                        String errorMessage = i18n.getMessage("rpNotElement",
                                fields[i].getXmlType().getLocalPart());
                        throw new ResourceException(errorMessage);
                    }
                    ElementDesc elementDesc = (ElementDesc) fields[i];
                    // assume nillable always false since no metadata for that
                    SimpleResourcePropertyMetaData metaData = new SimpleResourcePropertyMetaData(rpQName,
                            elementDesc.getMinOccurs(), elementDesc.getMaxOccurs(), false, Object.class, false);
                    prop = createNewResourceProperty(metaData, this.resourceBean);
                    this.resourcePropertySet.add(prop);
                }
            } catch (ResourceException e) {
                logger.error("", e);
                throw e;
            } catch (Exception e) {
                logger.error("", e);
                throw new ResourceException(i18n.getMessage("resourceInitError"), e);
            }
        }

    }

    /**
     * Warning: this is not a callback (but maybe it should be).
     * 
     * @param rpQName
     *            QName
     * @param resourceBean
     *            Object
     * @throws Exception
     * @return ResourceProperty
     */
    protected ResourceProperty createNewResourceProperty(QName rpQName, Object resourceBean) throws Exception {
        return createNewResourceProperty(new SimpleResourcePropertyMetaData(rpQName), resourceBean);
    }

    /**
     * Override this callback method to specialize the implementation of the
     * resource property value accessors on a per resource property basis. For
     * instance, in the overriden version, do special processing if the QName of
     * the property matches a special QName, or just call this base
     * implementation otherwise.
     * <p>
     * The default behavior is to create a new
     * {@link ReflectionResourceProperty ReflectionResourceProperty} constructed
     * with the QName of the resource property and the resource implementation
     * Bean used to construct this ReflectionResource object.
     * <p>
     * This function handles a few special cases:
     * <ul>
     * <li>If rpQName equals WSRFConstants.CURRENT_TIME, the resource property
     * MUST use a dynamic implementation of getCurrentTime(), since time never
     * stops changing. Therefore the resource bean callback used is not the
     * initial resource Bean but the ReflectionResource object, which bears such
     * an implementation of that function.</li>
     * <li>If rpQName equals WSRFConstants.TERMINATION_TIME, the resource
     * property created is set be nillable.</li>
     * </ul>
     * 
     * @param metaData
     *            Meta data associated with the resource property object
     * @param resourceBean
     *            same as passed to constructor or initialize
     */
    protected ResourceProperty createNewResourceProperty(ResourcePropertyMetaData metaData, Object resourceBean)
            throws Exception {
        Object resourceBeanCallback = resourceBean; // default

        QName rpQName = metaData.getName();

        ReflectionResourceProperty prop = null;

        if (rpQName.equals(WSRFConstants.TERMINATION_TIME)) {
            prop = new ReflectionResourceProperty(SimpleResourcePropertyMetaData.TERMINATION_TIME,
                    resourceBeanCallback);

            this.setTerminationTimeMethod = methodCache.getMethod(resourceBeanCallback.getClass(),
                    "setTerminationTime", SET_TERM_TIME_PARAM);

            this.getTerminationTimeMethod = methodCache.getMethod(resourceBeanCallback.getClass(),
                    "getTerminationTime", null);

        } else if (rpQName.equals(WSRFConstants.CURRENT_TIME)) {
            prop = new ReflectionResourceProperty(SimpleResourcePropertyMetaData.CURRENT_TIME, this);
        } else {
            prop = new ReflectionResourceProperty(metaData, resourceBeanCallback);
        }

        return prop;
    }

    public void setTerminationTime(Calendar time) {
        if (this.setTerminationTimeMethod != null) {
            try {
                this.setTerminationTimeMethod.invoke(this.getResourceBean(), new Object[] { time });
            } catch (Exception e) {
                logger.error("", e);
                throw new RuntimeException(e.getMessage());
            }
        }
    }

    public Calendar getTerminationTime() {
        if (this.getTerminationTimeMethod != null) {
            try {
                return (Calendar) this.getTerminationTimeMethod.invoke(this.getResourceBean(), null);
            } catch (Exception e) {
                logger.error("", e);
                throw new RuntimeException(e.getMessage());
            }
        }
        return null;
    }

    public Calendar getCurrentTime() {
        return Calendar.getInstance();
    }

    /**
     * See ResourceProperties.
     * 
     * @return ResourcePropertySet
     */
    public ResourcePropertySet getResourcePropertySet() {
        return this.resourcePropertySet;
    }

    /**
     * See ResourceIdentifier.
     * 
     * @return Object The key of the Resource. This is useful for instance when
     *         creating an endpoint reference that must be qualified with this
     *         resource.
     */
    public Object getID() {
        return this.ID;
    }

    /**
     * @return Object the Axis-generated Java Bean used as the main
     *         implementation of the resource state.
     */
    public Object getResourceBean() {
        return this.resourceBean;
    }

}