Package org.apache.commons.betwixt.io

Source Code of org.apache.commons.betwixt.io.BeanCreateRule

/*
* Copyright 2001-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.commons.betwixt.io;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.betwixt.AttributeDescriptor;
import org.apache.commons.betwixt.ElementDescriptor;
import org.apache.commons.betwixt.XMLBeanInfo;
import org.apache.commons.betwixt.XMLIntrospector;
import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
import org.apache.commons.betwixt.expression.Context;
import org.apache.commons.betwixt.expression.MethodUpdater;
import org.apache.commons.betwixt.expression.Updater;
import org.apache.commons.digester.Rule;
import org.apache.commons.digester.Rules;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;

/** <p><code>BeanCreateRule</code> is a Digester Rule for creating beans
  * from the betwixt XML metadata.</p>
  *
  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
  * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
  * @version $Revision: 1.25.2.1 $
  * @deprecated 0.5 this Rule does not allowed good integration with other Rules -
  * use {@link BeanRuleSet} instead.
  */
public class BeanCreateRule extends Rule {

    /** Logger */
    private static Log log = LogFactory.getLog( BeanCreateRule.class );
   
    /**
     * Set log to be used by <code>BeanCreateRule</code> instances
     * @param aLog the <code>Log</code> implementation for this class to log to
     */
    public static void setLog(Log aLog) {
        log = aLog;
    }
   
    /** The descriptor of this element */
    private ElementDescriptor descriptor;
    /** The Context used when evaluating Updaters */
    private Context context;
    /** Have we added our child rules to the digester? */
    private boolean addedChildren;
    /** In this begin-end loop did we actually create a new bean */
    private boolean createdBean;
    /** The type of the bean to create */
    private Class beanClass;
    /** The prefix added to digester rules */
    private String pathPrefix;
    /** Use id's to match beans? */
    private boolean matchIDs = true;
    /** allows an attribute to be specified to overload the types of beans used */
    private String classNameAttribute = "className";
   
    /**
     * Convenience constructor which uses <code>ID's</code> for matching.
     *
     * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
     * @param beanClass the <code>Class</code> to be created
     * @param pathPrefix the digester style path
     */
    public BeanCreateRule(
                            ElementDescriptor descriptor,
                            Class beanClass,
                            String pathPrefix ) {
        this( descriptor, beanClass, pathPrefix, true );
    }
   
    /**
     * Constructor taking a class.
     *
     * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
     * @param beanClass the <code>Class</code> to be created
     * @param pathPrefix the digester style path
     * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
     */
    public BeanCreateRule(
                            ElementDescriptor descriptor,
                            Class beanClass,
                            String pathPrefix,
                            boolean matchIDs ) {
        this(
                descriptor,
                beanClass,
                new Context(),
                pathPrefix,
                matchIDs);
    }
   
    /**
     * Convenience constructor which uses <code>ID's</code> for matching.
     *
     * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
     * @param beanClass the <code>Class</code> to be created
     */   
    public BeanCreateRule( ElementDescriptor descriptor, Class beanClass ) {
        this( descriptor, beanClass, true );
    }
   
    /**
     * Constructor uses standard qualified name.
     *
     * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
     * @param beanClass the <code>Class</code> to be created
     * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
     */
    public BeanCreateRule( ElementDescriptor descriptor, Class beanClass, boolean matchIDs ) {
        this( descriptor, beanClass, descriptor.getQualifiedName() + "/" , matchIDs );
    }
 
    /**
     * Convenience constructor which uses <code>ID's</code> for match.
     *
     * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
     * @param context the <code>Context</code> to be used to evaluate expressions
     * @param pathPrefix the digester path prefix
     */  
    public BeanCreateRule(
                            ElementDescriptor descriptor,
                            Context context,
                            String pathPrefix ) {   
        this( descriptor, context, pathPrefix, true );
    }
   
    /**
     * Constructor taking a context.
     *
     * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
     * @param context the <code>Context</code> to be used to evaluate expressions
     * @param pathPrefix the digester path prefix
     * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
     */
    public BeanCreateRule(
                            ElementDescriptor descriptor,
                            Context context,
                            String pathPrefix,
                            boolean matchIDs ) {
        this(
                descriptor,
                descriptor.getSingularPropertyType(),
                context,
                pathPrefix,
                matchIDs );
    }
   
    /**
     * Base constructor (used by other constructors).
     *
     * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
     * @param beanClass the <code>Class</code> of the bean to be created
     * @param context the <code>Context</code> to be used to evaluate expressions
     * @param pathPrefix the digester path prefix
     * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
     */
    private BeanCreateRule(
                            ElementDescriptor descriptor,
                            Class beanClass,
                            Context context,
                            String pathPrefix,
                            boolean matchIDs ) {
        this.descriptor = descriptor;       
        this.context = context;
        this.beanClass = beanClass;
        this.pathPrefix = pathPrefix;
        this.matchIDs = matchIDs;
        if (log.isTraceEnabled()) {
            log.trace("Created bean create rule");
            log.trace("Descriptor=" + descriptor);
            log.trace("Class=" + beanClass);
            log.trace("Path prefix=" + pathPrefix);
        }
    }
   
   
       
