001    package org.apache.commons.digester3;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import static java.lang.String.format;
023    import static org.apache.commons.beanutils.BeanUtils.setProperty;
024    import static org.apache.commons.beanutils.PropertyUtils.getPropertyDescriptor;
025    
026    import java.beans.PropertyDescriptor;
027    
028    import org.apache.commons.beanutils.DynaBean;
029    import org.apache.commons.beanutils.DynaProperty;
030    import org.xml.sax.Attributes;
031    
032    /**
033     * <p>
034     * Rule implements sets a bean property on the top object to the body text.
035     * </p>
036     * <p>
037     * The property set:
038     * </p>
039     * <ul>
040     * <li>can be specified when the rule is created</li>
041     * <li>or can match the current element when the rule is called.</li>
042     * </ul>
043     * <p>
044     * Using the second method and the {@link ExtendedBaseRules} child match pattern, all the child elements can be
045     * automatically mapped to properties on the parent object.
046     * </p>
047     */
048    public class BeanPropertySetterRule
049        extends Rule
050    {
051    
052        // ----------------------------------------------------------- Constructors
053    
054        /**
055         * <p>
056         * Construct rule that sets the given property from the body text.
057         * </p>
058         * 
059         * @param propertyName name of property to set
060         */
061        public BeanPropertySetterRule( String propertyName )
062        {
063            this.propertyName = propertyName;
064        }
065    
066        /**
067         * <p>
068         * Construct rule that automatically sets a property from the body text.
069         * <p>
070         * This construct creates a rule that sets the property on the top object named the same as the current element.
071         */
072        public BeanPropertySetterRule()
073        {
074            this( null );
075        }
076    
077        // ----------------------------------------------------- Instance Variables
078    
079        /**
080         * Set this property on the top object.
081         */
082        private String propertyName;
083    
084        /**
085         * Extract the property name from attribute
086         */
087        private String propertyNameFromAttribute;
088    
089        /**
090         * The body text used to set the property.
091         */
092        private String bodyText = null;
093    
094        // --------------------------------------------------------- Public Methods
095    
096        /**
097         * Returns the property name associated to this setter rule.
098         *
099         * @return The property name associated to this setter rule
100         */
101        public String getPropertyName()
102        {
103            return propertyName;
104        }
105    
106        /**
107         * Sets the attribute name from which the property name has to be extracted.
108         *
109         * @param propertyNameFromAttribute the attribute name from which the property name has to be extracted.
110         * @since 3.0
111         */
112        public void setPropertyNameFromAttribute( String propertyNameFromAttribute )
113        {
114            this.propertyNameFromAttribute = propertyNameFromAttribute;
115        }
116    
117        /**
118         * Returns the body text used to set the property.
119         *
120         * @return The body text used to set the property
121         */
122        protected String getBodyText()
123        {
124            return bodyText;
125        }
126    
127        /**
128         * {@inheritDoc}
129         */
130        @Override
131        public void begin( String namespace, String name, Attributes attributes )
132            throws Exception
133        {
134            if ( propertyNameFromAttribute != null )
135            {
136                propertyName = attributes.getValue( propertyNameFromAttribute );
137    
138                getDigester().getLogger().warn( format( "[BeanPropertySetterRule]{%s} Attribute '%s' not found in matching element '%s'",
139                                                        getDigester().getMatch(), propertyNameFromAttribute, name ) );
140            }
141        }
142    
143        /**
144         * {@inheritDoc}
145         */
146        @Override
147        public void body( String namespace, String name, String text )
148            throws Exception
149        {
150            // log some debugging information
151            if ( getDigester().getLogger().isDebugEnabled() )
152            {
153                getDigester().getLogger().debug( format( "[BeanPropertySetterRule]{%s} Called with text '%s'",
154                                                         getDigester().getMatch(),
155                                                         text ) );
156            }
157    
158            bodyText = text.trim();
159        }
160    
161        /**
162         * {@inheritDoc}
163         */
164        @Override
165        public void end( String namespace, String name )
166            throws Exception
167        {
168            String property = propertyName;
169    
170            if ( property == null )
171            {
172                // If we don't have a specific property name,
173                // use the element name.
174                property = name;
175            }
176    
177            // Get a reference to the top object
178            Object top = getDigester().peek();
179    
180            // log some debugging information
181            if ( getDigester().getLogger().isDebugEnabled() )
182            {
183                getDigester().getLogger().debug( format( "[BeanPropertySetterRule]{%s} Set %s property %s with text %s",
184                                                         getDigester().getMatch(),
185                                                         top.getClass().getName(),
186                                                         property,
187                                                         bodyText ) );
188            }
189    
190            // Force an exception if the property does not exist
191            // (BeanUtils.setProperty() silently returns in this case)
192            if ( top instanceof DynaBean )
193            {
194                DynaProperty desc = ( (DynaBean) top ).getDynaClass().getDynaProperty( property );
195                if ( desc == null )
196                {
197                    throw new NoSuchMethodException( "Bean has no property named " + property );
198                }
199            }
200            else
201            /* this is a standard JavaBean */
202            {
203                PropertyDescriptor desc = getPropertyDescriptor( top, property );
204                if ( desc == null )
205                {
206                    throw new NoSuchMethodException( "Bean has no property named " + property );
207                }
208            }
209    
210            // Set the property (with conversion as necessary)
211            setProperty( top, property, bodyText );
212        }
213    
214        /**
215         * {@inheritDoc}
216         */
217        @Override
218        public void finish()
219            throws Exception
220        {
221            bodyText = null;
222        }
223    
224        /**
225         * {@inheritDoc}
226         */
227        @Override
228        public String toString()
229        {
230            return format( "BeanPropertySetterRule[propertyName=%s]", propertyName );
231        }
232    
233    }