org.apache.ojb.broker.metadata.DescriptorRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ojb.broker.metadata.DescriptorRepository.java

Source

package org.apache.ojb.broker.metadata;

/* Copyright 2002-2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.Serializable;
import java.util.*;

import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.ojb.broker.OJBRuntimeException;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.locking.IsolationLevels;
import org.apache.ojb.broker.util.ClassHelper;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import org.apache.ojb.broker.util.logging.Logger;

/**
 * The repository containing all object mapping and manipulation information of
 * all used persistent objects.
 * <br>
 * Note: Be careful when use references of this class or caching instances of this class,
 * because instances could become invalid (see {@link MetadataManager}).
 *
 * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
 * @author <a href="mailto:leandro@ibnetwork.com.br">Leandro Rodrigo Saad Cruz<a>
 * @version $Id: DescriptorRepository.java,v 1.50.2.9 2005/12/21 22:26:11 tomdz Exp $
 */
public final class DescriptorRepository extends DescriptorBase
        implements Serializable, XmlCapable, IsolationLevels {
    static final long serialVersionUID = -1556339982311359524L;
    private Logger log = LoggerFactory.getLogger(DescriptorRepository.class);

    /**
     * The version identifier of the Repository.
     * Used to validate repository.xml against the dtd.
     */
    private static final String VERSION = "1.0";
    /**
     * the default isolation level used for this repository
     */
    private int defaultIsolationLevel = IsolationLevels.IL_DEFAULT;
    /**
     * This table holds all known Mapping descriptions.
     * Key values are the respective Class objects
     */
    private final HashMap descriptorTable;
    /**
     * We need a lot the extent, to which a class belongs
     * (@see DescriptorRepository#getExtentClass). To speed up the costy
     * evaluation, we use this tiny hash map.
     */
    private Map extentTable;

    private Map superClassMultipleJoinedTablesMap;

    private transient Map m_multiMappedTableMap;
    private transient Map m_topLevelClassTable;
    private transient Map m_firstConcreteClassMap;
    private transient Map m_allConcreteSubClass;

    /**
     * Constructor declaration
     */
    public DescriptorRepository() throws PersistenceBrokerException {
        descriptorTable = new HashMap();
        extentTable = new HashMap();
        superClassMultipleJoinedTablesMap = new HashMap();
    }

    public static String getVersion() {
        return VERSION;
    }

    /**
     * Add a pair of extent/classdescriptor to the extentTable to gain speed
     * while retrieval of extents.
     * @param classname the name of the extent itself
     * @param cld the class descriptor, where it belongs to
     */
    void addExtent(String classname, ClassDescriptor cld) {
        synchronized (extentTable) {
            extentTable.put(classname, cld);
        }
    }

    /**
     * Remove a pair of extent/classdescriptor from the extentTable.
     * @param classname the name of the extent itself
     */
    void removeExtent(String classname) {
        synchronized (extentTable) {
            // returns the super class for given extent class name
            ClassDescriptor cld = (ClassDescriptor) extentTable.remove(classname);
            if (cld != null && m_topLevelClassTable != null) {
                Class extClass = null;
                try {
                    extClass = ClassHelper.getClass(classname);
                } catch (ClassNotFoundException e) {
                    // Should not happen
                    throw new MetadataException("Can't instantiate class object for needed extent remove", e);
                }
                // remove extent from super class descriptor
                cld.removeExtentClass(classname);
                m_topLevelClassTable.remove(extClass);
                // clear map with first concrete classes, because the removed
                // extent could be such a first found concrete class
                m_firstConcreteClassMap = null;
            }
        }
    }

    /**
     * Returns the top level (extent) class to which the given class belongs.
     * This may be a (abstract) base-class, an interface or the given class
     * itself if given class is not defined as an extent in other class
     * descriptors.
     *
     * @throws ClassNotPersistenceCapableException if clazz is not persistence capable,
     * i.e. if clazz is not defined in the DescriptorRepository.
     */
    public Class getTopLevelClass(Class clazz) throws ClassNotPersistenceCapableException {
        if (m_topLevelClassTable == null) {
            m_topLevelClassTable = new HashMap();
        }
        // try to find an extent that contains clazz
        Class retval = (Class) m_topLevelClassTable.get(clazz);
        if (retval == null) {
            synchronized (extentTable) {
                ClassDescriptor cld = (ClassDescriptor) extentTable.get(clazz.getName());
                if (cld == null) {
                    // walk the super-references
                    cld = getDescriptorFor(clazz).getSuperClassDescriptor();
                }

                if (cld != null) {
                    // fix by Mark Rowell
                    // Changed to call getExtentClass recursively
                    retval = getTopLevelClass(cld.getClassOfObject());
                    // if such an extent could not be found just return clazz itself.
                    if (retval == null) {
                        retval = clazz;
                    }
                } else {
                    // check if class is persistence capable
                    // +
                    // Adam Jenkins: use the class that is associated
                    // with the descriptor instead of the actual class
                    ClassDescriptor temp = getDescriptorFor(clazz);
                    retval = temp.getClassOfObject();
                }
                m_topLevelClassTable.put(clazz, retval);
            }
        }
        return retval;
    }

    /**
     * @return all field descriptors for a class that belongs to a set of classes mapped
     * to the same table, otherwise the select queries produced won't contain the necessary
     * information to materialize extents mapped to the same class.
     */
    public synchronized FieldDescriptor[] getFieldDescriptorsForMultiMappedTable(ClassDescriptor targetCld) {
        if (m_multiMappedTableMap == null) {
            m_multiMappedTableMap = new HashMap();
        }

        FieldDescriptor[] retval = (FieldDescriptor[]) m_multiMappedTableMap.get(targetCld.getClassNameOfObject());
        if (retval == null) {
            retval = getAllMappedColumns(getClassesMappedToSameTable(targetCld));
            m_multiMappedTableMap.put(targetCld.getClassNameOfObject(), retval);
        }
        return retval;
    }

    private FieldDescriptor[] getAllMappedColumns(List classDescriptors) {
        /* mkalen: Use an ordered implementation not to loose individual field ordering.
        This is especially important for eg Oracle9i platform and LONGVARBINARY columns,
        see http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96654/basic.htm#1021777
        "If you do not use the SELECT-list order to access data,
         then you can lose the stream data."
        */
        List allFieldDescriptors = new Vector();

        Set visitedColumns = new HashSet();
        Iterator it = classDescriptors.iterator();
        ClassDescriptor temp = null;
        FieldDescriptor[] fields;
        while (it.hasNext()) {
            temp = (ClassDescriptor) it.next();
            fields = temp.getFieldDescriptions();
            if (fields != null) {
                for (int i = 0; i < fields.length; i++) {
                    /*
                    MBAIRD
                    hashmap will only allow one entry per unique key,
                    so no need to check contains(fields[i].getColumnName()).
                    arminw:
                    use contains to avoid overriding of target class fields by the same
                    field-descriptor of other classes mapped to the same DB table, because
                    the other class can use e.g. different FieldConversion.
                    In #getClassesMappedToSameTable(...) we make sure that target
                    class has first position in list.
                     */
                    final String columnName = fields[i].getColumnName();
                    if (!visitedColumns.contains(columnName)) {
                        visitedColumns.add(columnName);
                        allFieldDescriptors.add(fields[i]);
                    }
                }
            }
        }
        FieldDescriptor[] retval = new FieldDescriptor[allFieldDescriptors.size()];
        allFieldDescriptors.toArray(retval);
        return retval;
    }

    private List getClassesMappedToSameTable(ClassDescriptor targetCld) {
        /*
        try to find an extent that contains clazz
        clone map to avoid synchronization problems, because another thread
        can do a put(..) operation on descriptor table
        */
        Iterator iter = ((HashMap) descriptorTable.clone()).values().iterator();
        List retval = new ArrayList();
        // make sure that target class is at first position
        retval.add(targetCld);
        while (iter.hasNext()) {
            ClassDescriptor cld = (ClassDescriptor) iter.next();
            if (cld.getFullTableName() != null) {
                if (cld.getFullTableName().equals(targetCld.getFullTableName())
                        && !targetCld.getClassOfObject().equals(cld.getClassOfObject())) {
                    retval.add(cld);
                }
            }
        }
        return retval;
    }

    public Map getDescriptorTable() {
        return descriptorTable;
    }

    /**
     * Return the first found concrete class {@link ClassDescriptor}.
     * This means a class which is not an interface or an abstract class.
     * If given class descriptor is a concrete class, given class descriptor
     * was returned. If no concrete class can be found <code>null</code> will be
     * returned.
     */
    public ClassDescriptor findFirstConcreteClass(ClassDescriptor cld) {
        if (m_firstConcreteClassMap == null) {
            m_firstConcreteClassMap = new HashMap();
        }
        ClassDescriptor result = (ClassDescriptor) m_firstConcreteClassMap.get(cld.getClassNameOfObject());
        if (result == null) {
            if (cld.isInterface() || cld.isAbstract()) {
                if (cld.isExtent()) {
                    List extents = cld.getExtentClasses();
                    for (int i = 0; i < extents.size(); i++) {
                        Class ext = (Class) extents.get(i);
                        result = findFirstConcreteClass(getDescriptorFor(ext));
                        if (result != null)
                            break;
                    }
                } else {
                    LoggerFactory.getDefaultLogger()
                            .error("[" + this.getClass().getName() + "] Found interface/abstract class"
                                    + " in metadata declarations without concrete class: "
                                    + cld.getClassNameOfObject());
                }
                m_firstConcreteClassMap.put(cld.getClassNameOfObject(), result);
            } else {
                result = cld;
            }
        }
        return result;
    }

    /**
     *
     * Utility method to discover all concrete subclasses of a given super class. <br>
     * This method was introduced in order to get Extent Aware Iterators.
     *
     * @return a Collection of ClassDescriptor objects
     */
    public Collection getAllConcreteSubclassDescriptors(ClassDescriptor aCld) {
        if (m_allConcreteSubClass == null) {
            m_allConcreteSubClass = new HashMap();
        }
        Collection concreteSubclassClds = (Collection) m_allConcreteSubClass.get(aCld.getClassOfObject());

        if (concreteSubclassClds == null) {
            // BRJ: As long as we do not have an ordered Set
            // duplicates have to be prevented manually.
            // a HashSet should not be used because the order is unpredictable
            concreteSubclassClds = new ArrayList();
            Iterator iter = aCld.getExtentClasses().iterator();

            while (iter.hasNext()) {
                Class extentClass = (Class) iter.next();
                ClassDescriptor extCld = getDescriptorFor(extentClass);
                if (aCld.equals(extCld)) {
                    // prevent infinite recursion caused by cyclic references
                    continue;
                }
                if (!extCld.isInterface() && !extCld.isAbstract()) {
                    if (!concreteSubclassClds.contains(extCld)) {
                        concreteSubclassClds.add(extCld);
                    }
                }

                // recurse
                Iterator subIter = getAllConcreteSubclassDescriptors(extCld).iterator();
                while (subIter.hasNext()) {
                    ClassDescriptor subCld = (ClassDescriptor) subIter.next();
                    if (!concreteSubclassClds.contains(subCld)) {
                        concreteSubclassClds.add(subCld);
                    }
                }
            }
            m_allConcreteSubClass.put(aCld.getClassOfObject(), concreteSubclassClds);
        }

        return concreteSubclassClds;
    }

    /**
     * Checks if repository contains given class.
     */
    public boolean hasDescriptorFor(Class c) {
        return descriptorTable.containsKey(c.getName());
    }

    /**
     * lookup a ClassDescriptor in the internal Hashtable
     * @param strClassName a fully qualified class name as it is returned by Class.getName().
     */
    public ClassDescriptor getDescriptorFor(String strClassName) throws ClassNotPersistenceCapableException {
        ClassDescriptor result = discoverDescriptor(strClassName);
        if (result == null) {
            throw new ClassNotPersistenceCapableException(strClassName + " not found in OJB Repository");
        } else {
            return result;
        }
    }

    /**
     * lookup a ClassDescriptor in the internal Hashtable
     */
    public ClassDescriptor getDescriptorFor(Class c) throws ClassNotPersistenceCapableException {
        return this.getDescriptorFor(c.getName());
    }

    /**
     * Convenience for {@link #put(Class c, ClassDescriptor cld)}
     */
    public void setClassDescriptor(ClassDescriptor cld) {
        this.put(cld.getClassNameOfObject(), cld);
    }

    /**
     * Add a ClassDescriptor to the internal Hashtable<br>
     * Set the Repository for ClassDescriptor
     */
    public void put(Class c, ClassDescriptor cld) {
        this.put(c.getName(), cld);
    }

    /**
     * Add a ClassDescriptor to the internal Hashtable<br>
     * Set the Repository for ClassDescriptor
     */
    public void put(String classname, ClassDescriptor cld) {
        cld.setRepository(this); // BRJ
        synchronized (descriptorTable) {
            descriptorTable.put(classname, cld);
            List extentClasses = cld.getExtentClasses();
            for (int i = 0; i < extentClasses.size(); ++i) {
                addExtent(((Class) extentClasses.get(i)).getName(), cld);
            }
            changeDescriptorEvent();
        }
    }

    public void remove(String className) {
        synchronized (descriptorTable) {
            ClassDescriptor cld = (ClassDescriptor) descriptorTable.remove(className);
            if (cld != null) {
                // class itself could no longer be a extent
                Iterator it = descriptorTable.values().iterator();
                while (it.hasNext()) {
                    ((ClassDescriptor) it.next()).removeExtentClass(className);
                }
                removeExtent(className);
                List extentClasses = cld.getExtentClasses();
                for (int i = 0; i < extentClasses.size(); ++i) {
                    removeExtent(((Class) extentClasses.get(i)).getName());
                }
                changeDescriptorEvent();
                // deregister classes using mapping of classes to multiple joined tables
                // the registration is done by the class-descriptor itself
                deregisterSuperClassMultipleJoinedTables(cld);
            }
        }
    }

    public void remove(Class clazz) {
        remove(clazz.getName());
    }

    private synchronized void changeDescriptorEvent() {
        m_multiMappedTableMap = null;
        m_topLevelClassTable = null;
        m_firstConcreteClassMap = null;
        m_allConcreteSubClass = null;
    }

    /**
     * Returns an iterator over all managed {@link ClassDescriptor}.
     */
    public Iterator iterator() {
        /*
        clone map to avoid synchronization problems
        */
        return ((HashMap) descriptorTable.clone()).values().iterator();
    }

    /**
     * Returns the defaultIsolationLevel.
     * @return int
     */
    public int getDefaultIsolationLevel() {
        return defaultIsolationLevel;
    }

    /**
     * Sets the defaultIsolationLevel.
     * @param defaultIsolationLevel The defaultIsolationLevel to set
     */
    public void setDefaultIsolationLevel(int defaultIsolationLevel) {
        this.defaultIsolationLevel = defaultIsolationLevel;
    }

    /**
     * returns a string representation
     */
    public String toString() {
        Iterator it = descriptorTable.entrySet().iterator();
        ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
        String className = "class name: ";
        String tableName = "> table name: ";
        while (it.hasNext()) {
            Map.Entry me = (Map.Entry) it.next();
            ClassDescriptor descriptor = (ClassDescriptor) me.getValue();
            buf.append(className + me.getKey() + " =", tableName + descriptor.getFullTableName());
        }
        return buf.toString();
    }

    /*
     * @see XmlCapable#toXML()
     */
    public String toXML() {
        String eol = SystemUtils.LINE_SEPARATOR;
        StringBuffer buf = new StringBuffer();

        // write all ClassDescriptors
        Iterator i = this.iterator();
        while (i.hasNext()) {
            buf.append(((XmlCapable) i.next()).toXML() + eol);
        }
        return buf.toString();
    }

    /**
     * returns IsolationLevel literal as matching
     * to the corresponding id
     * @return the IsolationLevel literal
     */
    protected String getIsolationLevelAsString() {
        if (defaultIsolationLevel == IL_READ_UNCOMMITTED) {
            return LITERAL_IL_READ_UNCOMMITTED;
        } else if (defaultIsolationLevel == IL_READ_COMMITTED) {
            return LITERAL_IL_READ_COMMITTED;
        } else if (defaultIsolationLevel == IL_REPEATABLE_READ) {
            return LITERAL_IL_REPEATABLE_READ;
        } else if (defaultIsolationLevel == IL_SERIALIZABLE) {
            return LITERAL_IL_SERIALIZABLE;
        } else if (defaultIsolationLevel == IL_OPTIMISTIC) {
            return LITERAL_IL_OPTIMISTIC;
        }
        return LITERAL_IL_READ_UNCOMMITTED;
    }

    /**
     * Starts by looking to see if the <code>className</code> is
     * already mapped specifically to the descritpor repository.
     * If the <code>className</code> is not specifically mapped we
     * look at the <code>className</code>'s parent class for a mapping.
     * We do this until the parent class is of the type
     * <code>java.lang.Object</code>.  If no mapping was found,
     * <code>null</code> is returned.  Mappings successfuly discovered
     * through inheritence are added to the internal table of
     * class descriptors to improve performance on subsequent requests
     * for those classes.
     *
     * <br/>
     * author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
     * @param className name of class whose descriptor we need to find.
     * @return ClassDescriptor for <code>className</code> or <code>null</code>
     * if no ClassDescriptor could be located.
     */
    protected ClassDescriptor discoverDescriptor(String className) {
        ClassDescriptor result = (ClassDescriptor) descriptorTable.get(className);
        if (result == null) {
            Class clazz;
            try {
                clazz = ClassHelper.getClass(className, true);
            } catch (ClassNotFoundException e) {
                throw new OJBRuntimeException("Class, " + className + ", could not be found.", e);
            }
            result = discoverDescriptor(clazz);
        }
        return result;
    }

    /**
     * Internal method for recursivly searching for a class descriptor that avoids
     * class loading when we already have a class object.
     *
     * @param clazz The class whose descriptor we need to find
     * @return ClassDescriptor for <code>clazz</code> or <code>null</code>
     *         if no ClassDescriptor could be located.
     */
    private ClassDescriptor discoverDescriptor(Class clazz) {
        ClassDescriptor result = (ClassDescriptor) descriptorTable.get(clazz.getName());

        if (result == null) {
            Class superClass = clazz.getSuperclass();
            // only recurse if the superClass is not java.lang.Object
            if (superClass != null) {
                result = discoverDescriptor(superClass);
            }
            if (result == null) {
                // we're also checking the interfaces as there could be normal
                // mappings for them in the repository (using factory-class,
                // factory-method, and the property field accessor)
                Class[] interfaces = clazz.getInterfaces();

                if ((interfaces != null) && (interfaces.length > 0)) {
                    for (int idx = 0; (idx < interfaces.length) && (result == null); idx++) {
                        result = discoverDescriptor(interfaces[idx]);
                    }
                }
            }

            if (result != null) {
                descriptorTable.put(clazz.getName(), result);
            }
        }
        return result;
    }

    /**
     * Internal used! Register sub-classes of specified class when mapping class to
     * multiple joined tables is used. Normally this method is called by the {@link ClassDescriptor}
     * itself.
     *
     * @param cld The {@link ClassDescriptor} of the class to register.
     */
    protected void registerSuperClassMultipleJoinedTables(ClassDescriptor cld) {
        /*
        arminw: Sadly, we can't map to sub class-descriptor, because it's not guaranteed
        that they exist when this method is called. Thus we map the class instance instead
        of the class-descriptor.
        */
        if (cld.getBaseClass() != null) {
            try {
                Class superClass = ClassHelper.getClass(cld.getBaseClass());
                Class currentClass = cld.getClassOfObject();
                synchronized (descriptorTable) {
                    List subClasses = (List) superClassMultipleJoinedTablesMap.get(superClass);
                    if (subClasses == null) {
                        subClasses = new ArrayList();
                        superClassMultipleJoinedTablesMap.put(superClass, subClasses);
                    }
                    if (!subClasses.contains(currentClass)) {
                        if (log.isDebugEnabled()) {
                            log.debug("(MultipleJoinedTables): Register sub-class '" + currentClass
                                    + "' for class '" + superClass);
                        }
                        subClasses.add(currentClass);
                    }
                }
            } catch (Exception e) {
                throw new MetadataException(
                        "Can't register super class '" + cld.getBaseClass() + "' for class-descriptor: " + cld, e);
            }
        }
    }

    /**
     * Internal used! Deregister sub-classes of specified class when mapping to multiple joined tables
     * is used. Normally this method is called when {@link #remove(Class)} a class.
     *
     * @param cld The {@link ClassDescriptor} of the class to register.
     */
    protected void deregisterSuperClassMultipleJoinedTables(ClassDescriptor cld) {
        try {
            Class currentClass = cld.getClassOfObject();
            synchronized (descriptorTable) {
                // first remove registered sub-classes for current class
                List subClasses = (List) superClassMultipleJoinedTablesMap.remove(currentClass);
                if (subClasses != null && log.isDebugEnabled()) {
                    log.debug("(MultipleJoinedTables): Deregister class " + currentClass + " with sub classes "
                            + subClasses);
                }
                if (cld.getBaseClass() != null) {
                    // then remove sub-class entry of current class for super-class
                    Class superClass = ClassHelper.getClass(cld.getBaseClass());
                    subClasses = (List) superClassMultipleJoinedTablesMap.get(superClass);
                    if (subClasses != null) {
                        boolean removed = subClasses.remove(currentClass);
                        if (removed && log.isDebugEnabled()) {
                            log.debug("(MultipleJoinedTables): Remove sub-class entry '" + currentClass
                                    + "' in mapping for class '" + superClass + "'");
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new MetadataException(
                    "Can't deregister super class '" + cld.getBaseClass() + "' for class-descriptor: " + cld, e);
        }
    }

    /**
     * Return <em>sub-classes</em> of the specified class using the
     * <em>"super"-Reference</em> concept.
     * @param cld The {@link ClassDescriptor} of the class to search for sub-classes.
     * @param wholeTree If set <em>true</em>, the whole sub-class tree of the specified
     * class will be returned. If <em>false</em> only the direct sub-classes of the specified class
     * will be returned.
     * @return An array of <em>sub-classes</em> for the specified class.
     */
    public Class[] getSubClassesMultipleJoinedTables(ClassDescriptor cld, boolean wholeTree) {
        ArrayList result = new ArrayList();
        createResultSubClassesMultipleJoinedTables(result, cld, wholeTree);
        return (Class[]) result.toArray(new Class[result.size()]);
    }

    /**
     * Add all sub-classes using multiple joined tables feature for specified class.
     * @param result The list to add results.
     * @param cld The {@link ClassDescriptor} of the class to search for sub-classes.
     * @param wholeTree If set <em>true</em>, the whole sub-class tree of the specified
     * class will be returned. If <em>false</em> only the direct sub-classes of the specified class
     * will be returned.
     */
    private void createResultSubClassesMultipleJoinedTables(List result, ClassDescriptor cld, boolean wholeTree) {
        List tmp = (List) superClassMultipleJoinedTablesMap.get(cld.getClassOfObject());
        if (tmp != null) {
            result.addAll(tmp);
            if (wholeTree) {
                for (int i = 0; i < tmp.size(); i++) {
                    Class subClass = (Class) tmp.get(i);
                    ClassDescriptor subCld = getDescriptorFor(subClass);
                    createResultSubClassesMultipleJoinedTables(result, subCld, wholeTree);
                }
            }
        }
    }

    protected void finalize() throws Throwable {
        log.info("# finalize DescriptorRepository instance #");
        super.finalize();
    }
}