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    
024    import java.util.Formatter;
025    import java.util.Stack;
026    
027    import 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     */
039    public 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    }