/* * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved. * * This file was originally developed by Keith Visco during the * course of employment at Intalio Inc. * All portions of this file developed by Keith Visco after Jan 19 2005 are * Copyright (C) 2005 Keith Visco. All Rights Reserved. * * $Id$ */ package org.exolab.castor.xml.util; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.xml.InternalContext; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.MappingLoader; import org.exolab.castor.xml.Introspector; import org.exolab.castor.xml.ResolverException; import org.exolab.castor.xml.XMLClassDescriptor; import org.exolab.castor.xml.XMLClassDescriptorResolver; import org.exolab.castor.xml.util.resolvers.ResolveHelpers; /** * The default implementation of the ClassDescriptorResolver interface. * * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a> * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ */ public class XMLClassDescriptorResolverImpl implements XMLClassDescriptorResolver { /** * The Logger instance to use. */ private static final Log LOG = LogFactory.getLog(XMLClassDescriptorResolverImpl.class); /** * All resolved descriptors are kept here. */ private DescriptorCacheImpl _descriptorCache; /** * The MappingLoader instance to read descriptors from. */ private MappingLoader _mappingLoader; /** * The domain class loader to use. */ private ClassLoader _classLoader; /** * A flag to signal if introspection should be used or not. */ private Boolean _useIntrospector; /** * A flag to signal if descriptors should be determines via package * file .castor.cdr . */ private Boolean _loadPackageMappings; /** * The introspector to use. */ private Introspector _introspector; /** * The place where all resolving strategies and their commands put the results into * and can be read from. */ private ResolverStrategy _resolverStrategy; /** * Creates a new ClassDescriptorResolverImpl. * It is left empty to avoid cycles at construction. To guarantee * backward compatibility the backwardInit method will do all * required initialization if it hadn't happened before. */ public XMLClassDescriptorResolverImpl() { super(); _descriptorCache = new DescriptorCacheImpl(); } /** * {@inheritDoc} * The InternalContext itself is not stored! But all values of interest are read * and stored in local attributes. */ public void setInternalContext(final InternalContext internalContext) { _mappingLoader = internalContext.getMappingLoader(); _classLoader = internalContext.getClassLoader(); _useIntrospector = internalContext.getUseIntrospector(); _loadPackageMappings = internalContext.getLoadPackageMapping(); _introspector = internalContext.getIntrospector(); _resolverStrategy = internalContext.getResolverStrategy(); } /** * {@inheritDoc} */ public MappingLoader getMappingLoader() { return _mappingLoader; } /** * {@inheritDoc} */ public void setClassLoader(final ClassLoader loader) { _classLoader = loader; } /** * {@inheritDoc} */ public void setUseIntrospection(final boolean enable) { _useIntrospector = Boolean.valueOf(enable); } /** * {@inheritDoc} */ public void setLoadPackageMappings(final boolean loadPackageMappings) { _loadPackageMappings = Boolean.valueOf(loadPackageMappings); } /** * {@inheritDoc} */ public void setMappingLoader(final MappingLoader mappingLoader) { _mappingLoader = mappingLoader; if (mappingLoader != null) { for (ClassDescriptor classDescriptor : mappingLoader.getDescriptors()) { _descriptorCache.addDescriptor(classDescriptor.getJavaClass().getName(), (XMLClassDescriptor) classDescriptor); } } } /** * {@inheritDoc} */ public void setIntrospector(final Introspector introspector) { _introspector = introspector; } /** * {@inheritDoc} */ public void setResolverStrategy(final ResolverStrategy resolverStrategy) { _resolverStrategy = resolverStrategy; } /** * XMLClassDescriptorResolver was originally build to collect all required * information by itself... now with introduction of XMLContext and a more * IoC like concepts that all information is injected into a class... things * are different but this methods is there to guarantee backward * compatibility. * @return the {@link ResolverStrategy} to use */ private ResolverStrategy getResolverStrategy() { setAttributesIntoStrategy(); return _resolverStrategy; } /** * {@inheritDoc} */ public ClassDescriptor resolve(final Class<?> type) throws ResolverException { if (type == null) { String message = "Type argument must not be null for resolve"; LOG.warn(message); throw new IllegalArgumentException(message); } if (_descriptorCache.isMissingDescriptor(type.getName())) { if (LOG.isTraceEnabled()) { LOG.trace("Descriptor for " + type.getName() + " already marked as *MISSING*."); } return null; } if (_descriptorCache.getDescriptor(type.getName()) != null) { return _descriptorCache.getDescriptor(type.getName()); } ClassLoader l = _classLoader; if (l == null) { l = type.getClassLoader(); } if (l == null) { l = Thread.currentThread().getContextClassLoader(); } return this.resolve(type.getName(), l); } // -- resolve(Class) /** * {@inheritDoc} */ public XMLClassDescriptor resolve(final String className) throws ResolverException { if (className == null || className.length() == 0) { String message = "Cannot resolve a null or zero-length class name."; LOG.warn(message); throw new IllegalArgumentException(message); } if (_descriptorCache.isMissingDescriptor(className)) { if (LOG.isTraceEnabled()) { LOG.trace("Descriptor for " + className + " already marked as *MISSING*."); } return null; } if (_descriptorCache.getDescriptor(className) != null) { return _descriptorCache.getDescriptor(className); } ClassLoader l = _classLoader; if (l == null) { l = Thread.currentThread().getContextClassLoader(); } return this.resolve(className, l); } /** * {@inheritDoc} */ public XMLClassDescriptor resolve(final String className, final ClassLoader loader) throws ResolverException { if (className == null || className.length() == 0) { String message = "Cannot resolve a null or zero-length class name."; LOG.warn(message); throw new IllegalArgumentException(message); } if (_descriptorCache.isMissingDescriptor(className)) { if (LOG.isTraceEnabled()) { LOG.trace("Descriptor for " + className + " already marked as *MISSING*."); } return null; } if (_descriptorCache.getDescriptor(className) != null) { return _descriptorCache.getDescriptor(className); } ClassLoader l = loader; if (l == null) { l = _classLoader; } if (l == null) { l = Thread.currentThread().getContextClassLoader(); } getResolverStrategy().setProperty(ResolverStrategy.PROPERTY_CLASS_LOADER, l); return (XMLClassDescriptor) getResolverStrategy().resolveClass(_descriptorCache, className); } //-- resolve(String, ClassLoader) /** * {@inheritDoc} */ public XMLClassDescriptor resolveByXMLName(final String xmlName, final String namespaceURI, final ClassLoader loader) { if (xmlName == null || xmlName.length() == 0) { String message = "Cannot resolve a null or zero-length class name."; LOG.warn(message); throw new IllegalArgumentException(message); } // @TODO Joachim 2007-05-05 the class loader is NOT used! // get a list of all descriptors with the correct xmlName, regardless of their namespace List<ClassDescriptor> possibleMatches = _descriptorCache.getDescriptors(xmlName); if (possibleMatches.size() == 0) { // nothing matches that XML name return null; } if (possibleMatches.size() == 1) { // we have exactly one possible match - that's our result // (if it has the right namespace, it's an exact match, if not its // the only possible match) return (XMLClassDescriptor) possibleMatches.get(0); } // we have more than one result - only an exact match can be the result for (Iterator<ClassDescriptor> i = possibleMatches.iterator(); i.hasNext(); ) { XMLClassDescriptor descriptor = (XMLClassDescriptor) i.next(); if (ResolveHelpers.namespaceEquals(namespaceURI, descriptor.getNameSpaceURI())) { return descriptor; } } // no exact match and too many possible matches... return null; } //-- resolveByXMLName /** * {@inheritDoc} */ public Iterator<ClassDescriptor> resolveAllByXMLName(final String xmlName, final String namespaceURI, final ClassLoader loader) { if (xmlName == null || xmlName.length() == 0) { String message = "Cannot resolve a null or zero-length xml name."; LOG.warn(message); throw new IllegalArgumentException(message); } // get all descriptors with the matching xml name return _descriptorCache.getDescriptors(xmlName).iterator(); } //-- resolveAllByXMLName /** * {@inheritDoc} */ public void addClass(final String className) throws ResolverException { this.resolve(className); } /** * {@inheritDoc} */ public void addClasses(final String[] classNames) throws ResolverException { for (int i = 0; i < classNames.length; i++) { String className = classNames[i]; this.addClass(className); } } /** * {@inheritDoc} */ public void addClass(final Class<?> clazz) throws ResolverException { this.resolve(clazz); } /** * {@inheritDoc} */ public void addClasses(final Class<?>[] clazzes) throws ResolverException { for (int i = 0; i < clazzes.length; i++) { Class<?> clazz = clazzes[i]; this.addClass(clazz); } } /** * {@inheritDoc} */ public void addPackage(final String packageName) throws ResolverException { if (packageName == null || packageName.length() == 0) { String message = "Cannot resolve a null or zero-length package name."; LOG.warn(message); throw new IllegalArgumentException(message); } getResolverStrategy().resolvePackage(_descriptorCache, packageName); } /** * {@inheritDoc} */ public void addPackages(final String[] packageNames) throws ResolverException { for (int i = 0; i < packageNames.length; i++) { String packageName = packageNames[i]; this.addPackage(packageName); } } /** * {@inheritDoc} * @deprecated */ public void loadClassDescriptors(final String packageName) throws ResolverException { String message = "Already deprecated in the interface!"; LOG.warn(message); throw new UnsupportedOperationException(); } /** * To set all strategy properties to the values of the attributes of this instance. * Only exception is the class loader property which is always set in the resolve method. */ private void setAttributesIntoStrategy() { ResolverStrategy strategy = _resolverStrategy; strategy.setProperty( ResolverStrategy.PROPERTY_LOAD_PACKAGE_MAPPINGS, _loadPackageMappings); strategy.setProperty( ResolverStrategy.PROPERTY_USE_INTROSPECTION, _useIntrospector); strategy.setProperty( ResolverStrategy.PROPERTY_MAPPING_LOADER, _mappingLoader); strategy.setProperty( ResolverStrategy.PROPERTY_INTROSPECTOR, _introspector); } /** * Internal cache for XMLClassDescriptors.<br> * <br> * The cache maintains all descriptors loaded by its * <code>XMLClassDescriptorResolver</code>. It also keeps track of * mapping files and CDR lists that have been loaded. Just like the * ClassCache it also has a list of missing descriptors to avoid trying to * load those descriptors again. * * The cached descriptors are available via the name of the classes they * describe or via their XML name from a mapping file. * * @author <a href="mailto:stevendolg AT gxm DOT at">Steven Dolg</a> */ private static class DescriptorCacheImpl implements ResolverStrategy.ResolverResults { /** Logger to be used by DescriptorCache. */ private static final Log LOG2 = LogFactory.getLog(DescriptorCacheImpl.class); /** Some fixed text to detect errors... */ private static final String INTERNAL_CONTAINER_NAME = "-error-if-this-is-used-"; /** List of class names a descriptor is not available for. */ private final List<String> _missingTypes; /** Map of cached descriptors with the class names they describe as key. */ private final Map<String, ClassDescriptor> _typeMap; /** Map of cached descriptors with their XML names as key. */ private final Map<String, List<ClassDescriptor>> _xmlNameMap; /** Lock used to isolate write accesses to the caches internal lists and maps. */ private final ReentrantReadWriteLock _lock; /** * Default constructor.<br> * <br> * Initializes all lists and maps. */ public DescriptorCacheImpl() { super(); LOG2.debug("New instance!"); _typeMap = new HashMap<String, ClassDescriptor>(); _xmlNameMap = new HashMap<String, List<ClassDescriptor>>(); _missingTypes = new ArrayList<String>(); _lock = new ReentrantReadWriteLock(); } //--- DescriptorCacheImpl /** * Adds a descriptor to this caches maps.<br> * The descriptor is mapped both with the class name and its XML name. * * The descriptor will not be mapped with its XML name is * <code>null</code>, the empty string (""), or has the value of the * constant INTERNAL_CONTAINER_NAME. * * If there already is a descriptor for the given <code>className</code> * and/or the descriptor's XML name the previously cached descriptor is * replaced. * * @param className The class name to be used for mapping the given descriptor. * @param descriptor The descriptor to be mapped. * @throws InterruptedException * * @see #INTERNAL_CONTAINER_NAME */ public void addDescriptor(final String className, final XMLClassDescriptor descriptor) { if ((className == null) || (className.length() == 0)) { String message = "Class name to insert ClassDescriptor must not be null"; LOG2.warn(message); throw new IllegalArgumentException(message); } // acquire write lock first _lock.writeLock().lock(); try { if (descriptor == null) { if (LOG2.isDebugEnabled()) { LOG2.debug("Adding class name to missing classes: " + className); } _missingTypes.add(className); return; } if (LOG2.isDebugEnabled()) { LOG2.debug("Adding descriptor class for: " + className + " descriptor: " + descriptor); } _typeMap.put(className, descriptor); String xmlName = descriptor.getXMLName(); // ignore descriptors with an empty XMLName if (xmlName == null || xmlName.length() == 0) { return; } // ignore descriptors with the internal XMLName if (INTERNAL_CONTAINER_NAME.equals(xmlName)) { return; } // add new descriptor to the list for the corresponding XML name List<ClassDescriptor> descriptorList = _xmlNameMap.get(xmlName); if (descriptorList == null) { descriptorList = new ArrayList<ClassDescriptor>(); _xmlNameMap.put(xmlName, descriptorList); } if (!descriptorList.contains(descriptor)) { descriptorList.add(descriptor); } _missingTypes.remove(className); } finally { _lock.writeLock().unlock(); } } //-- addDescriptor /** * Gets the descriptor that is mapped to the given class name. * * @param className The class name to get a descriptor for. * @return The descriptor mapped to the given name or <code>null</code> * if no descriptor is stored in this cache. */ public XMLClassDescriptor getDescriptor(final String className) { // acquire read lock which is released via finally block _lock.readLock().lock(); try { if ((className == null) || ("".equals(className)) || (_missingTypes.contains(className))) { return null; } XMLClassDescriptor ret = (XMLClassDescriptor) _typeMap.get(className); if (LOG2.isDebugEnabled()) { LOG2.debug("Get descriptor for: " + className + " found: " + ret); } return ret; } finally { _lock.readLock().unlock(); } } //-- getDescriptor /** * Gets a list of descriptors that have the given XML name.<br> * <br> * This method will return all previously cached descriptors with the * given XML name regardless of their name space. * * @param xmlName The XML name of the descriptors to get. * @return A list of descriptors with the given XML name or an empty * list if no such descriptor is stored in this cache. This * method will never return <code>null</code>! */ public List<ClassDescriptor> getDescriptors(final String xmlName) { // before accessing XML name map acquire read lock first _lock.readLock().lock(); List<ClassDescriptor> list = _xmlNameMap.get(xmlName); _lock.readLock().unlock(); if (list == null) { // return an empty list list = new ArrayList<ClassDescriptor>(); } else { // return a copy of the original list list = new ArrayList<ClassDescriptor>(list); } return list; } //-- getDescriptorList /** * Checks whether the given class name is contained in the list of class * names the descriptor is found to be missing.<br> * <br> * NOTE: This does not check whether a descriptor is stored within this * cache or not. This rather checks the list of class names the * XMLClassDescriptorResolverImpl found to have no descriptor. The cache * itself has no means of determining that this is the case and thus * will never add/remove class names to/from it. * * @param className The class name to be checked. * @return <code>true</code> If the given class name was stated to * have no descriptor by a previous call to * <code>addMissingDescriptor</code> with exactly the same * class name. <code>false</code> otherwise. * * @see #addMissingDescriptor(String) */ public boolean isMissingDescriptor(final String className) { // before accessing list with missing types acquire read lock first _lock.readLock().lock(); try { return _missingTypes.contains(className); } finally { _lock.readLock().unlock(); } } //-- isMissingDescriptor /** * To add not only a single descriptor but a map of descriptors at once. * * @param descriptors a Map of className (String) and XMLClassDescriptor pairs */ public void addAllDescriptors(final Map descriptors) { if ((descriptors == null) || (descriptors.isEmpty())) { LOG2.debug("Called addAllDescriptors with null or empty descriptor map"); return; } for (Iterator<String> iter = descriptors.keySet().iterator(); iter.hasNext(); ) { String clsName = iter.next(); this.addDescriptor(clsName, (XMLClassDescriptor) descriptors.get(clsName)); } } } // -- DescriptorCacheImpl /** * Cleans the descriptor cache. * @see org.exolab.castor.xml.XMLClassDescriptorResolver#cleanDescriptorCache() */ public void cleanDescriptorCache() { _descriptorCache = new DescriptorCacheImpl(); } } // -- ClassDescriptorResolverImpl