View Javadoc
1   /*
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Intalio, Inc.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Intalio, Inc. Exolab is a registered
23   *    trademark of Intalio, Inc.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
42   *
43   * This file was originally developed by Keith Visco during the
44   * course of employment at Intalio Inc.
45   * All portions of this file developed by Keith Visco after Jan 19 2005 are
46   * Copyright (C) 2005 Keith Visco. All Rights Reserved.
47   *
48   * $Id$
49   */
50  package org.exolab.castor.xml.util;
51  
52  import java.util.ArrayList;
53  import java.util.HashMap;
54  import java.util.Iterator;
55  import java.util.List;
56  import java.util.Map;
57  import java.util.concurrent.locks.ReentrantReadWriteLock;
58  
59  import org.apache.commons.logging.Log;
60  import org.apache.commons.logging.LogFactory;
61  import org.castor.xml.InternalContext;
62  import org.exolab.castor.mapping.ClassDescriptor;
63  import org.exolab.castor.mapping.MappingLoader;
64  import org.exolab.castor.xml.Introspector;
65  import org.exolab.castor.xml.ResolverException;
66  import org.exolab.castor.xml.XMLClassDescriptor;
67  import org.exolab.castor.xml.XMLClassDescriptorResolver;
68  import org.exolab.castor.xml.util.resolvers.ResolveHelpers;
69  
70  /**
71   * The default implementation of the ClassDescriptorResolver interface.
72   *
73   * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
74   * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
75   */
76  public class XMLClassDescriptorResolverImpl implements XMLClassDescriptorResolver {
77      /** 
78       * The Logger instance to use.
79       */
80      private static final Log LOG = LogFactory.getLog(XMLClassDescriptorResolverImpl.class);
81  
82      /**
83       * All resolved descriptors are kept here.
84       */
85      private DescriptorCacheImpl _descriptorCache;
86      /**
87       * The MappingLoader instance to read descriptors from.
88       */
89      private MappingLoader _mappingLoader;
90      /**
91       * The domain class loader to use.
92       */
93      private ClassLoader _classLoader;
94      /**
95       * A flag to signal if introspection should be used or not.
96       */
97      private Boolean _useIntrospector;
98      /**
99       * A flag to signal if descriptors should be determines via package
100      * file .castor.cdr .
101      */
102     private Boolean _loadPackageMappings;
103     /**
104      * The introspector to use.
105      */
106     private Introspector _introspector;
107     /**
108      * The place where all resolving strategies and their commands put the results into
109      * and can be read from.
110      */
111     private ResolverStrategy _resolverStrategy;
112 
113     /**
114      * Creates a new ClassDescriptorResolverImpl.
115      * It is left empty to avoid cycles at construction. To guarantee
116      * backward compatibility the backwardInit method will do all
117      * required initialization if it hadn't happened before.
118      */
119     public XMLClassDescriptorResolverImpl() {
120         super();
121         _descriptorCache = new DescriptorCacheImpl();
122     }
123 
124     /**
125      * {@inheritDoc}
126      * The InternalContext itself is not stored! But all values of interest are read
127      * and stored in local attributes.
128      */
129     public void setInternalContext(final InternalContext internalContext) {
130         _mappingLoader = internalContext.getMappingLoader();
131         _classLoader = internalContext.getClassLoader();
132         _useIntrospector = internalContext.getUseIntrospector();
133         _loadPackageMappings = internalContext.getLoadPackageMapping();
134         _introspector = internalContext.getIntrospector();
135         _resolverStrategy = internalContext.getResolverStrategy();
136     }
137 
138     /**
139      * {@inheritDoc}
140      */
141     public MappingLoader getMappingLoader() {
142         return _mappingLoader;
143     }
144     
145     /**
146      * {@inheritDoc}
147      */
148     public void setClassLoader(final ClassLoader loader) {
149         _classLoader = loader;
150     }
151 
152     /**
153      * {@inheritDoc}
154      */
155     public void setUseIntrospection(final boolean enable) {
156         _useIntrospector = Boolean.valueOf(enable);
157     }
158     
159     /**
160      * {@inheritDoc}
161      */
162     public void setLoadPackageMappings(final boolean loadPackageMappings) {
163         _loadPackageMappings = Boolean.valueOf(loadPackageMappings);
164     }
165     
166     /**
167      * {@inheritDoc}
168      */
169     public void setMappingLoader(final MappingLoader mappingLoader) {
170         _mappingLoader = mappingLoader;
171         if (mappingLoader != null) {
172             for (ClassDescriptor classDescriptor : mappingLoader.getDescriptors()) {
173                 _descriptorCache.addDescriptor(classDescriptor.getJavaClass().getName(), 
174                         (XMLClassDescriptor) classDescriptor);
175             }
176         }
177     }
178     
179     /**
180      * {@inheritDoc}
181      */
182     public void setIntrospector(final Introspector introspector) {
183         _introspector = introspector;
184     }
185 
186     /**
187      * {@inheritDoc}
188      */
189     public void setResolverStrategy(final ResolverStrategy resolverStrategy) {
190         _resolverStrategy = resolverStrategy;
191     }
192     
193     /**
194      * XMLClassDescriptorResolver was originally build to collect all required
195      * information by itself... now with introduction of XMLContext and a more
196      * IoC like concepts that all information is injected into a class... things
197      * are different but this methods is there to guarantee backward 
198      * compatibility.
199      * @return the {@link ResolverStrategy} to use
200      */
201     private ResolverStrategy getResolverStrategy() {
202         setAttributesIntoStrategy();
203         return _resolverStrategy;
204     }
205     
206     /**
207      * {@inheritDoc}
208      */
209     public ClassDescriptor resolve(final Class<?> type) throws ResolverException {
210         if (type == null) {
211             String message = "Type argument must not be null for resolve";
212             LOG.warn(message);
213             throw new IllegalArgumentException(message);
214         }
215         
216         if (_descriptorCache.isMissingDescriptor(type.getName())) {
217             if (LOG.isTraceEnabled()) {
218                 LOG.trace("Descriptor for " + type.getName() + " already marked as *MISSING*.");
219             }
220             return null;
221         }
222         
223         if (_descriptorCache.getDescriptor(type.getName()) != null) {
224             return _descriptorCache.getDescriptor(type.getName());
225         }
226         
227         ClassLoader l = _classLoader;
228         if (l == null) { l = type.getClassLoader(); }
229         if (l == null) { l = Thread.currentThread().getContextClassLoader(); }
230         
231         return this.resolve(type.getName(), l);
232     } // -- resolve(Class)
233 
234     /**
235      * {@inheritDoc}
236      */
237     public XMLClassDescriptor resolve(final String className) throws ResolverException {
238         if (className == null || className.length() == 0) {
239             String message = "Cannot resolve a null or zero-length class name.";
240             LOG.warn(message);
241             throw new IllegalArgumentException(message);
242         }
243         
244         if (_descriptorCache.isMissingDescriptor(className)) {
245             if (LOG.isTraceEnabled()) {
246                 LOG.trace("Descriptor for " + className + " already marked as *MISSING*.");
247             }
248             return null;
249         }
250         
251         if (_descriptorCache.getDescriptor(className) != null) {
252             return _descriptorCache.getDescriptor(className);
253         }
254         
255         ClassLoader l = _classLoader;
256         if (l == null) { l = Thread.currentThread().getContextClassLoader(); }
257         
258         return this.resolve(className, l);
259     }
260 
261     /**
262      * {@inheritDoc}
263      */
264     public XMLClassDescriptor resolve(final String className, final ClassLoader loader)
265     throws ResolverException {
266         if (className == null || className.length() == 0) {
267             String message = "Cannot resolve a null or zero-length class name.";
268             LOG.warn(message);
269             throw new IllegalArgumentException(message);
270         }
271 
272         if (_descriptorCache.isMissingDescriptor(className)) {
273             if (LOG.isTraceEnabled()) {
274                 LOG.trace("Descriptor for " + className + " already marked as *MISSING*.");
275             }
276             return null;
277         }
278 
279         if (_descriptorCache.getDescriptor(className) != null) {
280             return _descriptorCache.getDescriptor(className);
281         }
282         
283         ClassLoader l = loader;
284         if (l == null) { l = _classLoader; }
285         if (l == null) { l = Thread.currentThread().getContextClassLoader(); }
286         
287         getResolverStrategy().setProperty(ResolverStrategy.PROPERTY_CLASS_LOADER, l);
288         return (XMLClassDescriptor) getResolverStrategy().resolveClass(_descriptorCache, className);
289     } //-- resolve(String, ClassLoader)
290 
291     /**
292      * {@inheritDoc}
293      */
294     public XMLClassDescriptor resolveByXMLName(final String xmlName, final String namespaceURI,
295             final ClassLoader loader) {
296         
297         if (xmlName == null || xmlName.length() == 0) {
298             String message = "Cannot resolve a null or zero-length class name.";
299             LOG.warn(message);
300             throw new IllegalArgumentException(message);
301         }
302         
303         // @TODO Joachim 2007-05-05 the class loader is NOT used!
304         // get a list of all descriptors with the correct xmlName, regardless of their namespace
305         List<ClassDescriptor> possibleMatches = _descriptorCache.getDescriptors(xmlName);
306         if (possibleMatches.size() == 0) {
307             // nothing matches that XML name
308             return null;
309         }
310         if (possibleMatches.size() == 1) {
311             // we have exactly one possible match - that's our result
312             // (if it has the right namespace, it's an exact match, if not its
313             // the only possible match)
314             return (XMLClassDescriptor) possibleMatches.get(0);
315         }
316 
317         // we have more than one result - only an exact match can be the result
318         for (Iterator<ClassDescriptor> i = possibleMatches.iterator(); i.hasNext(); ) {
319             XMLClassDescriptor descriptor = (XMLClassDescriptor) i.next();
320 
321             if (ResolveHelpers.namespaceEquals(namespaceURI, descriptor.getNameSpaceURI())) {
322                 return descriptor;
323             }
324         }
325 
326         // no exact match and too many possible matches...
327         return null;
328     } //-- resolveByXMLName
329 
330     /**
331      * {@inheritDoc}
332      */
333     public Iterator<ClassDescriptor> resolveAllByXMLName(final String xmlName, 
334             final String namespaceURI, final ClassLoader loader) {
335         
336         if (xmlName == null || xmlName.length() == 0) {
337             String message = "Cannot resolve a null or zero-length xml name.";
338             LOG.warn(message);
339             throw new IllegalArgumentException(message);
340         }
341 
342         // get all descriptors with the matching xml name
343         return _descriptorCache.getDescriptors(xmlName).iterator();
344     } //-- resolveAllByXMLName
345 
346     /**
347      * {@inheritDoc}
348      */
349     public void addClass(final String className) throws ResolverException {
350         this.resolve(className);
351     }
352 
353     /**
354      * {@inheritDoc}
355      */
356     public void addClasses(final String[] classNames) throws ResolverException {
357         for (int i = 0; i < classNames.length; i++) {
358             String className = classNames[i];
359             this.addClass(className);
360         }
361     }
362 
363     /**
364      * {@inheritDoc}
365      */
366     public void addClass(final Class<?> clazz) throws ResolverException {
367         this.resolve(clazz);
368     }
369 
370     /**
371      * {@inheritDoc}
372      */
373     public void addClasses(final Class<?>[] clazzes) throws ResolverException {
374         for (int i = 0; i < clazzes.length; i++) {
375             Class<?> clazz = clazzes[i];
376             this.addClass(clazz);
377         }
378     }
379 
380     /**
381      * {@inheritDoc}
382      */
383     public void addPackage(final String packageName) throws ResolverException {
384         if (packageName == null || packageName.length() == 0) {
385             String message = "Cannot resolve a null or zero-length package name.";
386             LOG.warn(message);
387             throw new IllegalArgumentException(message);
388         }
389         
390         getResolverStrategy().resolvePackage(_descriptorCache, packageName);
391     }
392 
393     /**
394      * {@inheritDoc}
395      */
396     public void addPackages(final String[] packageNames) throws ResolverException {
397         for (int i = 0; i < packageNames.length; i++) {
398             String packageName = packageNames[i];
399             this.addPackage(packageName);
400         }
401     }
402        
403     /**
404      * {@inheritDoc}
405      * @deprecated
406      */
407     public void loadClassDescriptors(final String packageName) throws ResolverException {
408         String message = "Already deprecated in the interface!";
409         LOG.warn(message);
410         throw new UnsupportedOperationException();
411     }
412     
413     /**
414      * To set all strategy properties to the values of the attributes of this instance.
415      * Only exception is the class loader property which is always set in the resolve method.
416      */
417     private void setAttributesIntoStrategy() {
418         ResolverStrategy strategy = _resolverStrategy;
419         strategy.setProperty(
420                 ResolverStrategy.PROPERTY_LOAD_PACKAGE_MAPPINGS, 
421                 _loadPackageMappings);
422         strategy.setProperty(
423                 ResolverStrategy.PROPERTY_USE_INTROSPECTION, _useIntrospector);
424         strategy.setProperty(
425                 ResolverStrategy.PROPERTY_MAPPING_LOADER, _mappingLoader);
426         strategy.setProperty(
427                 ResolverStrategy.PROPERTY_INTROSPECTOR, _introspector);
428     }
429 
430     /**
431      * Internal cache for XMLClassDescriptors.<br>
432      * <br>
433      * The cache maintains all descriptors loaded by its
434      * <code>XMLClassDescriptorResolver</code>. It also keeps track of
435      * mapping files and CDR lists that have been loaded. Just like the
436      * ClassCache it also has a list of missing descriptors to avoid trying to
437      * load those descriptors again.
438      * 
439      * The cached descriptors are available via the name of the classes they
440      * describe or via their XML name from a mapping file.
441      * 
442      * @author <a href="mailto:stevendolg AT gxm DOT at">Steven Dolg</a>
443      */
444     private static class DescriptorCacheImpl implements ResolverStrategy.ResolverResults {
445         /** Logger to be used by DescriptorCache. */
446         private static final Log LOG2 = LogFactory.getLog(DescriptorCacheImpl.class);
447         /** Some fixed text to detect errors... */
448         private static final String INTERNAL_CONTAINER_NAME = "-error-if-this-is-used-";
449 
450         /** List of class names a descriptor is not available for. */
451         private final List<String> _missingTypes;
452 
453         /** Map of cached descriptors with the class names they describe as key. */
454         private final Map<String, ClassDescriptor> _typeMap;
455 
456         /** Map of cached descriptors with their XML names as key. */
457         private final Map<String, List<ClassDescriptor>> _xmlNameMap;
458 
459         /** Lock used to isolate write accesses to the caches internal lists and maps. */
460         private final ReentrantReadWriteLock _lock;
461 
462         /**
463          * Default constructor.<br>
464          * <br>
465          * Initializes all lists and maps.
466          */
467         public DescriptorCacheImpl() {
468             super();
469             
470             LOG2.debug("New instance!");
471             
472             _typeMap = new HashMap<String, ClassDescriptor>();
473             _xmlNameMap = new HashMap<String, List<ClassDescriptor>>();
474             _missingTypes = new ArrayList<String>();
475             _lock = new ReentrantReadWriteLock();
476         } //--- DescriptorCacheImpl
477 
478         /**
479          * Adds a descriptor to this caches maps.<br>
480          * The descriptor is mapped both with the class name and its XML name.
481          * 
482          * The descriptor will not be mapped with its XML name is
483          * <code>null</code>, the empty string (""), or has the value of the
484          * constant INTERNAL_CONTAINER_NAME.
485          * 
486          * If there already is a descriptor for the given <code>className</code>
487          * and/or the descriptor's XML name the previously cached descriptor is
488          * replaced.
489          * 
490          * @param className The class name to be used for mapping the given descriptor.
491          * @param descriptor The descriptor to be mapped.
492          * @throws InterruptedException 
493          * 
494          * @see #INTERNAL_CONTAINER_NAME
495          */
496         public void addDescriptor(final String className, final XMLClassDescriptor descriptor) {
497             if ((className == null) || (className.length() == 0)) {
498                 String message = "Class name to insert ClassDescriptor must not be null";
499                 LOG2.warn(message);
500                 throw new IllegalArgumentException(message);
501             }
502 
503             // acquire write lock first
504             _lock.writeLock().lock();
505             try {
506 
507                 if (descriptor == null) {
508                     if (LOG2.isDebugEnabled()) {
509                         LOG2.debug("Adding class name to missing classes: " + className);
510                     }
511                     _missingTypes.add(className);
512                     return;
513                 }
514                 
515                 if (LOG2.isDebugEnabled()) {
516                     LOG2.debug("Adding descriptor class for: " 
517                             + className + " descriptor: " + descriptor);
518                 }
519                 _typeMap.put(className, descriptor);
520     
521                 String xmlName = descriptor.getXMLName();
522                 // ignore descriptors with an empty XMLName
523                 if (xmlName == null || xmlName.length() == 0) {
524                     return;
525                 }
526     
527                 // ignore descriptors with the internal XMLName
528                 if (INTERNAL_CONTAINER_NAME.equals(xmlName)) {
529                     return;
530                 }
531     
532                 // add new descriptor to the list for the corresponding XML name  
533                 List<ClassDescriptor> descriptorList = _xmlNameMap.get(xmlName);
534                 if (descriptorList == null) {
535                     descriptorList = new ArrayList<ClassDescriptor>();
536                     _xmlNameMap.put(xmlName, descriptorList);
537                 }
538                 if (!descriptorList.contains(descriptor)) {
539                     descriptorList.add(descriptor);
540                 }
541                 
542                 _missingTypes.remove(className);
543             } finally {
544                 _lock.writeLock().unlock();
545             }
546         } //-- addDescriptor
547 
548         /**
549          * Gets the descriptor that is mapped to the given class name.
550          * 
551          * @param className The class name to get a descriptor for.
552          * @return The descriptor mapped to the given name or <code>null</code>
553          *         if no descriptor is stored in this cache.
554          */
555         public XMLClassDescriptor getDescriptor(final String className) {
556 
557             // acquire read lock which is released via finally block
558             _lock.readLock().lock();
559             try {
560 
561                 if ((className == null) 
562                         || ("".equals(className)) 
563                         || (_missingTypes.contains(className))) {
564                     return null;
565                 }
566                 
567                 XMLClassDescriptor ret = (XMLClassDescriptor) _typeMap.get(className);
568                 if (LOG2.isDebugEnabled()) {
569                     LOG2.debug("Get descriptor for: " + className + " found: " + ret);
570                 }
571                 return ret;
572             } finally {
573                 _lock.readLock().unlock();
574             }
575         } //-- getDescriptor
576 
577         /**
578          * Gets a list of descriptors that have the given XML name.<br>
579          * <br>
580          * This method will return all previously cached descriptors with the
581          * given XML name regardless of their name space.
582          * 
583          * @param xmlName The XML name of the descriptors to get.
584          * @return A list of descriptors with the given XML name or an empty
585          *         list if no such descriptor is stored in this cache. This
586          *         method will never return <code>null</code>!
587          */
588         public List<ClassDescriptor> getDescriptors(final String xmlName) {
589 
590             // before accessing XML name map acquire read lock first
591             _lock.readLock().lock();
592             List<ClassDescriptor> list = _xmlNameMap.get(xmlName);
593             _lock.readLock().unlock();
594 
595             if (list == null) {
596 
597                 // return an empty list
598                 list = new ArrayList<ClassDescriptor>();
599             } else {
600 
601                 // return a copy of the original list
602                 list = new ArrayList<ClassDescriptor>(list);
603             }
604             return list;
605         } //-- getDescriptorList
606 
607         /**
608          * Checks whether the given class name is contained in the list of class
609          * names the descriptor is found to be missing.<br>
610          * <br>
611          * NOTE: This does not check whether a descriptor is stored within this
612          * cache or not. This rather checks the list of class names the
613          * XMLClassDescriptorResolverImpl found to have no descriptor. The cache
614          * itself has no means of determining that this is the case and thus
615          * will never add/remove class names to/from it.
616          * 
617          * @param className The class name to be checked.
618          * @return <code>true</code> If the given class name was stated to
619          *         have no descriptor by a previous call to
620          *         <code>addMissingDescriptor</code> with exactly the same
621          *         class name. <code>false</code> otherwise.
622          * 
623          * @see #addMissingDescriptor(String)
624          */
625         public boolean isMissingDescriptor(final String className) {
626 
627             // before accessing list with missing types acquire read lock first
628             _lock.readLock().lock();
629             try {
630                 return _missingTypes.contains(className);
631             } finally {
632                 _lock.readLock().unlock();
633             }
634         } //-- isMissingDescriptor
635 
636         /**
637          * To add not only a single descriptor but a map of descriptors at once.
638          * 
639          * @param descriptors a Map of className (String) and XMLClassDescriptor pairs
640          */
641         public void addAllDescriptors(final Map descriptors) {
642             if ((descriptors == null) || (descriptors.isEmpty())) {
643                 LOG2.debug("Called addAllDescriptors with null or empty descriptor map");
644                 return;
645             }
646             
647             for (Iterator<String> iter = descriptors.keySet().iterator(); iter.hasNext(); ) {
648                 String clsName = iter.next();
649                 this.addDescriptor(clsName, (XMLClassDescriptor) descriptors.get(clsName));
650             }
651         }
652     } // -- DescriptorCacheImpl
653     
654     /**
655      * Cleans the descriptor cache.
656      * @see org.exolab.castor.xml.XMLClassDescriptorResolver#cleanDescriptorCache()
657      */
658     public void cleanDescriptorCache() {
659         _descriptorCache = new DescriptorCacheImpl();
660     }
661 } // -- ClassDescriptorResolverImpl