001    package org.apache.ojb.broker.metadata;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.io.Serializable;
019    import java.util.*;
020    
021    import org.apache.commons.lang.SystemUtils;
022    import org.apache.commons.lang.builder.ToStringBuilder;
023    import org.apache.commons.lang.builder.ToStringStyle;
024    import org.apache.ojb.broker.OJBRuntimeException;
025    import org.apache.ojb.broker.PersistenceBrokerException;
026    import org.apache.ojb.broker.locking.IsolationLevels;
027    import org.apache.ojb.broker.util.ClassHelper;
028    import org.apache.ojb.broker.util.logging.LoggerFactory;
029    import org.apache.ojb.broker.util.logging.Logger;
030    
031    /**
032     * The repository containing all object mapping and manipulation information of
033     * all used persistent objects.
034     * <br>
035     * Note: Be careful when use references of this class or caching instances of this class,
036     * because instances could become invalid (see {@link MetadataManager}).
037     *
038     * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
039     * @author <a href="mailto:leandro@ibnetwork.com.br">Leandro Rodrigo Saad Cruz<a>
040     * @version $Id: DescriptorRepository.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $
041     */
042    @SuppressWarnings("unchecked")
043    public final class DescriptorRepository extends DescriptorBase
044            implements Serializable, XmlCapable, IsolationLevels
045    {
046        static final long serialVersionUID = -1556339982311359524L;
047        private Logger log = LoggerFactory.getLogger(DescriptorRepository.class);
048    
049        /**
050         * The version identifier of the Repository.
051         * Used to validate repository.xml against the dtd.
052         */
053        private static final String VERSION = "1.0";
054        /**
055         * the default isolation level used for this repository
056         */
057        private int defaultIsolationLevel = IsolationLevels.IL_DEFAULT;
058        /**
059         * This table holds all known Mapping descriptions.
060         * Key values are the respective Class objects
061         */
062            private final HashMap descriptorTable;
063        /**
064         * We need a lot the extent, to which a class belongs
065         * (@see DescriptorRepository#getExtentClass). To speed up the costy
066         * evaluation, we use this tiny hash map.
067         */
068        private Map extentTable;
069    
070        private Map superClassMultipleJoinedTablesMap;
071    
072        private transient Map m_multiMappedTableMap;
073        private transient Map m_topLevelClassTable;
074        private transient Map m_firstConcreteClassMap;
075        private transient Map m_allConcreteSubClass;
076    
077        /**
078         * Constructor declaration
079         */
080        public DescriptorRepository() throws PersistenceBrokerException
081        {
082            descriptorTable = new HashMap();
083            extentTable = new HashMap();
084            superClassMultipleJoinedTablesMap = new HashMap();
085        }
086    
087        public static String getVersion()
088        {
089            return VERSION;
090        }
091    
092        /**
093         * Add a pair of extent/classdescriptor to the extentTable to gain speed
094         * while retrieval of extents.
095         * @param classname the name of the extent itself
096         * @param cld the class descriptor, where it belongs to
097         */
098        void addExtent(String classname, ClassDescriptor cld)
099        {
100            synchronized (extentTable)
101            {
102                extentTable.put(classname, cld);
103            }
104        }
105    
106        /**
107         * Remove a pair of extent/classdescriptor from the extentTable.
108         * @param classname the name of the extent itself
109         */
110        void removeExtent(String classname)
111        {
112            synchronized (extentTable)
113            {
114                // returns the super class for given extent class name
115                ClassDescriptor cld = (ClassDescriptor) extentTable.remove(classname);
116                if(cld != null && m_topLevelClassTable != null)
117                {
118                    Class extClass = null;
119                    try
120                    {
121                        extClass = ClassHelper.getClass(classname);
122                    }
123                    catch (ClassNotFoundException e)
124                    {
125                        // Should not happen
126                        throw new MetadataException("Can't instantiate class object for needed extent remove", e);
127                    }
128                    // remove extent from super class descriptor
129                    cld.removeExtentClass(classname);
130                    m_topLevelClassTable.remove(extClass);
131                    // clear map with first concrete classes, because the removed
132                    // extent could be such a first found concrete class
133                    m_firstConcreteClassMap = null;
134                }
135            }
136        }
137    
138        /**
139         * Returns the top level (extent) class to which the given class belongs.
140         * This may be a (abstract) base-class, an interface or the given class
141         * itself if given class is not defined as an extent in other class
142         * descriptors.
143         *
144         * @throws ClassNotPersistenceCapableException if clazz is not persistence capable,
145         * i.e. if clazz is not defined in the DescriptorRepository.
146         */
147        public Class getTopLevelClass(Class clazz) throws ClassNotPersistenceCapableException
148        {
149            if(m_topLevelClassTable == null)
150            {
151                m_topLevelClassTable = new HashMap();
152            }
153            // try to find an extent that contains clazz
154            Class retval = (Class) m_topLevelClassTable.get(clazz);
155            if (retval == null)
156            {
157                synchronized (extentTable)
158                {
159                    ClassDescriptor cld = (ClassDescriptor) extentTable.get(clazz.getName());
160                    if (cld == null)
161                    {
162                        // walk the super-references
163                        cld = getDescriptorFor(clazz).getSuperClassDescriptor();
164                    }
165                    
166                    if (cld != null)
167                    {
168                        // fix by Mark Rowell
169                        // Changed to call getExtentClass recursively
170                        retval = getTopLevelClass(cld.getClassOfObject());
171                        // if such an extent could not be found just return clazz itself.
172                        if (retval == null)
173                        {
174                            retval = clazz;
175                        }
176                    }
177                    else
178                    {
179                        // check if class is persistence capable
180                        // +
181                        // Adam Jenkins: use the class that is associated
182                        // with the descriptor instead of the actual class
183                        ClassDescriptor temp = getDescriptorFor(clazz);
184                        retval = temp.getClassOfObject();
185                    }
186                    m_topLevelClassTable.put(clazz, retval);
187                }
188            }
189            return retval;
190        }
191    
192        /**
193         * @return all field descriptors for a class that belongs to a set of classes mapped
194         * to the same table, otherwise the select queries produced won't contain the necessary
195         * information to materialize extents mapped to the same class.
196         */
197        public synchronized FieldDescriptor[] getFieldDescriptorsForMultiMappedTable(ClassDescriptor targetCld)
198        {
199            if (m_multiMappedTableMap == null)
200            {
201                m_multiMappedTableMap = new HashMap();
202            }
203    
204            FieldDescriptor[] retval = (FieldDescriptor[]) m_multiMappedTableMap.get(targetCld.getClassNameOfObject());
205            if (retval == null)
206            {
207                retval = getAllMappedColumns(getClassesMappedToSameTable(targetCld));
208                m_multiMappedTableMap.put(targetCld.getClassNameOfObject(), retval);
209            }
210            return retval;
211        }
212    
213        private FieldDescriptor[] getAllMappedColumns(List classDescriptors)
214        {
215            /* mkalen: Use an ordered implementation not to loose individual field ordering.
216                This is especially important for eg Oracle9i platform and LONGVARBINARY columns,
217                see http://download-west.oracle.com/docs/cd/B10501_01/java.920/a96654/basic.htm#1021777
218                "If you do not use the SELECT-list order to access data,
219                 then you can lose the stream data."
220            */
221            List allFieldDescriptors = new Vector();
222    
223            Set visitedColumns = new HashSet();
224            Iterator it = classDescriptors.iterator();
225            ClassDescriptor temp = null;
226            FieldDescriptor[] fields;
227            while (it.hasNext())
228            {
229                temp = (ClassDescriptor) it.next();
230                fields = temp.getFieldDescriptions();
231                if (fields != null)
232                {
233                    for (int i = 0; i < fields.length; i++)
234                    {
235                        /*
236                        MBAIRD
237                        hashmap will only allow one entry per unique key,
238                        so no need to check contains(fields[i].getColumnName()).
239                        arminw:
240                        use contains to avoid overriding of target class fields by the same
241                        field-descriptor of other classes mapped to the same DB table, because
242                        the other class can use e.g. different FieldConversion.
243                        In #getClassesMappedToSameTable(...) we make sure that target
244                        class has first position in list.
245                         */
246                        final String columnName = fields[i].getColumnName();
247                        if (!visitedColumns.contains(columnName))
248                        {
249                            visitedColumns.add(columnName);
250                            allFieldDescriptors.add(fields[i]);
251                        }
252                    }
253                }
254            }
255            FieldDescriptor[] retval = new FieldDescriptor[allFieldDescriptors.size()];
256            allFieldDescriptors.toArray(retval);
257            return retval;
258        }
259    
260        private List getClassesMappedToSameTable(ClassDescriptor targetCld)
261        {
262            /*
263            try to find an extent that contains clazz
264            clone map to avoid synchronization problems, because another thread
265            can do a put(..) operation on descriptor table
266            */
267            Iterator iter = ((HashMap)descriptorTable.clone()).values().iterator();
268            List retval = new ArrayList();
269            // make sure that target class is at first position
270            retval.add(targetCld);
271            while (iter.hasNext())
272            {
273                ClassDescriptor cld = (ClassDescriptor) iter.next();
274                if (cld.getFullTableName() != null)
275                {
276                    if (cld.getFullTableName().equals(targetCld.getFullTableName())
277                            && !targetCld.getClassOfObject().equals(cld.getClassOfObject()))
278                    {
279                        retval.add(cld);
280                    }
281                }
282            }
283            return retval;
284        }
285    
286        public Map getDescriptorTable()
287        {
288            return descriptorTable;
289        }
290    
291        /**
292         * Return the first found concrete class {@link ClassDescriptor}.
293         * This means a class which is not an interface or an abstract class.
294         * If given class descriptor is a concrete class, given class descriptor
295         * was returned. If no concrete class can be found <code>null</code> will be
296         * returned.
297         */
298        public ClassDescriptor findFirstConcreteClass(ClassDescriptor cld)
299        {
300            if(m_firstConcreteClassMap == null)
301            {
302                m_firstConcreteClassMap = new HashMap();
303            }
304            ClassDescriptor result = (ClassDescriptor) m_firstConcreteClassMap.get(cld.getClassNameOfObject());
305            if (result == null)
306            {
307                if(cld.isInterface() || cld.isAbstract())
308                {
309                    if(cld.isExtent())
310                    {
311                        List extents = cld.getExtentClasses();
312                        for (int i = 0; i < extents.size(); i++)
313                        {
314                            Class ext = (Class) extents.get(i);
315                            result = findFirstConcreteClass(getDescriptorFor(ext));
316                            if(result != null) break;
317                        }
318                    }
319                    else
320                    {
321                        LoggerFactory.getDefaultLogger().error("["+this.getClass().getName()+"] Found interface/abstract class" +
322                                " in metadata declarations without concrete class: "+cld.getClassNameOfObject());
323                    }
324                    m_firstConcreteClassMap.put(cld.getClassNameOfObject(), result);
325                }
326                else
327                {
328                    result = cld;
329                }
330            }
331            return result;
332        }
333    
334        /**
335         *
336         * Utility method to discover all concrete subclasses of a given super class. <br>
337         * This method was introduced in order to get Extent Aware Iterators.
338         *
339         * @return a Collection of ClassDescriptor objects
340         */
341        public Collection getAllConcreteSubclassDescriptors(ClassDescriptor aCld)
342        {
343            if(m_allConcreteSubClass == null)
344            {
345                m_allConcreteSubClass = new HashMap();
346            }
347            Collection concreteSubclassClds = (Collection) m_allConcreteSubClass.get(aCld.getClassOfObject());
348    
349            if (concreteSubclassClds == null)
350            {
351                // BRJ: As long as we do not have an ordered Set
352                // duplicates have to be prevented manually.
353                // a HashSet should not be used because the order is unpredictable
354                concreteSubclassClds = new ArrayList();
355                Iterator iter = aCld.getExtentClasses().iterator();
356    
357                while (iter.hasNext())
358                {
359                    Class extentClass = (Class) iter.next();
360                    ClassDescriptor extCld = getDescriptorFor(extentClass);
361                    if (aCld.equals(extCld))
362                    {
363                        // prevent infinite recursion caused by cyclic references
364                        continue;
365                    }
366                    if (!extCld.isInterface() && !extCld.isAbstract())
367                    {
368                        if (!concreteSubclassClds.contains(extCld))
369                        {
370                            concreteSubclassClds.add(extCld);
371                        }
372                    }
373    
374                    // recurse
375                    Iterator subIter = getAllConcreteSubclassDescriptors(extCld).iterator();
376                    while (subIter.hasNext())
377                    {
378                        ClassDescriptor subCld = (ClassDescriptor)subIter.next();
379                        if (!concreteSubclassClds.contains(subCld))
380                        {
381                            concreteSubclassClds.add(subCld);
382                        }
383                    }
384                }
385                m_allConcreteSubClass.put(aCld.getClassOfObject(), concreteSubclassClds);
386            }
387    
388            return concreteSubclassClds;
389        }
390    
391    
392        /**
393         * Checks if repository contains given class.
394         */
395        public boolean hasDescriptorFor(Class c)
396        {
397            return descriptorTable.containsKey(c.getName());
398        }
399    
400        /**
401         * lookup a ClassDescriptor in the internal Hashtable
402         * @param strClassName a fully qualified class name as it is returned by Class.getName().
403         */
404        public ClassDescriptor getDescriptorFor(String strClassName) throws ClassNotPersistenceCapableException
405        {
406            ClassDescriptor result = discoverDescriptor(strClassName);
407            if (result == null)
408            {
409                throw new ClassNotPersistenceCapableException(strClassName + " not found in OJB Repository");
410            }
411            else
412            {
413                return result;
414            }
415        }
416    
417        /**
418         * lookup a ClassDescriptor in the internal Hashtable
419         */
420        public ClassDescriptor getDescriptorFor(Class c) throws ClassNotPersistenceCapableException
421        {
422            return this.getDescriptorFor(c.getName());
423        }
424    
425        /**
426         * Convenience for {@link #put(Class c, ClassDescriptor cld)}
427         */
428        public void setClassDescriptor(ClassDescriptor cld)
429        {
430            this.put(cld.getClassNameOfObject(), cld);
431        }
432    
433        /**
434         * Add a ClassDescriptor to the internal Hashtable<br>
435         * Set the Repository for ClassDescriptor
436         */
437        public void put(Class c, ClassDescriptor cld)
438        {
439            this.put(c.getName(), cld);
440        }
441    
442        /**
443         * Add a ClassDescriptor to the internal Hashtable<br>
444         * Set the Repository for ClassDescriptor
445         */
446        public void put(String classname, ClassDescriptor cld)
447        {
448            cld.setRepository(this); // BRJ
449            synchronized (descriptorTable)
450            {
451                descriptorTable.put(classname, cld);
452                List extentClasses = cld.getExtentClasses();
453                for (int i = 0; i < extentClasses.size(); ++i)
454                {
455                    addExtent(((Class) extentClasses.get(i)).getName(), cld);
456                }
457                changeDescriptorEvent();
458            }
459        }
460    
461        public void remove(String className)
462        {
463            synchronized (descriptorTable)
464            {
465                ClassDescriptor cld = (ClassDescriptor) descriptorTable.remove(className);
466                if(cld != null)
467                {
468                    // class itself could no longer be a extent
469                    Iterator it = descriptorTable.values().iterator();
470                    while (it.hasNext())
471                    {
472                        ((ClassDescriptor) it.next()).removeExtentClass(className);
473                    }
474                    removeExtent(className);
475                    List extentClasses = cld.getExtentClasses();
476                    for (int i = 0; i < extentClasses.size(); ++i)
477                    {
478                        removeExtent(((Class) extentClasses.get(i)).getName());
479                    }
480                    changeDescriptorEvent();
481                    // deregister classes using mapping of classes to multiple joined tables
482                    // the registration is done by the class-descriptor itself
483                    deregisterSuperClassMultipleJoinedTables(cld);
484                }
485            }
486        }
487    
488        public void remove(Class clazz)
489        {
490            remove(clazz.getName());
491        }
492    
493        private synchronized void changeDescriptorEvent()
494        {
495            m_multiMappedTableMap = null;
496            m_topLevelClassTable = null;
497            m_firstConcreteClassMap = null;
498            m_allConcreteSubClass = null;
499        }
500    
501        /**
502         * Returns an iterator over all managed {@link ClassDescriptor}.
503         */
504        public Iterator iterator()
505        {
506            /*
507            clone map to avoid synchronization problems
508            */
509            return ((HashMap)descriptorTable.clone()).values().iterator();
510        }
511    
512        /**
513         * Returns the defaultIsolationLevel.
514         * @return int
515         */
516        public int getDefaultIsolationLevel()
517        {
518            return defaultIsolationLevel;
519        }
520    
521        /**
522         * Sets the defaultIsolationLevel.
523         * @param defaultIsolationLevel The defaultIsolationLevel to set
524         */
525        public void setDefaultIsolationLevel(int defaultIsolationLevel)
526        {
527            this.defaultIsolationLevel = defaultIsolationLevel;
528        }
529    
530        /**
531         * returns a string representation
532         */
533        public String toString()
534        {
535            /**
536             * Kuali Foundation modification -- 6/19/2009
537             */
538            synchronized (descriptorTable) {                        
539            /**
540             * End of Kuali Foundation modification
541             */
542                    Iterator it = descriptorTable.entrySet().iterator();
543                    ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
544                    String className = "class name: ";
545                    String tableName = "> table name: ";
546                    while (it.hasNext())
547                    {
548                        Map.Entry me = (Map.Entry) it.next();
549                        ClassDescriptor descriptor = (ClassDescriptor) me.getValue();
550                        buf.append(className + me.getKey() + " =", tableName + descriptor.getFullTableName());
551                    }
552                    return buf.toString();
553            /**
554             * Kuali Foundation modification -- 6/19/2009
555             */
556                    }
557            /**
558             * End of Kuali Foundation modification
559             */
560        }
561    
562        /*
563         * @see XmlCapable#toXML()
564         */
565        public String toXML()
566        {
567            String eol = SystemUtils.LINE_SEPARATOR;
568            StringBuffer buf = new StringBuffer();
569    
570            // write all ClassDescriptors
571            Iterator i = this.iterator();
572            while (i.hasNext())
573            {
574                buf.append(((XmlCapable) i.next()).toXML() + eol);
575            }
576            return buf.toString();
577        }
578    
579        /**
580         * returns IsolationLevel literal as matching
581         * to the corresponding id
582         * @return the IsolationLevel literal
583         */
584        protected String getIsolationLevelAsString()
585        {
586            if (defaultIsolationLevel == IL_READ_UNCOMMITTED)
587            {
588                return LITERAL_IL_READ_UNCOMMITTED;
589            }
590            else if (defaultIsolationLevel == IL_READ_COMMITTED)
591            {
592                return LITERAL_IL_READ_COMMITTED;
593            }
594            else if (defaultIsolationLevel == IL_REPEATABLE_READ)
595            {
596                return LITERAL_IL_REPEATABLE_READ;
597            }
598            else if (defaultIsolationLevel == IL_SERIALIZABLE)
599            {
600                return LITERAL_IL_SERIALIZABLE;
601            }
602            else if (defaultIsolationLevel == IL_OPTIMISTIC)
603            {
604                return LITERAL_IL_OPTIMISTIC;
605            }
606            return LITERAL_IL_READ_UNCOMMITTED;
607        }
608    
609        /**
610         * Starts by looking to see if the <code>className</code> is
611         * already mapped specifically to the descritpor repository.
612         * If the <code>className</code> is not specifically mapped we
613         * look at the <code>className</code>'s parent class for a mapping.
614         * We do this until the parent class is of the type
615         * <code>java.lang.Object</code>.  If no mapping was found,
616         * <code>null</code> is returned.  Mappings successfuly discovered
617         * through inheritence are added to the internal table of
618         * class descriptors to improve performance on subsequent requests
619         * for those classes.
620         *
621         * <br/>
622         * author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
623         * @param className name of class whose descriptor we need to find.
624         * @return ClassDescriptor for <code>className</code> or <code>null</code>
625         * if no ClassDescriptor could be located.
626         */
627        protected ClassDescriptor discoverDescriptor(String className)
628        {
629            ClassDescriptor result = (ClassDescriptor) descriptorTable.get(className);
630            if (result == null)
631            {
632                Class clazz;
633                try
634                {
635                    clazz = ClassHelper.getClass(className, true);
636                }
637                catch (ClassNotFoundException e)
638                {
639                    throw new OJBRuntimeException("Class, " + className + ", could not be found.", e);
640                }
641                result = discoverDescriptor(clazz);
642             }
643            return result;
644        }
645    
646        /**
647         * Internal method for recursivly searching for a class descriptor that avoids
648         * class loading when we already have a class object.
649         *
650         * @param clazz The class whose descriptor we need to find
651         * @return ClassDescriptor for <code>clazz</code> or <code>null</code>
652         *         if no ClassDescriptor could be located.
653         */
654        private ClassDescriptor discoverDescriptor(Class clazz)
655        {
656            ClassDescriptor result = (ClassDescriptor) descriptorTable.get(clazz.getName());
657    
658            if (result == null)
659            {
660                Class superClass = clazz.getSuperclass();
661                // only recurse if the superClass is not java.lang.Object
662                if (superClass != null)
663                {
664                    result = discoverDescriptor(superClass);
665                }
666                if (result == null)
667                {
668                    // we're also checking the interfaces as there could be normal
669                    // mappings for them in the repository (using factory-class,
670                    // factory-method, and the property field accessor)
671                    Class[] interfaces = clazz.getInterfaces();
672    
673                    if ((interfaces != null) && (interfaces.length > 0))
674                    {
675                        for (int idx = 0; (idx < interfaces.length) && (result == null); idx++)
676                        {
677                            result = discoverDescriptor(interfaces[idx]);
678                        }
679                    }
680                }
681    
682                if (result != null)
683                {
684                    /**
685                     * Kuali Foundation modification -- 6/19/2009
686                     */
687                    synchronized (descriptorTable) {
688                    /**
689                     * End of Kuali Foundation modification
690                     */
691                            descriptorTable.put(clazz.getName(), result);
692                    /**
693                     * Kuali Foundation modification -- 6/19/2009
694                     */
695                    }
696                    /**
697                     * End of Kuali Foundation modification
698                     */
699                }
700            }
701            return result;
702        }
703    
704        /**
705         * Internal used! Register sub-classes of specified class when mapping class to
706         * multiple joined tables is used. Normally this method is called by the {@link ClassDescriptor}
707         * itself.
708         *
709         * @param cld The {@link ClassDescriptor} of the class to register.
710         */
711        protected void registerSuperClassMultipleJoinedTables(ClassDescriptor cld)
712        {
713            /*
714            arminw: Sadly, we can't map to sub class-descriptor, because it's not guaranteed
715            that they exist when this method is called. Thus we map the class instance instead
716            of the class-descriptor.
717            */
718            if(cld.getBaseClass() != null)
719            {
720                try
721                {
722                    Class superClass = ClassHelper.getClass(cld.getBaseClass());
723                    Class currentClass = cld.getClassOfObject();
724                    synchronized(descriptorTable)
725                    {
726                        List subClasses = (List) superClassMultipleJoinedTablesMap.get(superClass);
727                        if(subClasses == null)
728                        {
729                            subClasses = new ArrayList();
730                            superClassMultipleJoinedTablesMap.put(superClass,  subClasses);
731                        }
732                        if(!subClasses.contains(currentClass))
733                        {
734                            if(log.isDebugEnabled())
735                            {
736                                log.debug("(MultipleJoinedTables): Register sub-class '" + currentClass
737                                        + "' for class '" + superClass);
738                            }
739                            subClasses.add(currentClass);
740                        }
741                    }
742                }
743                catch(Exception e)
744                {
745                    throw new MetadataException("Can't register super class '" + cld.getBaseClass()
746                            + "' for class-descriptor: " + cld, e);
747                }
748            }
749        }
750    
751        /**
752         * Internal used! Deregister sub-classes of specified class when mapping to multiple joined tables
753         * is used. Normally this method is called when {@link #remove(Class)} a class.
754         *
755         * @param cld The {@link ClassDescriptor} of the class to register.
756         */
757        protected void deregisterSuperClassMultipleJoinedTables(ClassDescriptor cld)
758        {
759            try
760            {
761                Class currentClass = cld.getClassOfObject();
762                synchronized(descriptorTable)
763                {
764                    // first remove registered sub-classes for current class
765                    List subClasses = (List) superClassMultipleJoinedTablesMap.remove(currentClass);
766                    if(subClasses != null && log.isDebugEnabled())
767                    {
768                        log.debug("(MultipleJoinedTables): Deregister class " + currentClass
769                                + " with sub classes " + subClasses);
770                    }
771                    if(cld.getBaseClass() != null)
772                    {
773                        // then remove sub-class entry of current class for super-class
774                        Class superClass = ClassHelper.getClass(cld.getBaseClass());
775                        subClasses = (List) superClassMultipleJoinedTablesMap.get(superClass);
776                        if(subClasses != null)
777                        {
778                            boolean removed = subClasses.remove(currentClass);
779                            if(removed && log.isDebugEnabled())
780                            {
781                                log.debug("(MultipleJoinedTables): Remove sub-class entry '" + currentClass
782                                + "' in mapping for class '" + superClass + "'");
783                            }
784                        }
785                    }
786                }
787            }
788            catch(Exception e)
789            {
790                throw new MetadataException("Can't deregister super class '" + cld.getBaseClass()
791                        + "' for class-descriptor: " + cld, e);
792            }
793        }
794    
795        /**
796         * Return <em>sub-classes</em> of the specified class using the
797         * <em>"super"-Reference</em> concept.
798         * @param cld The {@link ClassDescriptor} of the class to search for sub-classes.
799         * @param wholeTree If set <em>true</em>, the whole sub-class tree of the specified
800         * class will be returned. If <em>false</em> only the direct sub-classes of the specified class
801         * will be returned.
802         * @return An array of <em>sub-classes</em> for the specified class.
803         */
804        public Class[] getSubClassesMultipleJoinedTables(ClassDescriptor cld, boolean wholeTree)
805        {
806            ArrayList result = new ArrayList();
807            createResultSubClassesMultipleJoinedTables(result, cld, wholeTree);
808            return (Class[]) result.toArray(new Class[result.size()]);
809        }
810    
811        /**
812         * Add all sub-classes using multiple joined tables feature for specified class.
813         * @param result The list to add results.
814         * @param cld The {@link ClassDescriptor} of the class to search for sub-classes.
815         * @param wholeTree If set <em>true</em>, the whole sub-class tree of the specified
816         * class will be returned. If <em>false</em> only the direct sub-classes of the specified class
817         * will be returned.
818         */
819        private void createResultSubClassesMultipleJoinedTables(List result, ClassDescriptor cld, boolean wholeTree)
820        {
821            List tmp = (List) superClassMultipleJoinedTablesMap.get(cld.getClassOfObject());
822            if(tmp != null)
823            {
824                result.addAll(tmp);
825                if(wholeTree)
826                {
827                    for(int i = 0; i < tmp.size(); i++)
828                    {
829                        Class subClass = (Class) tmp.get(i);
830                        ClassDescriptor subCld = getDescriptorFor(subClass);
831                        createResultSubClassesMultipleJoinedTables(result, subCld, wholeTree);
832                    }
833                }
834            }
835        }
836    
837        protected void finalize() throws Throwable
838        {
839            log.info("# finalize DescriptorRepository instance #");
840            super.finalize();
841        }
842    }