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.System.arraycopy;
023    import static java.lang.String.format;
024    import static org.apache.commons.beanutils.ConstructorUtils.getAccessibleConstructor;
025    import static org.apache.commons.beanutils.ConvertUtils.convert;
026    
027    import java.lang.reflect.Constructor;
028    import java.lang.reflect.Method;
029    import java.util.ArrayList;
030    import java.util.Arrays;
031    
032    import net.sf.cglib.proxy.Callback;
033    import net.sf.cglib.proxy.Enhancer;
034    import net.sf.cglib.proxy.Factory;
035    import net.sf.cglib.proxy.MethodInterceptor;
036    import net.sf.cglib.proxy.MethodProxy;
037    
038    import org.xml.sax.Attributes;
039    import org.xml.sax.SAXException;
040    
041    /**
042     * Rule implementation that creates a new object and pushes it onto the object stack. When the element is complete, the
043     * object will be popped
044     */
045    public class ObjectCreateRule
046        extends Rule
047    {
048        private static class DeferredConstructionCallback implements MethodInterceptor
049        {
050            Constructor<?> constructor;
051            Object[] constructorArgs;
052            ArrayList<RecordedInvocation> invocations = new ArrayList<RecordedInvocation>();
053            Object delegate;
054    
055            DeferredConstructionCallback( Constructor<?> constructor, Object[] constructorArgs )
056            {
057                this.constructor = constructor;
058                this.constructorArgs = constructorArgs;
059            }
060    
061            public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy )
062                throws Throwable
063            {
064                boolean hasDelegate = delegate != null;
065                if ( !hasDelegate )
066                {
067                    invocations.add( new RecordedInvocation( method, args ) );
068                }
069                if ( hasDelegate )
070                {
071                    return proxy.invoke( delegate, args );
072                }
073                return proxy.invokeSuper( obj, args );
074            }
075    
076            void establishDelegate()
077                throws Exception
078            {
079                convertTo( constructor.getParameterTypes(), constructorArgs );
080                delegate = constructor.newInstance( constructorArgs );
081                for ( RecordedInvocation invocation : invocations )
082                {
083                    invocation.getInvokedMethod().invoke( delegate, invocation.getArguments() );
084                }
085                constructor = null;
086                constructorArgs = null;
087                invocations = null;
088            }
089        }
090    
091        private static class ProxyManager
092        {
093            private final Class<?> clazz;
094            private final Constructor<?> constructor;
095            private final Object[] templateConstructorArguments;
096            private final Digester digester;
097            private final boolean hasDefaultConstructor;
098            private Factory factory;
099    
100            ProxyManager( Class<?> clazz, Constructor<?> constructor, Object[] constructorArguments, Digester digester )
101            {
102                this.clazz = clazz;
103                hasDefaultConstructor = getAccessibleConstructor( clazz, new Class[0] ) != null;
104                this.constructor = constructor;
105                Class<?>[] argTypes = constructor.getParameterTypes();
106                templateConstructorArguments = new Object[argTypes.length];
107                if ( constructorArguments == null )
108                {
109                    for ( int i = 0; i < templateConstructorArguments.length; i++ )
110                    {
111                        if ( argTypes[i].equals( boolean.class ) )
112                        {
113                            templateConstructorArguments[i] = Boolean.FALSE;
114                            continue;
115                        }
116                        if ( argTypes[i].isPrimitive() )
117                        {
118                            templateConstructorArguments[i] = convert( "0", argTypes[i] );
119                            continue;
120                        }
121                        templateConstructorArguments[i] = null;
122                    }
123                }
124                else
125                {
126                    if ( constructorArguments.length != argTypes.length )
127                    {
128                        throw new IllegalArgumentException(
129                            format( "wrong number of constructor arguments specified: %s instead of %s",
130                            constructorArguments.length, argTypes.length ) );
131                    }
132                    arraycopy( constructorArguments, 0, templateConstructorArguments, 0, constructorArguments.length );
133                }
134                convertTo( argTypes, templateConstructorArguments );
135                this.digester = digester;
136            }
137    
138            Object createProxy()
139            {
140                Object[] constructorArguments = new Object[templateConstructorArguments.length];
141                arraycopy( templateConstructorArguments, 0, constructorArguments, 0, constructorArguments.length );
142                digester.pushParams( constructorArguments );
143    
144                DeferredConstructionCallback callback =
145                    new DeferredConstructionCallback( constructor, constructorArguments );
146    
147                Object result;
148    
149                if ( factory == null )
150                {
151                    Enhancer enhancer = new Enhancer();
152                    enhancer.setSuperclass( clazz );
153                    enhancer.setCallback( callback );
154                    enhancer.setClassLoader( digester.getClassLoader() );
155                    enhancer.setInterceptDuringConstruction( false );
156                    if ( hasDefaultConstructor )
157                    {
158                        result = enhancer.create();
159                    }
160                    else
161                    {
162                        result = enhancer.create( constructor.getParameterTypes(), constructorArguments );
163                    }
164                    factory = (Factory) result;
165                    return result;
166                }
167    
168                if ( hasDefaultConstructor )
169                {
170                    result = factory.newInstance( callback );
171                }
172                else
173                {
174                    result = factory.newInstance( constructor.getParameterTypes(),
175                        constructorArguments, new Callback[] { callback } );
176                }
177                return result;
178            }
179    
180            void finalize( Object proxy )
181                throws Exception
182            {
183                digester.popParams();
184                ( (DeferredConstructionCallback) ( (Factory) proxy ).getCallback( 0 ) ).establishDelegate();
185            }
186        }
187    
188        // ----------------------------------------------------------- Constructors
189    
190        /**
191         * Construct an object create rule with the specified class name.
192         *
193         * @param className Java class name of the object to be created
194         */
195        public ObjectCreateRule( String className )
196        {
197            this( className, (String) null );
198        }
199    
200        /**
201         * Construct an object create rule with the specified class.
202         *
203         * @param clazz Java class name of the object to be created
204         */
205        public ObjectCreateRule( Class<?> clazz )
206        {
207            this( clazz.getName(), (String) null );
208            this.clazz = clazz;
209        }
210    
211        /**
212         * Construct an object create rule with the specified class name and an optional attribute name containing an
213         * override.
214         *
215         * @param className Java class name of the object to be created
216         * @param attributeName Attribute name which, if present, contains an override of the class name to create
217         */
218        public ObjectCreateRule( String className, String attributeName )
219        {
220            this.className = className;
221            this.attributeName = attributeName;
222        }
223    
224        /**
225         * Construct an object create rule with the specified class and an optional attribute name containing an override.
226         *
227         * @param attributeName Attribute name which, if present, contains an
228         * @param clazz Java class name of the object to be created override of the class name to create
229         */
230        public ObjectCreateRule( String attributeName, Class<?> clazz )
231        {
232            this( clazz.getName(), attributeName );
233            this.clazz = clazz;
234        }
235    
236        // ----------------------------------------------------- Instance Variables
237    
238        /**
239         * The attribute containing an override class name if it is present.
240         */
241        protected String attributeName = null;
242    
243        /**
244         * The Java class of the object to be created.
245         */
246        protected Class<?> clazz = null;
247    
248        /**
249         * The Java class name of the object to be created.
250         */
251        protected String className = null;
252    
253        /**
254         * The constructor argument types.
255         *
256         * @since 3.2
257         */
258        private Class<?>[] constructorArgumentTypes;
259    
260        /**
261         * The explictly specified default constructor arguments which may be overridden by CallParamRules.
262         *
263         * @since 3.2
264         */
265        private Object[] defaultConstructorArguments;
266    
267        /**
268         * Helper object for managing proxies.
269         *
270         * @since 3.2
271         */
272        private ProxyManager proxyManager;
273    
274        // --------------------------------------------------------- Public Methods
275    
276        /**
277         * Allows users to specify constructor argument types.
278         *
279         * @param constructorArgumentTypes the constructor argument types
280         * @since 3.2
281         */
282        public void setConstructorArgumentTypes( Class<?>... constructorArgumentTypes )
283        {
284            if ( constructorArgumentTypes == null )
285            {
286                throw new IllegalArgumentException( "Parameter 'constructorArgumentTypes' must not be null" );
287            }
288    
289            this.constructorArgumentTypes = constructorArgumentTypes;
290        }
291    
292        /**
293         * Allows users to specify default constructor arguments.  If a default/no-arg constructor is not available
294         * for the target class, these arguments will be used to create the proxy object.  For any argument
295         * not supplied by a {@link CallParamRule}, the corresponding item from this array will be used
296         * to construct the final object as well.
297         *
298         * @param constructorArguments the default constructor arguments.
299         * @since 3.2
300         */
301        public void setDefaultConstructorArguments( Object... constructorArguments )
302        {
303            if ( constructorArguments == null )
304            {
305                throw new IllegalArgumentException( "Parameter 'constructorArguments' must not be null" );
306            }
307    
308            this.defaultConstructorArguments = constructorArguments;
309        }
310    
311        /**
312         * {@inheritDoc}
313         */
314        @Override
315        public void begin( String namespace, String name, Attributes attributes )
316            throws Exception
317        {
318            Class<?> clazz = this.clazz;
319    
320            if ( clazz == null )
321            {
322                // Identify the name of the class to instantiate
323                String realClassName = className;
324                if ( attributeName != null )
325                {
326                    String value = attributes.getValue( attributeName );
327                    if ( value != null )
328                    {
329                        realClassName = value;
330                    }
331                }
332                if ( getDigester().getLogger().isDebugEnabled() )
333                {
334                    getDigester().getLogger().debug( format( "[ObjectCreateRule]{%s} New '%s'",
335                                                             getDigester().getMatch(),
336                                                             realClassName ) );
337                }
338    
339                // Instantiate the new object and push it on the context stack
340                clazz = getDigester().getClassLoader().loadClass( realClassName );
341            }
342            Object instance;
343            if ( constructorArgumentTypes == null || constructorArgumentTypes.length == 0 )
344            {
345                if ( getDigester().getLogger().isDebugEnabled() )
346                {
347                    getDigester()
348                        .getLogger()
349                        .debug( format( "[ObjectCreateRule]{%s} New '%s' using default empty constructor",
350                                        getDigester().getMatch(),
351                                        clazz.getName() ) );
352                }
353    
354                instance = clazz.newInstance();
355            }
356            else
357            {
358                if ( proxyManager == null )
359                {
360                    Constructor<?> constructor = getAccessibleConstructor( clazz, constructorArgumentTypes );
361    
362                    if ( constructor == null )
363                    {
364                        throw new SAXException(
365                                       format( "[ObjectCreateRule]{%s} Class '%s' does not have a construcor with types %s",
366                                               getDigester().getMatch(),
367                                               clazz.getName(),
368                                               Arrays.toString( constructorArgumentTypes ) ) );
369                    }
370                    proxyManager = new ProxyManager( clazz, constructor, defaultConstructorArguments, getDigester() );
371                }
372                instance = proxyManager.createProxy();
373            }
374            getDigester().push( instance );
375        }
376    
377        /**
378         * {@inheritDoc}
379         */
380        @Override
381        public void end( String namespace, String name )
382            throws Exception
383        {
384            Object top = getDigester().pop();
385    
386            if ( proxyManager != null )
387            {
388                proxyManager.finalize( top );
389            }
390    
391            if ( getDigester().getLogger().isDebugEnabled() )
392            {
393                getDigester().getLogger().debug( format( "[ObjectCreateRule]{%s} Pop '%s'",
394                                                         getDigester().getMatch(),
395                                                         top.getClass().getName() ) );
396            }
397        }
398    
399        /**
400         * {@inheritDoc}
401         */
402        @Override
403        public String toString()
404        {
405            return format( "ObjectCreateRule[className=%s, attributeName=%s]", className, attributeName );
406        }
407    
408        private static void convertTo( Class<?>[] types, Object[] array )
409        {
410            if ( array.length != types.length )
411            {
412                throw new IllegalArgumentException();
413            }
414            // this piece of code is adapted from CallMethodRule
415            for ( int i = 0; i < array.length; i++ )
416            {
417                // convert nulls and convert stringy parameters for non-stringy param types
418                if ( array[i] == null
419                        || ( array[i] instanceof String && !String.class.isAssignableFrom( types[i] ) ) )
420                {
421                    array[i] = convert( (String) array[i], types[i] );
422                }
423            }
424        }
425    
426    }