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