    // Rule interface
    //-------------------------------------------------------------------------   
   
    /**
     * Process the beginning of this element.
     *
     * @param attributes The attribute list of this element
     */
    public void begin(Attributes attributes) {
        log.debug( "Called with descriptor: " + descriptor
                    + " propertyType: " + descriptor.getPropertyType() );
       
        if (log.isTraceEnabled()) {
            int attributesLength = attributes.getLength();
            if (attributesLength > 0) {
                log.trace("Attributes:");
            }
            for (int i=0, size=attributesLength; i<size; i++) {
                log.trace("Local:" + attributes.getLocalName(i));
                log.trace("URI:" + attributes.getURI(i));
                log.trace("QName:" + attributes.getQName(i));
            }
        }
       

       
        // XXX: if a single rule instance gets reused and nesting occurs
        // XXX: we should probably use a stack of booleans to test if we created a bean
        // XXX: or let digester take nulls, which would be easier for us ;-)
        createdBean = false;
               
        Object instance = null;
        if ( beanClass != null ) {
            instance = createBean(attributes);
            if ( instance != null ) {
                createdBean = true;

                context.setBean( instance );
                digester.push(instance);
               
       
                // if we are a reference to a type we should lookup the original
                // as this ElementDescriptor will be 'hollow' and have no child attributes/elements.
                // XXX: this should probably be done by the NodeDescriptors...
                ElementDescriptor typeDescriptor = getElementDescriptor( descriptor );
                //ElementDescriptor typeDescriptor = descriptor;
       
                // iterate through all attributes       
                AttributeDescriptor[] attributeDescriptors
                    = typeDescriptor.getAttributeDescriptors();
                if ( attributeDescriptors != null ) {
                    for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) {
                        AttributeDescriptor attributeDescriptor = attributeDescriptors[i];
                       
                        // The following isn't really the right way to find the attribute
                        // but it's quite robust.
                        // The idea is that you try both namespace and local name first
                        // and if this returns null try the qName.
                        String value = attributes.getValue(
                            attributeDescriptor.getURI(),
                            attributeDescriptor.getLocalName()
                        );
                       
                        if (value == null) {
                            value = attributes.getValue(attributeDescriptor.getQualifiedName());
                        }
                       
                        if (log.isTraceEnabled()) {
                            log.trace("Attr URL:" + attributeDescriptor.getURI());
                            log.trace("Attr LocalName:" + attributeDescriptor.getLocalName() );
                            log.trace(value);
                        }
                       
                        Updater updater = attributeDescriptor.getUpdater();
                        log.trace(updater);
                        if ( updater != null && value != null ) {
                            updater.update( context, value );
                        }
                    }
                }
               
                addChildRules();
               
                // add bean for ID matching
                if ( matchIDs ) {
                    // XXX need to support custom ID attribute names
                    // XXX i have a feeling that the current mechanism might need to change
                    // XXX so i'm leaving this till later
                    String id = attributes.getValue( "id" );
                    if ( id != null ) {
                        getBeansById().put( id, instance );
                    }
                }
            }
        }
    }

    /**
     * Process the end of this element.
     */
    public void end() {
        if ( createdBean ) {
           
            // force any setters of the parent bean to be called for this new bean instance
            Updater updater = descriptor.getUpdater();
            Object instance = context.getBean();

            Object top = digester.pop();
            if (digester.getCount() == 0) {
                context.setBean(null);
            }else{
                context.setBean( digester.peek() );
            }

            if ( updater != null ) {
                if ( log.isDebugEnabled() ) {
                    log.debug( "Calling updater for: " + descriptor + " with: "
                        + instance + " on bean: " + context.getBean() );
                }
                updater.update( context, instance );
            } else {
                if ( log.isDebugEnabled() ) {
                    log.debug( "No updater for: " + descriptor + " with: "
                        + instance + " on bean: " + context.getBean() );
                }
            }
        }
    }

    /**
     * Tidy up.
     */
    public void finish() {}


    // Properties
    //-------------------------------------------------------------------------   
   

    /**
     * The name of the attribute which can be specified in the XML to override the
     * type of a bean used at a certain point in the schema.
     *
     * <p>The default value is 'className'.</p>
     *
     * @return The name of the attribute used to overload the class name of a bean
     */
    public String getClassNameAttribute() {
        return classNameAttribute;
    }

    /**
     * Sets the name of the attribute which can be specified in
     * the XML to override the type of a bean used at a certain
     * point in the schema.
     *
     * <p>The default value is 'className'.</p>
     *
     * @param classNameAttribute The name of the attribute used to overload the class name of a bean
     */
    public void setClassNameAttribute(String classNameAttribute) {
        this.classNameAttribute = classNameAttribute;
    }

    // Implementation methods
    //-------------------------------------------------------------------------   
   
    /**
     * Factory method to create new bean instances
     *
     * @param attributes the <code>Attributes</code> used to match <code>ID/IDREF</code>
     * @return the created bean
     */
    protected Object createBean(Attributes attributes) {
        //
        // See if we've got an IDREF
        //
        // XXX This should be customizable but i'm not really convinced by the existing system
        // XXX maybe it's going to have to change so i'll use 'idref' for nows
        //
        if ( matchIDs ) {
            String idref = attributes.getValue( "idref" );
            if ( idref != null ) {
                // XXX need to check up about ordering
                // XXX this is a very simple system that assumes that id occurs before idrefs
                // XXX would need some thought about how to implement a fuller system
                log.trace( "Found IDREF" );
                Object bean = getBeansById().get( idref );
                if ( bean != null ) {
                    if (log.isTraceEnabled()) {
                        log.trace( "Matched bean " + bean );
                    }
                    return bean;
                }
                log.trace( "No match found" );
            }
        }
       
        Class theClass = beanClass;
        try {
           
            String className = attributes.getValue(classNameAttribute);
            if (className != null) {
                // load the class we should instantiate
                theClass = getDigester().getClassLoader().loadClass(className);
            }
            if (log.isTraceEnabled()) {
                log.trace( "Creating instance of " + theClass );
            }
            return theClass.newInstance();
           
        } catch (Exception e) {
            log.warn( "Could not create instance of type: " + theClass.getName() );
            return null;
        }
    }   
       
    /** Adds the rules to the digester for all child elements */
    protected void addChildRules() {
        if ( ! addedChildren ) {
            addedChildren = true;
           
            addChildRules( pathPrefix, descriptor );
        }
    }
                       
    /**
     * Add child rules for given descriptor at given prefix
     *
     * @param prefix add child rules at this (digester) path prefix
     * @param currentDescriptor add child rules for this descriptor
     */
    protected void addChildRules(String prefix, ElementDescriptor currentDescriptor ) {        
       
        if (log.isTraceEnabled()) {
            log.trace("Adding child rules for " + currentDescriptor + "@" + prefix);
        }
       
        // if we are a reference to a type we should lookup the original
        // as this ElementDescriptor will be 'hollow' and have no child attributes/elements.
        // XXX: this should probably be done by the NodeDescriptors...
        ElementDescriptor typeDescriptor = getElementDescriptor( currentDescriptor );
        //ElementDescriptor typeDescriptor = descriptor;

       
        ElementDescriptor[] childDescriptors = typeDescriptor.getElementDescriptors();
        if ( childDescriptors != null ) {
            for ( int i = 0, size = childDescriptors.length; i < size; i++ ) {
                final ElementDescriptor childDescriptor = childDescriptors[i];
                if (log.isTraceEnabled()) {
                    log.trace("Processing child " + childDescriptor);
                }
               
                String qualifiedName = childDescriptor.getQualifiedName();
                if ( qualifiedName == null ) {
                    log.trace( "Ignoring" );
                    continue;
                }
                String path = prefix + qualifiedName;
                // this code is for making sure that recursive elements
                // can also be used..
               
                if ( qualifiedName.equals( currentDescriptor.getQualifiedName() )
                        && currentDescriptor.getPropertyName() != null ) {
                    log.trace("Creating generic rule for recursive elements");
                    int index = -1;
                    if (childDescriptor.isWrapCollectionsInElement()) {
                        index = prefix.indexOf(qualifiedName);
                        if (index == -1) {
                            // shouldn't happen..
                            log.debug( "Oops - this shouldn't happen" );
                            continue;
                        }
                        int removeSlash = prefix.endsWith("/")?1:0;
                        path = "*/" + prefix.substring(index, prefix.length()-removeSlash);
                    }else{
                        // we have a element/element type of thing..
                        ElementDescriptor[] desc = currentDescriptor.getElementDescriptors();
                        if (desc.length == 1) {
                            path = "*/"+desc[0].getQualifiedName();
                        }
                    }
                    Rule rule = new BeanCreateRule( childDescriptor, context, path, matchIDs);
                    addRule(path, rule);
                    continue;
                }
                if ( childDescriptor.getUpdater() != null ) {
                    if (log.isTraceEnabled()) {
                        log.trace("Element has updater "
                         + ((MethodUpdater) childDescriptor.getUpdater()).getMethod().getName());
                    }
                    if ( childDescriptor.isPrimitiveType() ) {
                        addPrimitiveTypeRule(path, childDescriptor);
                       
                    } else {
                        // add the first child to the path
                        ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors();
                        if ( grandChildren != null && grandChildren.length > 0 ) {
                            ElementDescriptor grandChild = grandChildren[0];
                            String grandChildQName = grandChild.getQualifiedName();
                            if ( grandChildQName != null && grandChildQName.length() > 0 ) {
                                if (childDescriptor.isWrapCollectionsInElement()) {
                                    path += '/' + grandChildQName;
                                   
                                } else {
                                    path = prefix + (prefix.endsWith("/")?"":"/") + grandChildQName;
                                }
                            }
                        }
                       
                        // maybe we are adding a primitve type to a collection/array
                        Class beanClass = childDescriptor.getSingularPropertyType();
                        if ( XMLIntrospectorHelper.isPrimitiveType( beanClass ) ) {
                            addPrimitiveTypeRule(path, childDescriptor);
                           
                        } else {
                            Rule rule = new BeanCreateRule(
                                                        childDescriptor,
                                                        context,
                                                        path + '/',
                                                        matchIDs );
                            addRule( path, rule );
                        }
                    }
                } else {
                    log.trace("Element does not have updater");
                }

                ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors();
                if ( grandChildren != null && grandChildren.length > 0 ) {
                    log.trace("Adding grand children");
                    addChildRules( path + '/', childDescriptor );
                }
            }
        }
    }
   
    /**
     * Get the associated bean reader.
     *
     * @return the <code>BeanReader</code digesting the xml
     */
    protected BeanReader getBeanReader() {
        // XXX this breaks the rule contact
        // XXX maybe the reader should be passed in the constructor
        return (BeanReader) getDigester();
    }
   
    /** Allows the navigation from a reference to a property object to the descriptor defining what
     * the property is. i.e. doing the join from a reference to a type to lookup its descriptor.
     * This could be done automatically by the NodeDescriptors. Refer to TODO.txt for more info.
     *
     * @param propertyDescriptor find descriptor for property object referenced by this descriptor
     * @return descriptor for the singular property class type referenced.
     */
    protected ElementDescriptor getElementDescriptor( ElementDescriptor propertyDescriptor ) {
        Class beanClass = propertyDescriptor.getSingularPropertyType();
        if ( beanClass != null ) {
            XMLIntrospector introspector = getBeanReader().getXMLIntrospector();
            try {
                XMLBeanInfo xmlInfo = introspector.introspect( beanClass );
                return xmlInfo.getElementDescriptor();
               
            } catch (Exception e) {
                log.warn( "Could not introspect class: " + beanClass, e );
            }
        }
        // could not find a better descriptor so use the one we've got
        return propertyDescriptor;
    }
   
    /**
     * Adds a new Digester rule to process the text as a primitive type
     *
     * @param path digester path where this rule will be attached
     * @param childDescriptor update this <code>ElementDescriptor</code> with the body text
     */
    protected void addPrimitiveTypeRule(String path, final ElementDescriptor childDescriptor) {
        Rule rule = new Rule() {
            public void body(String text) throws Exception {
                childDescriptor.getUpdater().update( context, text );
            }       
        };
        addRule( path, rule );
    }
   
    /**
     * Safely add a rule with given path.
     *
     * @param path the digester path to add rule at
     * @param rule the <code>Rule</code> to add
     */
    protected void addRule(String path, Rule rule) {
        Rules rules = digester.getRules();
        List matches = rules.match(null, path);
        if ( matches.isEmpty() ) {
            if ( log.isDebugEnabled() ) {
                log.debug( "Adding digester rule for path: " + path + " rule: " + rule );
            }
            digester.addRule( path, rule );
           
        } else {
            if ( log.isDebugEnabled() ) {
                log.debug( "Ignoring duplicate digester rule for path: "
                            + path + " rule: " + rule );
                log.debug( "New rule (not added): " + rule );
                log.debug( "Existing rule:" + matches.get(0) );
            }
        }
    }   

    /**
     * Get the map used to index beans (previously read in) by id.
     * This is stored in the evaluation context.
     *
     * @return map indexing beans created by id
     */
    protected Map getBeansById() {
        //
        // we need a single index for beans read in by id
        // so that we can use them for idref-matching
        // store this in the context
        //
        Map beansById = (Map) context.getVariable( "beans-index" );
        if ( beansById == null ) {
            // lazy creation
            beansById = new HashMap();
            context.setVariable( "beans-index", beansById );
            log.trace( "Created new index-by-id map" );
        }
       
        return beansById;
    }
   
    /**
     * Return something meaningful for logging.
     *
     * @return something useful for logging
     */
    public String toString() {
        return "BeanCreateRule [path prefix=" + pathPrefix + " descriptor=" + descriptor + "]";
    }
   
}
TOP

Related Classes of org.apache.commons.betwixt.io.BeanCreateRule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.