001    package org.apache.commons.betwixt.digester;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     * 
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     * 
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */ 
019    import java.beans.BeanInfo;
020    import java.beans.Introspector;
021    import java.beans.PropertyDescriptor;
022    import java.lang.reflect.Method;
023    
024    import org.apache.commons.betwixt.AttributeDescriptor;
025    import org.apache.commons.betwixt.ElementDescriptor;
026    import org.apache.commons.betwixt.XMLUtils;
027    import org.apache.commons.betwixt.expression.ConstantExpression;
028    import org.apache.commons.betwixt.expression.MethodExpression;
029    import org.apache.commons.betwixt.expression.MethodUpdater;
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.xml.sax.Attributes;
033    import org.xml.sax.SAXException;
034    
035    /** 
036      * <p><code>AttributeRule</code> the digester Rule for parsing the 
037      * &lt;attribute&gt; elements.</p>
038      *
039      * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
040      * @version $Id: AttributeRule.java 438373 2006-08-30 05:17:21Z bayard $
041      */
042    public class AttributeRule extends RuleSupport {
043    
044        /** Logger */
045        private static final Log log = LogFactory.getLog( AttributeRule.class );
046        /** This loads all classes created by name. Defaults to this class's classloader */
047        private ClassLoader classLoader;
048        /** The <code>Class</code> whose .betwixt file is being digested */
049        private Class beanClass;
050        
051        /** Base constructor */
052        public AttributeRule() {
053            this.classLoader = getClass().getClassLoader();
054        }
055        
056        // Rule interface
057        //-------------------------------------------------------------------------    
058        
059        /**
060         * Process the beginning of this element.
061         *
062         * @param attributes The attribute list of this element
063         * @throws SAXException 1. If the attribute tag is not inside an element tag.
064         * 2. If the name attribute is not valid XML attribute name.
065         */
066        public void begin(String name, String namespace, Attributes attributes) throws SAXException {
067            
068            AttributeDescriptor descriptor = new AttributeDescriptor();
069            String nameAttributeValue = attributes.getValue( "name" );
070    
071            // check that name is well formed 
072            if ( !XMLUtils.isWellFormedXMLName( nameAttributeValue ) ) {
073                throw new SAXException("'" + nameAttributeValue + "' would not be a well formed xml attribute name.");
074            }
075            
076            String qName = nameAttributeValue;
077            descriptor.setLocalName( nameAttributeValue );
078            String uri = attributes.getValue( "uri" );
079            if ( uri != null ) {
080                descriptor.setURI( uri );  
081                String prefix = getXMLIntrospector().getConfiguration().getPrefixMapper().getPrefix(uri);
082                qName = prefix + ":" + nameAttributeValue; 
083            }
084            descriptor.setQualifiedName( qName );
085            
086            String propertyName = attributes.getValue( "property" );
087            descriptor.setPropertyName( propertyName );
088            descriptor.setPropertyType( loadClass( attributes.getValue( "type" ) ) );
089            
090            if ( propertyName != null && propertyName.length() > 0 ) {
091                configureDescriptor(descriptor);
092            } else {
093                String value = attributes.getValue( "value" );
094                if ( value != null ) {
095                    descriptor.setTextExpression( new ConstantExpression( value ) );
096                }
097            }
098    
099            Object top = digester.peek();
100            if ( top instanceof ElementDescriptor ) {
101                ElementDescriptor parent = (ElementDescriptor) top;
102                parent.addAttributeDescriptor( descriptor );
103            } else {
104                throw new SAXException( "Invalid use of <attribute>. It should " 
105                    + "be nested inside an <element> element" );
106            }            
107    
108            digester.push(descriptor);        
109        }
110    
111    
112        /**
113         * Process the end of this element.
114         */
115        public void end(String name, String namespace) {
116            AttributeDescriptor descriptor = (AttributeDescriptor)digester.pop();
117            ElementDescriptor parent = (ElementDescriptor)digester.peek();
118    
119            // check for attribute suppression
120            if( getXMLIntrospector().getConfiguration().getAttributeSuppressionStrategy().suppress(descriptor)) {
121                parent.removeAttributeDescriptor(descriptor);
122            }
123        }
124    
125        
126        // Implementation methods
127        //-------------------------------------------------------------------------    
128        /**
129         * Loads a class (using the appropriate classloader)
130         *
131         * @param name the name of the class to load
132         * @return the class instance loaded by the appropriate classloader
133         */
134        protected Class loadClass( String name ) {
135            // XXX: should use a ClassLoader to handle complex class loading situations
136            if ( name != null ) {
137                try {
138                    return classLoader.loadClass(name);
139                } catch (Exception e) { // SWALLOW
140                }
141            }
142            return null;            
143        }
144        
145        /** 
146         * Set the Expression and Updater from a bean property name 
147         * @param attributeDescriptor configure this <code>AttributeDescriptor</code> 
148         * from the property with a matching name in the bean class
149         */
150        protected void configureDescriptor(AttributeDescriptor attributeDescriptor) {
151            Class beanClass = getBeanClass();
152            if ( beanClass != null ) {
153                String name = attributeDescriptor.getPropertyName();
154                try {
155                    BeanInfo beanInfo;
156                    if( getXMLIntrospector().getConfiguration().ignoreAllBeanInfo() ) {
157                        beanInfo = Introspector.getBeanInfo( beanClass, Introspector.IGNORE_ALL_BEANINFO );
158                    }
159                    else {
160                        beanInfo = Introspector.getBeanInfo( beanClass );
161                    }
162                    PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
163                    if ( descriptors != null ) {
164                        for ( int i = 0, size = descriptors.length; i < size; i++ ) {
165                            PropertyDescriptor descriptor = descriptors[i];
166                            if ( name.equals( descriptor.getName() ) ) {
167                                configureProperty( attributeDescriptor, descriptor );
168                                getProcessedPropertyNameSet().add( name );
169                                break;
170                            }
171                        }
172                    }
173                } catch (Exception e) {
174                    log.warn( "Caught introspection exception", e );
175                }
176            }
177        }    
178        
179        /**
180         * Configure an <code>AttributeDescriptor</code> from a <code>PropertyDescriptor</code>
181         *
182         * @param attributeDescriptor configure this <code>AttributeDescriptor</code>
183         * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
184         */
185        private void configureProperty( 
186                                        AttributeDescriptor attributeDescriptor, 
187                                        PropertyDescriptor propertyDescriptor ) {
188            Class type = propertyDescriptor.getPropertyType();
189            Method readMethod = propertyDescriptor.getReadMethod();
190            Method writeMethod = propertyDescriptor.getWriteMethod();
191            
192            if ( readMethod == null ) {
193                log.trace( "No read method" );
194                return;
195            }
196            
197            if ( log.isTraceEnabled() ) {
198                log.trace( "Read method=" + readMethod );
199            }
200            
201            // choose response from property type
202            if ( getXMLIntrospector().isLoopType( type ) ) {
203                log.warn( "Using loop type for an attribute. Type = " 
204                        + type.getName() + " attribute: " + attributeDescriptor.getQualifiedName() );
205            }
206    
207            log.trace( "Standard property" );
208            attributeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
209            
210            if ( writeMethod != null ) {
211                attributeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
212            }
213            
214            attributeDescriptor.setPropertyName( propertyDescriptor.getName() );
215            attributeDescriptor.setPropertyType( type );        
216            
217            // XXX: associate more bean information with the descriptor?
218            //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
219            //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
220        }
221        
222    }