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;
023
024import java.util.Formatter;
025import java.util.Stack;
026
027import org.xml.sax.Attributes;
028
029/**
030 * <p>
031 * Rule implementation that uses an {@link ObjectCreationFactory} to create a new object which it pushes onto the object
032 * stack. When the element is complete, the object will be popped.
033 * </p>
034 * <p>
035 * This rule is intended in situations where the element's attributes are needed before the object can be created. A
036 * common senario is for the ObjectCreationFactory implementation to use the attributes as parameters in a call to
037 * either a factory method or to a non-empty constructor.
038 */
039public class FactoryCreateRule
040    extends Rule
041{
042
043    // ----------------------------------------------------------- Fields
044
045    /** Should exceptions thrown by the factory be ignored? */
046    private boolean ignoreCreateExceptions;
047
048    /** Stock to manage */
049    private Stack<Boolean> exceptionIgnoredStack;
050
051    // ----------------------------------------------------------- Constructors
052
053    /**
054     * <p>
055     * Construct a factory create rule that will use the specified class name to create an {@link ObjectCreationFactory}
056     * which will then be used to create an object and push it on the stack.
057     * </p>
058     * <p>
059     * Exceptions thrown during the object creation process will be propagated.
060     * </p>
061     *
062     * @param className Java class name of the object creation factory class
063     */
064    public FactoryCreateRule( String className )
065    {
066        this( className, false );
067    }
068
069    /**
070     * <p>
071     * Construct a factory create rule that will use the specified class to create an {@link ObjectCreationFactory}
072     * which will then be used to create an object and push it on the stack.
073     * </p>
074     * <p>
075     * Exceptions thrown during the object creation process will be propagated.
076     * </p>
077     *
078     * @param clazz Java class name of the object creation factory class
079     */
080    public FactoryCreateRule( Class<? extends ObjectCreationFactory<?>> clazz )
081    {
082        this( clazz, false );
083    }
084
085    /**
086     * <p>
087     * Construct a factory create rule that will use the specified class name (possibly overridden by the specified
088     * attribute if present) to create an {@link ObjectCreationFactory}, which will then be used to instantiate an
089     * object instance and push it onto the stack.
090     * </p>
091     * <p>
092     * Exceptions thrown during the object creation process will be propagated.
093     * </p>
094     *
095     * @param className Default Java class name of the factory class
096     * @param attributeName Attribute name which, if present, contains an override of the class name of the object
097     *            creation factory to create.
098     */
099    public FactoryCreateRule( String className, String attributeName )
100    {
101        this( className, attributeName, false );
102    }
103
104    /**
105     * <p>
106     * Construct a factory create rule that will use the specified class (possibly overridden by the specified attribute
107     * if present) to create an {@link ObjectCreationFactory}, which will then be used to instantiate an object instance
108     * and push it onto the stack.
109     * </p>
110     * <p>
111     * Exceptions thrown during the object creation process will be propagated.
112     * </p>
113     *
114     * @param clazz Default Java class name of the factory class
115     * @param attributeName Attribute name which, if present, contains an override of the class name of the object
116     *            creation factory to create.
117     */
118    public FactoryCreateRule( Class<? extends ObjectCreationFactory<?>> clazz, String attributeName )
119    {
120        this( clazz, attributeName, false );
121    }
122
123    /**
124     * <p>
125     * Construct a factory create rule using the given, already instantiated, {@link ObjectCreationFactory}.
126     * </p>
127     * <p>
128     * Exceptions thrown during the object creation process will be propagated.
129     * </p>
130     *
131     * @param creationFactory called on to create the object.
132     */
133    public FactoryCreateRule( ObjectCreationFactory<?> creationFactory )
134    {
135        this( creationFactory, false );
136    }
137
138    /**
139     * Construct a factory create rule that will use the specified class name to create an {@link ObjectCreationFactory}
140     * which will then be used to create an object and push it on the stack.
141     *
142     * @param className Java class name of the object creation factory class
143     * @param ignoreCreateExceptions if true, exceptions thrown by the object creation factory will be ignored.
144     */
145    public FactoryCreateRule( String className, boolean ignoreCreateExceptions )
146    {
147        this( className, null, ignoreCreateExceptions );
148    }
149
150    /**
151     * Construct a factory create rule that will use the specified class to create an {@link ObjectCreationFactory}
152     * which will then be used to create an object and push it on the stack.
153     *
154     * @param clazz Java class name of the object creation factory class
155     * @param ignoreCreateExceptions if true, exceptions thrown by the object creation factory will be ignored.
156     */
157    public FactoryCreateRule( Class<? extends ObjectCreationFactory<?>> clazz, boolean ignoreCreateExceptions )
158    {
159        this( clazz, null, ignoreCreateExceptions );
160    }
161
162    /**
163     * Construct a factory create rule that will use the specified class name (possibly overridden by the specified
164     * attribute if present) to create an {@link ObjectCreationFactory}, which will then be used to instantiate an
165     * object instance and push it onto the stack.
166     *
167     * @param className Default Java class name of the factory class
168     * @param attributeName Attribute name which, if present, contains an override of the class name of the object
169     *            creation factory to create.
170     * @param ignoreCreateExceptions if true, exceptions thrown by the object creation factory will be ignored.
171     */
172    public FactoryCreateRule( String className, String attributeName, boolean ignoreCreateExceptions )
173    {
174        this.className = className;
175        this.attributeName = attributeName;
176        this.ignoreCreateExceptions = ignoreCreateExceptions;
177    }
178
179    /**
180     * Construct a factory create rule that will use the specified class (possibly overridden by the specified attribute
181     * if present) to create an {@link ObjectCreationFactory}, which will then be used to instantiate an object instance
182     * and push it onto the stack.
183     *
184     * @param clazz Default Java class name of the factory class
185     * @param attributeName Attribute name which, if present, contains an override of the class name of the object
186     *            creation factory to create.
187     * @param ignoreCreateExceptions if true, exceptions thrown by the object creation factory will be ignored.
188     */
189    public FactoryCreateRule( Class<? extends ObjectCreationFactory<?>> clazz, String attributeName,
190                              boolean ignoreCreateExceptions )
191    {
192        this( clazz.getName(), attributeName, ignoreCreateExceptions );
193    }
194
195    /**
196     * Construct a factory create rule using the given, already instantiated, {@link ObjectCreationFactory}.
197     *
198     * @param creationFactory called on to create the object.
199     * @param ignoreCreateExceptions if true, exceptions thrown by the object creation factory will be ignored.
200     */
201    public FactoryCreateRule( ObjectCreationFactory<?> creationFactory, boolean ignoreCreateExceptions )
202    {
203        this.creationFactory = creationFactory;
204        this.ignoreCreateExceptions = ignoreCreateExceptions;
205    }
206
207    // ----------------------------------------------------- Instance Variables
208
209    /**
210     * The attribute containing an override class name if it is present.
211     */
212    protected String attributeName = null;
213
214    /**
215     * The Java class name of the ObjectCreationFactory to be created. This class must have a no-arguments constructor.
216     */
217    protected String className = null;
218
219    /**
220     * The object creation factory we will use to instantiate objects as required based on the attributes specified in
221     * the matched XML element.
222     */
223    protected ObjectCreationFactory<?> creationFactory = null;
224
225    // --------------------------------------------------------- Public Methods
226
227    /**
228     * {@inheritDoc}
229     */
230    @Override
231    public void begin( String namespace, String name, Attributes attributes )
232        throws Exception
233    {
234        if ( ignoreCreateExceptions )
235        {
236            if ( exceptionIgnoredStack == null )
237            {
238                exceptionIgnoredStack = new Stack<Boolean>();
239            }
240
241            try
242            {
243                Object instance = getFactory( attributes ).createObject( attributes );
244
245                if ( getDigester().getLogger().isDebugEnabled() )
246                {
247                    getDigester().getLogger().debug( format( "[FactoryCreateRule]{%s} New %s",
248                                                             getDigester().getMatch(),
249                                                             ( instance == null ? "null object"
250                                                                             : instance.getClass().getName() ) ) );
251                }
252                getDigester().push( instance );
253                exceptionIgnoredStack.push( Boolean.FALSE );
254
255            }
256            catch ( Exception e )
257            {
258                // log message and error
259                if ( getDigester().getLogger().isInfoEnabled() )
260                {
261                    getDigester().getLogger().info( format( "[FactoryCreateRule]{%s} Create exception ignored: %s",
262                                                            getDigester().getMatch(),
263                                                            ( ( e.getMessage() == null ) ? e.getClass().getName()
264                                                                            : e.getMessage() ) ) );
265                    if ( getDigester().getLogger().isDebugEnabled() )
266                    {
267                        getDigester().getLogger().debug( "[FactoryCreateRule] Ignored exception:", e );
268                    }
269                }
270                exceptionIgnoredStack.push( Boolean.TRUE );
271            }
272
273        }
274        else
275        {
276            Object instance = getFactory( attributes ).createObject( attributes );
277
278            if ( getDigester().getLogger().isDebugEnabled() )
279            {
280                getDigester().getLogger().debug( format( "[FactoryCreateRule]{%s} New %s",
281                                                         getDigester().getMatch(),
282                                                         ( instance == null ? "null object"
283                                                                         : instance.getClass().getName() ) ) );
284            }
285            getDigester().push( instance );
286        }
287    }
288
289    /**
290     * {@inheritDoc}
291     */
292    @Override
293    public void end( String namespace, String name )
294        throws Exception
295    {
296        // check if object was created
297        // this only happens if an exception was thrown and we're ignoring them
298        if ( ignoreCreateExceptions
299                        && exceptionIgnoredStack != null
300                        && !exceptionIgnoredStack.empty()
301                        && exceptionIgnoredStack.pop().booleanValue() )
302        {
303            // creation exception was ignored
304            // nothing was put onto the stack
305            if ( getDigester().getLogger().isTraceEnabled() )
306            {
307                getDigester().getLogger().trace( format( "[FactoryCreateRule]{%s} No creation so no push so no pop",
308                                                         getDigester().getMatch() ) );
309            }
310            return;
311        }
312
313        Object top = getDigester().pop();
314        if ( getDigester().getLogger().isDebugEnabled() )
315        {
316            getDigester().getLogger().debug( format( "[FactoryCreateRule]{%s} Pop %s",
317                                                     getDigester().getMatch(),
318                                                     top.getClass().getName() ) );
319        }
320    }
321
322    /**
323     * {@inheritDoc}
324     */
325    @Override
326    public void finish()
327        throws Exception
328    {
329        if ( attributeName != null )
330        {
331            creationFactory = null;
332        }
333    }
334
335    /**
336     * {@inheritDoc}
337     */
338    @Override
339    public String toString()
340    {
341        Formatter formatter = new Formatter().format( "FactoryCreateRule[className=%s, attributeName=%s",
342                                                      className, attributeName );
343        if ( creationFactory != null )
344        {
345            formatter.format( ", creationFactory=%s", creationFactory );
346        }
347        formatter.format( "]" );
348        return ( formatter.toString() );
349    }
350
351    // ------------------------------------------------------ Protected Methods
352
353    /**
354     * Return an instance of our associated object creation factory, creating one if necessary.
355     *
356     * @param attributes Attributes passed to our factory creation element
357     * @return An instance of our associated object creation factory, creating one if necessary
358     * @exception Exception if any error occurs
359     */
360    protected ObjectCreationFactory<?> getFactory( Attributes attributes )
361        throws Exception
362    {
363        if ( creationFactory == null )
364        {
365            String realClassName = className;
366            if ( attributeName != null )
367            {
368                String value = attributes.getValue( attributeName );
369                if ( value != null )
370                {
371                    realClassName = value;
372                }
373            }
374            if ( getDigester().getLogger().isDebugEnabled() )
375            {
376                getDigester().getLogger().debug( format( "[FactoryCreateRule]{%s} New factory %s",
377                                                         getDigester().getMatch(), realClassName ) );
378            }
379            Class<?> clazz = getDigester().getClassLoader().loadClass( realClassName );
380            creationFactory = (ObjectCreationFactory<?>) clazz.newInstance();
381            creationFactory.setDigester( getDigester() );
382        }
383        return ( creationFactory );
384    }
385
386}