001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */ 
017    package org.apache.commons.betwixt.io.read;
018    
019    import java.beans.IntrospectionException;
020    import java.lang.reflect.Constructor;
021    
022    import org.apache.commons.betwixt.ElementDescriptor;
023    import org.apache.commons.betwixt.XMLBeanInfo;
024    import org.apache.commons.logging.Log;
025    
026    /**  
027      * Group of factory methods for <code>ChainedBeanCreator</code>'s.
028      * The standard implementations used by Betwixt are present here.
029      *
030      * @author Robert Burrell Donkin
031      * @since 0.5
032      */
033    public class ChainedBeanCreatorFactory {
034        
035        private static final Class[] EMPTY_CLASS_ARRAY = {};
036        private static final Object[] EMPTY_OBJECT_ARRAY = {};
037        
038        /** Singleton instance for creating derived beans */
039        private static final ChainedBeanCreator derivedBeanCreator 
040            = new ChainedBeanCreator() {
041                public Object create(
042                                    ElementMapping elementMapping, 
043                                    ReadContext context, 
044                                    BeanCreationChain chain) {
045                                    
046                    Log log = context.getLog();
047                    String className 
048                        = elementMapping
049                            .getAttributes().getValue( context.getClassNameAttribute() );
050                    if ( className != null ) {
051                        try {
052                            // load the class we should instantiate
053                            ClassLoader classLoader = context.getClassLoader();
054                            Class clazz = null;
055                            if ( classLoader == null ) {
056                                log.warn("Read context classloader not set." );
057                            } else {
058                                try
059                                {
060                                    clazz = classLoader.loadClass( className );
061                                } catch (ClassNotFoundException e) {
062                                    log.info("Class not found in context classloader:");
063                                    log.debug(clazz, e);
064                                }
065                            }
066                            if (clazz == null) {
067                                clazz = Class.forName(className);
068                            }
069                            return newInstance(clazz, log);
070                                            
071                        } catch (Exception e) {
072                            // it would be nice to have a pluggable strategy for exception management
073                            log.warn( "Could not create instance of type: " + className );
074                            log.debug( "Create new instance failed: ", e );
075                            return null;
076                        }
077                        
078                    } else {
079                        // pass responsibility down the chain
080                        return chain.create( elementMapping, context );
081                    }
082                }
083            };
084        
085        /**
086          * Creates a <code>ChainedBeanCreator</code> that constructs derived beans.
087          * These have their classname set by an xml attribute.
088          * @return <code>ChainedBeanCreator</code> that implements Derived beans logic, not null
089          */
090        public static final ChainedBeanCreator createDerivedBeanCreator() {
091            return derivedBeanCreator;
092        }
093        
094        /**
095         * Constructs a new instance of the given class.
096         * Access is forced.
097         * @param theClass <code>Class</code>, not null
098         * @param log <code>Log</code>, not null
099         * @return <code>Object</code>, an instance of the given class
100         * @throws Exception
101         */
102        private static final Object newInstance(Class theClass, Log log) throws Exception {
103            Object result = null;
104            try {
105                Constructor constructor = theClass.getConstructor(EMPTY_CLASS_ARRAY);
106                if (!constructor.isAccessible()) {
107                    constructor.setAccessible(true);
108                }
109                result = constructor.newInstance(EMPTY_OBJECT_ARRAY);
110            } catch (SecurityException e) {
111                log.debug("Cannot force accessibility to constructor", e);
112            
113            } catch (NoSuchMethodException e) {
114                if (log.isDebugEnabled()) { 
115                    log.debug("Class " + theClass + " has no empty constructor.");
116                }
117            }
118            
119            if (result == null) {
120                result = theClass.newInstance();
121            }
122            return result; 
123        }
124        
125        /** Singleton instance that creates beans based on type */
126        private static final ChainedBeanCreator elementTypeBeanCreator 
127            = new ChainedBeanCreator() {
128                public Object create(
129                                    ElementMapping element, 
130                                    ReadContext context, 
131                                    BeanCreationChain chain) {
132                    
133                    Log log = context.getLog();
134                    Class theClass = null;
135                    
136                    ElementDescriptor descriptor = element.getDescriptor();
137                    if ( descriptor != null ) {
138                        // check for polymorphism 
139                        theClass = context.resolvePolymorphicType(element);
140                        
141                        if (theClass == null)
142                        {
143                            // created based on implementation class
144                            theClass = descriptor.getImplementationClass();
145                        }
146                    }
147                    
148                    if ( theClass == null ) {
149                        // create based on type
150                        theClass = element.getType();
151                    }
152                    
153                    if (descriptor != null && descriptor.isPolymorphic()) {
154                        // check that the type is suitably named
155                        try {
156                            XMLBeanInfo xmlBeanInfo = context.getXMLIntrospector().introspect(theClass);
157                            String namespace = element.getNamespace();
158                            String name = element.getName();
159                            if (namespace == null) {
160                                if (!name.equals(xmlBeanInfo.getElementDescriptor().getQualifiedName())) {
161                                    context.getLog().debug("Polymorphic type does not match element");
162                                    return null;
163                                }
164                            } else if (!namespace.equals(xmlBeanInfo.getElementDescriptor().getURI())
165                                    || !name.equals(xmlBeanInfo.getElementDescriptor().getLocalName())) {
166                                context.getLog().debug("Polymorphic type does not match element");
167                                return null;
168                            }
169                        } catch (IntrospectionException e) {
170                            context.getLog().warn( 
171                                "Could not introspect type to test introspection: " + theClass.getName() );
172                            context.getLog().debug( "Introspection failed: ", e );
173                            return null;
174                        }
175                        
176                    }
177                    
178                    if ( log.isTraceEnabled() ) {
179                        log.trace(
180                            "Creating instance of class " + theClass.getName() 
181                            + " for element " + element.getName());
182                    }
183                    
184                    try {
185    
186                        Object result = newInstance(theClass, log);
187                        return result;
188                        
189                    } catch (Exception e) {
190                        // it would be nice to have a pluggable strategy for exception management
191                        context.getLog().warn( 
192                            "Could not create instance of type: " + theClass.getName() );
193                        context.getLog().debug( "Create new instance failed: ", e );
194                        return null;
195                    }
196                }
197            }; 
198        
199        /**
200          * Creates a <code>ChainedBeanCreator</code> that constructs beans based on element type.
201          * @return <code>ChainedBeanCreator</code> that implements load by type beans logic, not null
202          */
203        public static final ChainedBeanCreator createElementTypeBeanCreator() {
204            return elementTypeBeanCreator;
205        }
206        
207        /** Singleton instance that creates beans based on IDREF */
208        private static final ChainedBeanCreator idRefBeanCreator 
209            = new ChainedBeanCreator() {
210                public Object create(
211                                    ElementMapping elementMapping, 
212                                    ReadContext context, 
213                                    BeanCreationChain chain) {
214                    if ( context.getMapIDs() ) {
215                        String idref = elementMapping.getAttributes().getValue( "idref" );
216                        if ( idref != null ) {
217                            // XXX need to check up about ordering
218                            // XXX this is a very simple system that assumes that 
219                            // XXX id occurs before idrefs
220                            // XXX would need some thought about how to implement a fuller system
221                            context.getLog().trace( "Found IDREF" );
222                            Object bean = context.getBean( idref );
223                            if ( bean != null ) {
224                                if ( context.getLog().isTraceEnabled() ) {
225                                    context.getLog().trace( "Matched bean " + bean );
226                                }
227                                return bean;
228                            }
229                            context.getLog().trace( "No match found" );
230                        }
231                    }
232                    return chain.create( elementMapping, context );
233                }
234            }; 
235        
236        /**
237          * Creates a <code>ChainedBeanCreator</code> that finds existing beans based on their IDREF.
238          * @return <code>ChainedBeanCreator</code> that implements IDREF beans logic, not null
239          */
240        public static final ChainedBeanCreator createIDREFBeanCreator() {
241            return idRefBeanCreator;
242        }
243    }