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