001package 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
022import static java.lang.String.format;
023import static org.apache.commons.beanutils.BeanUtils.setProperty;
024import static org.apache.commons.beanutils.PropertyUtils.getPropertyDescriptor;
025
026import java.beans.PropertyDescriptor;
027
028import org.apache.commons.beanutils.DynaBean;
029import org.apache.commons.beanutils.DynaProperty;
030import 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 */
048public 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}