001    package org.apache.ojb.broker.core;
002    
003    /* Copyright 2003-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.lang.reflect.Constructor;
019    import java.util.Collection;
020    import java.util.Collections;
021    import java.util.Enumeration;
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Set;
026    
027    import org.apache.commons.lang.ObjectUtils;
028    import org.apache.ojb.broker.Identity;
029    import org.apache.ojb.broker.IdentityFactory;
030    import org.apache.ojb.broker.ManageableCollection;
031    import org.apache.ojb.broker.MtoNImplementor;
032    import org.apache.ojb.broker.OptimisticLockException;
033    import org.apache.ojb.broker.PBKey;
034    import org.apache.ojb.broker.PBState;
035    import org.apache.ojb.broker.PersistenceBrokerException;
036    import org.apache.ojb.broker.TransactionAbortedException;
037    import org.apache.ojb.broker.TransactionInProgressException;
038    import org.apache.ojb.broker.TransactionNotInProgressException;
039    import org.apache.ojb.broker.accesslayer.ChainingIterator;
040    import org.apache.ojb.broker.accesslayer.ConnectionManagerFactory;
041    import org.apache.ojb.broker.accesslayer.ConnectionManagerIF;
042    import org.apache.ojb.broker.accesslayer.JdbcAccess;
043    import org.apache.ojb.broker.accesslayer.JdbcAccessFactory;
044    import org.apache.ojb.broker.accesslayer.OJBIterator;
045    import org.apache.ojb.broker.accesslayer.PagingIterator;
046    import org.apache.ojb.broker.accesslayer.PkEnumeration;
047    import org.apache.ojb.broker.accesslayer.RelationshipPrefetcherFactory;
048    import org.apache.ojb.broker.accesslayer.StatementManagerFactory;
049    import org.apache.ojb.broker.accesslayer.StatementManagerIF;
050    import org.apache.ojb.broker.accesslayer.sql.SqlGenerator;
051    import org.apache.ojb.broker.accesslayer.sql.SqlGeneratorFactory;
052    import org.apache.ojb.broker.cache.MaterializationCache;
053    import org.apache.ojb.broker.cache.ObjectCache;
054    import org.apache.ojb.broker.cache.ObjectCacheFactory;
055    import org.apache.ojb.broker.cache.ObjectCacheInternal;
056    import org.apache.ojb.broker.core.proxy.AbstractProxyFactory;
057    import org.apache.ojb.broker.core.proxy.CollectionProxy;
058    import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
059    import org.apache.ojb.broker.core.proxy.IndirectionHandler;
060    import org.apache.ojb.broker.core.proxy.ProxyFactory;
061    import org.apache.ojb.broker.core.proxy.VirtualProxy;
062    import org.apache.ojb.broker.metadata.ClassDescriptor;
063    import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
064    import org.apache.ojb.broker.metadata.CollectionDescriptor;
065    import org.apache.ojb.broker.metadata.DescriptorRepository;
066    import org.apache.ojb.broker.metadata.FieldDescriptor;
067    import org.apache.ojb.broker.metadata.MetadataManager;
068    import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
069    import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
070    import org.apache.ojb.broker.query.Query;
071    import org.apache.ojb.broker.query.QueryByIdentity;
072    import org.apache.ojb.broker.query.QueryBySQL;
073    import org.apache.ojb.broker.util.BrokerHelper;
074    import org.apache.ojb.broker.util.IdentityArrayList;
075    import org.apache.ojb.broker.util.ObjectModification;
076    import org.apache.ojb.broker.util.logging.Logger;
077    import org.apache.ojb.broker.util.logging.LoggerFactory;
078    import org.apache.ojb.broker.util.sequence.SequenceManager;
079    import org.apache.ojb.broker.util.sequence.SequenceManagerFactory;
080    
081    /**
082     * The PersistenceBrokerImpl is an implementation of the PersistenceBroker
083     * Interface that specifies a persistence mechanism for Java objects.
084     * This Concrete implementation provides an object relational mapping
085     * and allows to store and retrieve arbitrary objects in/from relational
086     * databases accessed by JDBC.
087     *
088     * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
089     * @author <a href="mailto:leandro@ibnetwork.com.br">Leandro Rodrigo Saad Cruz<a>
090     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird<a>
091     * @author <a href="mailto:jbraeuchi@gmx.ch">Jakob Braeuchi</a>
092     *
093     * @version $Id: PersistenceBrokerImpl.java,v 1.4 2008-07-08 15:53:48 sfheise Exp $
094     */
095    public class PersistenceBrokerImpl extends PersistenceBrokerAbstractImpl implements PBState
096    {
097        private Logger logger = LoggerFactory.getLogger(PersistenceBrokerImpl.class);
098    
099        protected PersistenceBrokerFactoryIF pbf;
100        protected BrokerHelper brokerHelper;
101        protected MtoNBroker mtoNBroker;
102        protected QueryReferenceBroker referencesBroker;
103    
104        /**
105         * signs if this broker was closed
106         */
107        private boolean isClosed;
108        /**
109         * Reflects the transaction status of this instance.
110         */
111        private boolean inTransaction;
112        /**
113         * Flag indicate that this PB instance is handled
114         * in a managed environment.
115         */
116        private boolean managed;
117        private MaterializationCache objectCache;
118        /**
119         * m_DbAccess is used to do all Jdbc related work: connecting, executing...
120         */
121        private JdbcAccess dbAccess;
122        /**
123         * holds mapping information for all classes to be treated by PersistenceBroker
124         */
125        private DescriptorRepository descriptorRepository = null;
126        private ConnectionManagerIF connectionManager = null;
127        private SequenceManager sequenceManager = null;
128        private StatementManagerIF statementManager = null;
129        private SqlGenerator sqlGenerator;
130        private IdentityFactory identityFactory;
131        private RelationshipPrefetcherFactory relationshipPrefetcherFactory;
132        private ProxyFactory proxyFactory;
133        private PBKey pbKey;
134    
135        /**
136         * List of objects being stored now, allows to avoid infinite
137         * recursion storeCollections -> storeReferences -> storeCollections...
138         *
139         */
140        /*
141        we use an object identity based List to compare objects to prevent problems
142        with user implemented equals/hashCode methods of persistence capable objects
143        (e.g. objects are equals but PK fields not)
144        */
145        private List nowStoring = new IdentityArrayList();
146    
147        /**
148         * Lists for object registration during delete operations.
149         * We reuse these list to avoid excessive object creation.
150         * @see #clearRegistrationLists
151         */
152        /*
153        arminw: list was cleared before delete method end. Internal we only
154        call doDelete(...) method. Same procedure as 'nowStoring'
155    
156        we use an object identity based List to compare objects to prevent problems
157        with user implemented equals/hashCode methods of persistence capable objects
158        (e.g. objects are equals but PK fields not)
159        */
160        private List markedForDelete = new IdentityArrayList();
161    
162        /**
163         * The set of identities of all deleted objects during current transaction
164         */
165        /*
166        olegnitz: this is the only way I know that solves the following problem
167        of batch mode: if one does store() after delete() for the same OID,
168        the broker checks whether the given OID exists in database to decide
169        which action to do: INSERT or UPDATE. If the preceding DELETE statement is
170        still in batch (not executed yet), then the OID exists in database so
171        the broker does UPDATE. Due the the following set of deleted OIDs
172        the broker will know that it should do INSERT.
173        */
174        private Set deletedDuringTransaction = new HashSet();
175    
176        /**
177         * Constructor used by {@link PersistenceBrokerFactoryIF} implementation.
178         */
179        public PersistenceBrokerImpl(PBKey key, PersistenceBrokerFactoryIF pbf)
180        {
181            refresh();
182            if(key == null) throw new PersistenceBrokerException("Could not instantiate broker with PBKey 'null'");
183            this.pbf = pbf;
184            this.pbKey = key;
185            /*
186            be careful when changing initializing order
187            */
188            brokerHelper = new BrokerHelper(this);
189            connectionManager = ConnectionManagerFactory.getInstance().createConnectionManager(this);
190            /*
191            TODO: find better solution
192            MaterializationCache is a interim solution help to solve the problem of not full
193            materialized object reads by concurrent threads and will be replaced when
194            the new real two-level cache was introduced
195            */
196            objectCache = ObjectCacheFactory.getInstance().createObjectCache(this);
197            sequenceManager = SequenceManagerFactory.getSequenceManager(this);
198            dbAccess = JdbcAccessFactory.getInstance().createJdbcAccess(this);
199            statementManager = StatementManagerFactory.getInstance().createStatementManager(this);
200            sqlGenerator = SqlGeneratorFactory.getInstance().createSqlGenerator(
201                            connectionManager.getSupportedPlatform());
202            mtoNBroker = new MtoNBroker(this);
203            referencesBroker = new QueryReferenceBroker(this);
204            identityFactory = new IdentityFactoryImpl(this);
205            relationshipPrefetcherFactory = new RelationshipPrefetcherFactory(this);
206            proxyFactory = AbstractProxyFactory.getProxyFactory();
207        }
208    
209        public MaterializationCache getInternalCache()
210        {
211            return objectCache;
212        }
213    
214        public IdentityFactory serviceIdentity()
215        {
216            return this.identityFactory;
217        }
218    
219        public SqlGenerator serviceSqlGenerator()
220        {
221            return this.sqlGenerator;
222        }
223    
224        public StatementManagerIF serviceStatementManager()
225        {
226            return statementManager;
227        }
228    
229        public JdbcAccess serviceJdbcAccess()
230        {
231            return dbAccess;
232        }
233    
234        public ConnectionManagerIF serviceConnectionManager()
235        {
236            return connectionManager;
237        }
238    
239        public SequenceManager serviceSequenceManager()
240        {
241            return this.sequenceManager;
242        }
243    
244        public BrokerHelper serviceBrokerHelper()
245        {
246            return this.brokerHelper;
247        }
248    
249        public ObjectCache serviceObjectCache()
250        {
251            return this.objectCache;
252        }
253    
254        public QueryReferenceBroker getReferenceBroker()
255        {
256            return this.referencesBroker;
257        }
258    
259        public RelationshipPrefetcherFactory getRelationshipPrefetcherFactory()
260        {
261            return relationshipPrefetcherFactory;
262        }
263    
264        public boolean isClosed()
265        {
266            return this.isClosed;
267        }
268    
269        public void setClosed(boolean closed)
270        {
271            // When lookup the PB instance from pool method setClosed(false)
272            // was called before returning instance from pool, in this case
273            // OJB have to refresh the instance.
274            if(!closed)
275            {
276                refresh();
277            }
278            this.isClosed = closed;
279        }
280    
281        /**
282         * If <em>true</em> this instance is handled by a managed
283         * environment - registered within a JTA transaction.
284         */
285        public boolean isManaged()
286        {
287            return managed;
288        }
289    
290        /**
291         * Set <em>true</em> if this instance is registered within a
292         * JTA transaction. On {@link #close()} call this flag was reset
293         * to <em>false</em> automatic.
294         */
295        public void setManaged(boolean managed)
296        {
297            this.managed = managed;
298        }
299    
300        /**
301         * Lookup the current {@link DescriptorRepository} for
302         * this class. This method is responsible to keep this
303         * PB instance in sync with {@link MetadataManager}.
304         */
305        public void refresh()
306        {
307            // guarantee that refreshed use initial status
308            setInTransaction(false);
309            this.descriptorRepository = MetadataManager.getInstance().getRepository();
310        }
311    
312        /**
313         * Release all resources used by this
314         * class - CAUTION: No further operations can be
315         * done with this instance after calling this method.
316         */
317        public void destroy()
318        {
319            removeAllListeners();
320            if (connectionManager != null)
321            {
322                if(connectionManager.isInLocalTransaction())
323                {
324                    connectionManager.localRollback();
325                }
326                connectionManager.releaseConnection();
327            }
328            this.setClosed(true);
329    
330            this.descriptorRepository = null;
331            this.pbKey = null;
332            this.pbf = null;
333            this.connectionManager = null;
334            this.dbAccess = null;
335            this.objectCache = null;
336            this.sequenceManager = null;
337            this.sqlGenerator = null;
338            this.statementManager = null;
339        }
340    
341        public PBKey getPBKey()
342        {
343            return pbKey;
344        }
345    
346        public void setPBKey(PBKey key)
347        {
348            this.pbKey = key;
349        }
350    
351        /**
352         * @see org.apache.ojb.broker.PersistenceBroker#close()
353         */
354        public boolean close()
355        {
356            /**
357             * MBAIRD: if we call close on a broker that is in a transaction,
358             * we should just abort whatever it's doing.
359             */
360            if (isInTransaction())
361            {
362                logger.error("Broker is still in PB-transaction, do automatic abort before close!");
363                abortTransaction();
364            }
365            if (logger.isDebugEnabled())
366            {
367                logger.debug("PB.close was called: " + this);
368            }
369            try
370            {
371                fireBrokerEvent(BEFORE_CLOSE_EVENT);
372                clearRegistrationLists();
373                referencesBroker.removePrefetchingListeners();
374                if (connectionManager != null)
375                {
376                    connectionManager.releaseConnection();
377                    /*
378                    arminw:
379                    set batch mode explicit to 'false'. Using
380    
381                    connectionManager.setBatchMode(
382                            connectionManager.getConnectionDescriptor().getBatchMode());
383    
384                    cause many unexpected junit failures/errors when running
385                    test suite with batch-mode 'true' setting.
386                    */
387                    connectionManager.setBatchMode(false);
388                }
389            }
390            finally
391            {
392                // reset flag indicating use in managed environment
393                setManaged(false);
394                // free current used DescriptorRepository reference
395                descriptorRepository = null;
396                removeAllListeners();
397                this.setClosed(true);
398            }
399            return true;
400        }
401    
402        /**
403         * Abort and close the transaction.
404         * Calling abort abandons all persistent object modifications and releases the
405         * associated locks.
406         * If transaction is not in progress a TransactionNotInProgressException is thrown
407         */
408        public synchronized void abortTransaction() throws TransactionNotInProgressException
409        {
410            if(isInTransaction())
411            {
412                fireBrokerEvent(BEFORE_ROLLBACK_EVENT);
413                setInTransaction(false);
414                clearRegistrationLists();
415                referencesBroker.removePrefetchingListeners();
416                /*
417                arminw:
418                check if we in local tx, before do local rollback
419                Necessary, because ConnectionManager may do a rollback by itself
420                or in managed environments the used connection is already be closed
421                */
422                if(connectionManager.isInLocalTransaction()) this.connectionManager.localRollback();
423                fireBrokerEvent(AFTER_ROLLBACK_EVENT);
424            }
425        }
426    
427        /**
428         * begin a transaction against the underlying RDBMS.
429         * Calling <code>beginTransaction</code> multiple times,
430         * without an intervening call to <code>commitTransaction</code> or <code>abortTransaction</code>,
431         * causes the exception <code>TransactionInProgressException</code> to be thrown
432         * on the second and subsequent calls.
433         */
434        public synchronized void beginTransaction() throws TransactionInProgressException, TransactionAbortedException
435        {
436            if (isInTransaction())
437            {
438                throw new TransactionInProgressException("PersistenceBroker is already in transaction");
439            }
440            fireBrokerEvent(BEFORE_BEGIN_EVENT);
441            setInTransaction(true);
442            this.connectionManager.localBegin();
443            fireBrokerEvent(AFTER_BEGIN_EVENT);
444        }
445    
446        /**
447         * Commit and close the transaction.
448         * Calling <code>commit</code> commits to the database all
449         * UPDATE, INSERT and DELETE statements called within the transaction and
450         * releases any locks held by the transaction.
451         * If beginTransaction() has not been called before a
452         * TransactionNotInProgressException exception is thrown.
453         * If the transaction cannot be commited a TransactionAbortedException exception is thrown.
454         */
455        public synchronized void commitTransaction() throws TransactionNotInProgressException, TransactionAbortedException
456        {
457            if (!isInTransaction())
458            {
459                throw new TransactionNotInProgressException("PersistenceBroker is NOT in transaction, can't commit");
460            }
461            fireBrokerEvent(BEFORE_COMMIT_EVENT);
462            setInTransaction(false);
463            clearRegistrationLists();
464            referencesBroker.removePrefetchingListeners();
465            /*
466            arminw:
467            In managed environments it should be possible to close a used connection before
468            the tx was commited, thus it will be possible that the PB instance is in PB-tx, but
469            the connection is already closed. To avoid problems check if CM is in local tx before
470            do the CM.commit call
471            */
472            if(connectionManager.isInLocalTransaction())
473            {
474                this.connectionManager.localCommit();
475            }
476            fireBrokerEvent(AFTER_COMMIT_EVENT);
477        }
478    
479        /**
480         * Deletes the concrete representation of the specified object in the underlying
481         * persistence system. This method is intended for use in top-level api or
482         * by internal calls.
483         *
484         * @param obj The object to delete.
485         * @param ignoreReferences With this flag the automatic deletion/unlinking
486         * of references can be suppressed (independent of the used auto-delete setting in metadata),
487         * except {@link org.apache.ojb.broker.metadata.SuperReferenceDescriptor}
488         * these kind of reference (descriptor) will always be performed. If <em>true</em>
489         * all "normal" referenced objects will be ignored, only the specified object is handled.
490         * @throws PersistenceBrokerException
491         */
492        public void delete(Object obj, boolean ignoreReferences) throws PersistenceBrokerException
493        {
494            if(isTxCheck() && !isInTransaction())
495            {
496                if(logger.isEnabledFor(Logger.ERROR))
497                {
498                    String msg = "No running PB-tx found. Please, only delete objects in context of a PB-transaction" +
499                        " to avoid side-effects - e.g. when rollback of complex objects.";
500                    try
501                    {
502                        throw new Exception("** Delete object without active PersistenceBroker transaction **");
503                    }
504                    catch(Exception e)
505                    {
506                        logger.error(msg, e);
507                    }
508                }
509            }
510            try
511            {
512                doDelete(obj, ignoreReferences);
513            }
514            finally
515            {
516                markedForDelete.clear();
517            }
518        }
519    
520        /**
521         * @see org.apache.ojb.broker.PersistenceBroker#delete
522         */
523        public void delete(Object obj) throws PersistenceBrokerException
524        {
525            delete(obj, false);
526        }
527    
528        /**
529         * do delete given object. Should be used by all intern classes to delete
530         * objects.
531         */
532        private void doDelete(Object obj, boolean ignoreReferences) throws PersistenceBrokerException
533        {
534            //logger.info("DELETING " + obj);
535            // object is not null
536            if (obj != null)
537            {
538                obj = getProxyFactory().getRealObject(obj);
539                /**
540                 * Kuali Foundation modification -- 8/24/2007
541                 */
542                if ( obj == null ) return;
543                /**
544                 * End of Kuali Foundation modification
545                 */
546                /**
547                 * MBAIRD
548                 * 1. if we are marked for delete already, avoid recursing on this object
549                 *
550                 * arminw:
551                 * use object instead Identity object in markedForDelete List,
552                 * because using objects we get a better performance. I can't find
553                 * side-effects in doing so.
554                 */
555                if (markedForDelete.contains(obj))
556                {
557                    return;
558                }
559                
560                ClassDescriptor cld = getClassDescriptor(obj.getClass());
561                //BRJ: check for valid pk
562                if (!serviceBrokerHelper().assertValidPkForDelete(cld, obj))
563                {
564                    String msg = "Cannot delete object without valid PKs. " + obj;
565                    logger.error(msg);
566                    return;
567                }
568                
569                /**
570                 * MBAIRD
571                 * 2. register object in markedForDelete map.
572                 */
573                markedForDelete.add(obj);
574                Identity oid = serviceIdentity().buildIdentity(cld, obj);
575    
576                // Invoke events on PersistenceBrokerAware instances and listeners
577                BEFORE_DELETE_EVENT.setTarget(obj);
578                fireBrokerEvent(BEFORE_DELETE_EVENT);
579                BEFORE_DELETE_EVENT.setTarget(null);
580    
581                // now perform deletion
582                performDeletion(cld, obj, oid, ignoreReferences);
583                     
584                // Invoke events on PersistenceBrokerAware instances and listeners
585                AFTER_DELETE_EVENT.setTarget(obj);
586                fireBrokerEvent(AFTER_DELETE_EVENT);
587                AFTER_DELETE_EVENT.setTarget(null);
588                            
589                // let the connection manager to execute batch
590                connectionManager.executeBatchIfNecessary();
591            }
592        }
593                     
594        /**
595         * This method perform the delete of the specified object
596         * based on the {@link org.apache.ojb.broker.metadata.ClassDescriptor}.
597         */
598        private void performDeletion(final ClassDescriptor cld, final Object obj, final Identity oid, final boolean ignoreReferences) throws PersistenceBrokerException
599        {
600                // 1. delete dependend collections
601                if (!ignoreReferences  && cld.getCollectionDescriptors().size() > 0)
602                {
603                    deleteCollections(obj, cld.getCollectionDescriptors());
604                }
605                // 2. delete object from directly mapped table
606                try
607                {
608                    dbAccess.executeDelete(cld, obj); // use obj not oid to delete, BRJ
609                }
610                catch(OptimisticLockException e)
611                {
612                    // ensure that the outdated object be removed from cache
613                    objectCache.remove(oid);
614                    throw e;
615                }
616    
617                // 3. Add OID to the set of deleted objects
618                deletedDuringTransaction.add(oid);
619    
620                // 4. delete dependend upon objects last to avoid FK violations
621                if (cld.getObjectReferenceDescriptors().size() > 0)
622                {
623                    deleteReferences(cld, obj, oid, ignoreReferences);
624                }
625                // remove obj from the object cache:
626                objectCache.remove(oid);
627        }
628    
629        /**
630         * Extent aware Delete by Query
631         * @param query
632         * @param cld
633         * @throws PersistenceBrokerException
634         */
635        private void deleteByQuery(Query query, ClassDescriptor cld) throws PersistenceBrokerException
636        {
637            if (logger.isDebugEnabled())
638            {
639                logger.debug("deleteByQuery " + cld.getClassNameOfObject() + ", " + query);
640            }
641    
642            if (query instanceof QueryBySQL)
643            {
644                String sql = ((QueryBySQL) query).getSql();
645                this.dbAccess.executeUpdateSQL(sql, cld);
646            }
647            else
648            {
649                // if query is Identity based transform it to a criteria based query first
650                if (query instanceof QueryByIdentity)
651                {
652                    QueryByIdentity qbi = (QueryByIdentity) query;
653                    Object oid = qbi.getExampleObject();
654                    // make sure it's an Identity
655                    if (!(oid instanceof Identity))
656                    {
657                        oid = serviceIdentity().buildIdentity(oid);
658                    }
659                    query = referencesBroker.getPKQuery((Identity) oid);
660                }
661    
662                if (!cld.isInterface())
663                {
664                    this.dbAccess.executeDelete(query, cld);
665                }
666    
667                // if class is an extent, we have to delete all extent classes too
668                String lastUsedTable = cld.getFullTableName();
669                if (cld.isExtent())
670                {
671                    Iterator extents = getDescriptorRepository().getAllConcreteSubclassDescriptors(cld).iterator();
672    
673                    while (extents.hasNext())
674                    {
675                        ClassDescriptor extCld = (ClassDescriptor) extents.next();
676    
677                        // read same table only once
678                        if (!extCld.getFullTableName().equals(lastUsedTable))
679                        {
680                            lastUsedTable = extCld.getFullTableName();
681                            this.dbAccess.executeDelete(query, extCld);
682                        }
683                    }
684                }
685    
686            }
687        }
688    
689        /**
690         * @see org.apache.ojb.broker.PersistenceBroker#deleteByQuery(Query)
691         */
692        public void deleteByQuery(Query query) throws PersistenceBrokerException
693        {
694            ClassDescriptor cld = getClassDescriptor(query.getSearchClass());
695            deleteByQuery(query, cld);
696        }
697    
698        /**
699         * Deletes references that <b>obj</b> points to.
700         * All objects which we have a FK poiting to (Via ReferenceDescriptors)
701         * will be deleted if auto-delete is true <b>AND</b>
702         * the member field containing the object reference is NOT null.
703         *
704         * @param obj Object which we will delete references for
705         * @param listRds list of ObjectRederenceDescriptors
706         * @param ignoreReferences With this flag the automatic deletion/unlinking
707         * of references can be suppressed (independent of the used auto-delete setting in metadata),
708         * except {@link org.apache.ojb.broker.metadata.SuperReferenceDescriptor}
709         * these kind of reference (descriptor) will always be performed.
710         * @throws PersistenceBrokerException if some goes wrong - please see the error message for details
711         */
712        private void deleteReferences(ClassDescriptor cld, Object obj, Identity oid, boolean ignoreReferences) throws PersistenceBrokerException
713        {
714            List listRds = cld.getObjectReferenceDescriptors();
715            // get all members of obj that are references and delete them
716            Iterator i = listRds.iterator();
717            while (i.hasNext())
718            {
719                ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
720                if ((!ignoreReferences && rds.getCascadingDelete() == ObjectReferenceDescriptor.CASCADE_OBJECT)
721                        || rds.isSuperReferenceDescriptor())
722                {
723                    Object referencedObject = rds.getPersistentField().get(obj);
724                    if (referencedObject != null)
725                    {
726                            if(rds.isSuperReferenceDescriptor())
727                            {
728                                    ClassDescriptor base = cld.getSuperClassDescriptor();
729                                    /*
730                                     arminw: If "table-per-subclass" inheritance is used we have to
731                                     guarantee that all super-class table entries are deleted too.
732                                     Thus we have to perform the recursive deletion of all super-class
733                                     table entries.
734                                     */
735                                    performDeletion(base, referencedObject, oid, ignoreReferences);
736                            }
737                            else
738                            {
739                                    doDelete(referencedObject, ignoreReferences);
740                            }
741                    }
742                }
743            }
744        }
745    
746        /**
747         * Deletes collections of objects poiting to <b>obj</b>.
748         * All object which have a FK poiting to this object (Via CollectionDescriptors)
749         * will be deleted if auto-delete is true <b>AND</b>
750         * the member field containing the object reference if NOT null.
751         *
752         * @param obj Object which we will delete collections for
753         * @param listCds list of ObjectReferenceDescriptors
754         * @throws PersistenceBrokerException if some goes wrong - please see the error message for details
755         */
756        private void deleteCollections(Object obj, List listCds) throws PersistenceBrokerException
757        {
758            // get all members of obj that are collections and delete all their elements
759            Iterator i = listCds.iterator();
760    
761            while (i.hasNext())
762            {
763                CollectionDescriptor cds = (CollectionDescriptor) i.next();
764                if(cds.getCascadingDelete() != ObjectReferenceDescriptor.CASCADE_NONE)
765                {
766                    if(cds.isMtoNRelation())
767                    {
768                        // if this is a m:n mapped table, remove entries from indirection table
769                        mtoNBroker.deleteMtoNImplementor(cds, obj);
770                    }
771                    /*
772                    if cascading delete is on, delete all referenced objects.
773                    NOTE: User has to take care to populate all referenced objects before delete
774                    the main object to avoid referential constraint violation
775                     */
776                    if (cds.getCascadingDelete() == ObjectReferenceDescriptor.CASCADE_OBJECT)
777                    {
778                        Object col = cds.getPersistentField().get(obj);
779                        if (col != null)
780                        {
781                            Iterator colIterator = BrokerHelper.getCollectionIterator(col);
782                            while (colIterator.hasNext())
783                            {
784                                doDelete(colIterator.next(), false);
785                            }
786                        }
787                    }
788                }
789            }
790        }
791    
792        /**
793         * Store an Object.
794         * @see org.apache.ojb.broker.PersistenceBroker#store(Object)
795         */
796        public void store(Object obj) throws PersistenceBrokerException
797        {
798            obj = extractObjectToStore(obj);
799            // only do something if obj != null
800            if(obj == null) return;
801    
802            ClassDescriptor cld = getClassDescriptor(obj.getClass());
803            /*
804            if one of the PK fields was null, we assume the objects
805            was new and needs insert
806            */
807            boolean insert = serviceBrokerHelper().hasNullPKField(cld, obj);
808            Identity oid = serviceIdentity().buildIdentity(cld, obj);
809            /*
810            if PK values are set, lookup cache or db to see whether object
811            needs insert or update
812            */
813            if (!insert)
814            {
815                insert = objectCache.lookup(oid) == null
816                    && !serviceBrokerHelper().doesExist(cld, oid, obj);
817            }
818            store(obj, oid, cld, insert);
819        }
820    
821        /**
822         * Check if the given object is <code>null</code> or an unmaterialized proxy object - in
823         * both cases <code>null</code> will be returned, else the given object itself or the
824         * materialized proxy object will be returned.
825         */
826        private Object extractObjectToStore(Object obj)
827        {
828            Object result = obj;
829            // only do something if obj != null
830            if(result != null)
831            {
832                // ProxyObjects only have to be updated if their real
833                // subjects have been loaded
834                result = getProxyFactory().getRealObjectIfMaterialized(obj);
835                // null for unmaterialized Proxy
836                if (result == null)
837                {
838                    if(logger.isDebugEnabled())
839                        logger.debug("No materialized object could be found -> nothing to store," +
840                                " object was " + ObjectUtils.identityToString(obj));
841                }
842            }
843            return result;
844        }
845    
846        /**
847         * Method which start the real store work (insert or update)
848         * and is intended for use by top-level api or internal calls.
849         *
850         * @param obj The object to store.
851         * @param oid The {@link Identity} of the object to store.
852         * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the object.
853         * @param insert If <em>true</em> an insert operation will be performed, else update operation.
854         * @param ignoreReferences With this flag the automatic storing/linking
855         * of references can be suppressed (independent of the used auto-update setting in metadata),
856         * except {@link org.apache.ojb.broker.metadata.SuperReferenceDescriptor}
857         * these kind of reference (descriptor) will always be performed. If <em>true</em>
858         * all "normal" referenced objects will be ignored, only the specified object is handled.
859         */
860        public void store(Object obj, Identity oid, ClassDescriptor cld,  boolean insert, boolean ignoreReferences)
861        {
862            if(obj == null || nowStoring.contains(obj))
863            {
864                return;
865            }
866    
867            /*
868            if the object has been deleted during this transaction,
869            then we must insert it
870            */
871            //System.out.println("## insert: " +insert + " / deleted: " + deletedDuringTransaction);
872            if (!insert)
873            {
874                insert = deletedDuringTransaction.contains(oid);
875            }
876    
877            //************************************************
878            // now store it:
879            if(isTxCheck() && !isInTransaction())
880            {
881                if(logger.isEnabledFor(Logger.ERROR))
882                {
883                    try
884                    {
885                        throw new Exception("** Try to store object without active PersistenceBroker transaction **");
886                    }
887                    catch(Exception e)
888                    {
889                        logger.error("No running tx found, please only store in context of an PB-transaction" +
890                        ", to avoid side-effects - e.g. when rollback of complex objects", e);
891                    }
892                }
893            }
894            // Invoke events on PersistenceBrokerAware instances and listeners
895            if (insert)
896            {
897                BEFORE_STORE_EVENT.setTarget(obj);
898                fireBrokerEvent(BEFORE_STORE_EVENT);
899                BEFORE_STORE_EVENT.setTarget(null);
900            }
901            else
902            {
903                BEFORE_UPDATE_EVENT.setTarget(obj);
904                fireBrokerEvent(BEFORE_UPDATE_EVENT);
905                BEFORE_UPDATE_EVENT.setTarget(null);
906            }
907    
908            try
909            {
910                nowStoring.add(obj);
911                storeToDb(obj, cld, oid, insert, ignoreReferences);
912            }
913            finally
914            {
915                // to optimize calls to DB don't remove already stored objects
916                nowStoring.remove(obj);
917            }
918    
919    
920            // Invoke events on PersistenceBrokerAware instances and listeners
921            if (insert)
922            {
923                AFTER_STORE_EVENT.setTarget(obj);
924                fireBrokerEvent(AFTER_STORE_EVENT);
925                AFTER_STORE_EVENT.setTarget(null);
926            }
927            else
928            {
929                AFTER_UPDATE_EVENT.setTarget(obj);
930                fireBrokerEvent(AFTER_UPDATE_EVENT);
931                AFTER_UPDATE_EVENT.setTarget(null);
932            }
933            // end of store operation
934            //************************************************
935    
936            // if the object was stored, remove it from deleted set
937            if(deletedDuringTransaction.size() > 0) deletedDuringTransaction.remove(oid);
938    
939            // let the connection manager to execute batch
940            connectionManager.executeBatchIfNecessary();
941        }
942    
943        /**
944         * Internal used method which start the real store work.
945         */
946        protected void store(Object obj, Identity oid, ClassDescriptor cld,  boolean insert)
947        {
948            store(obj, oid, cld, insert, false);
949        }
950    
951        /**
952         * Store all object references that <b>obj</b> points to.
953         * All objects which we have a FK pointing to (Via ReferenceDescriptors) will be
954         * stored if auto-update is true <b>AND</b> the member field containing the object
955         * reference is NOT null.
956         * With flag <em>ignoreReferences</em> the storing/linking
957         * of references can be suppressed (independent of the used auto-update setting),
958         * except {@link org.apache.ojb.broker.metadata.SuperReferenceDescriptor}
959         * these kind of reference (descriptor) will always be performed.
960         *
961         * @param obj Object which we will store references for
962         */
963        private void storeReferences(Object obj, ClassDescriptor cld, boolean insert, boolean ignoreReferences)
964        {
965            // get all members of obj that are references and store them
966            Collection listRds = cld.getObjectReferenceDescriptors();
967            // return if nothing to do
968            if(listRds == null || listRds.size() == 0)
969            {
970                return;
971            }
972            Iterator i = listRds.iterator();
973            while (i.hasNext())
974            {
975                ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
976                /*
977                arminw: the super-references (used for table per subclass inheritance) must
978                be performed in any case. The "normal" 1:1 references can be ignored when
979                flag "ignoreReferences" is set
980                */
981                if((!ignoreReferences && rds.getCascadingStore() != ObjectReferenceDescriptor.CASCADE_NONE)
982                        || rds.isSuperReferenceDescriptor())
983                {
984                    storeAndLinkOneToOne(false, obj, cld, rds, insert);
985                }
986            }
987        }
988    
989        /**
990         * Store/Link 1:1 reference.
991         *
992         * @param obj real object the reference starts
993         * @param rds {@link ObjectReferenceDescriptor} of the real object
994         * @param insert flag for insert operation
995         */
996        private void storeAndLinkOneToOne(boolean onlyLink, Object obj, ClassDescriptor cld,
997                                          ObjectReferenceDescriptor rds, boolean insert)
998        {
999            Object ref = rds.getPersistentField().get(obj);
1000            if (!onlyLink && rds.getCascadingStore() == ObjectReferenceDescriptor.CASCADE_OBJECT)
1001            {
1002                if(rds.isSuperReferenceDescriptor())
1003                {
1004                    ClassDescriptor superCld = rds.getClassDescriptor().getSuperClassDescriptor();
1005                    Identity oid = serviceIdentity().buildIdentity(superCld, ref);
1006                    storeToDb(ref, superCld, oid, insert);
1007                }
1008                else store(ref);
1009            }
1010        
1011            /**
1012             * Kuali Foundation modification -- 1/10/2008
1013             */
1014            ref = getProxyFactory().getRealObject(ref);
1015            /**
1016             * End of Kuali Foundation modification
1017             */
1018            link(obj, cld, rds, ref, insert);
1019        }
1020    
1021        /**
1022         * Store/Link collections of objects poiting to <b>obj</b>.
1023         * More info please see comments in source.
1024         *
1025         * @param obj real object which we will store collections for
1026         * @throws PersistenceBrokerException if some goes wrong - please see the error message for details
1027         */
1028        private void storeCollections(Object obj, ClassDescriptor cld, boolean insert) throws PersistenceBrokerException
1029        {
1030            // get all members of obj that are collections and store all their elements
1031            Collection listCods = cld.getCollectionDescriptors();
1032            // return if nothing to do
1033            if (listCods.size() == 0)
1034            {
1035                return;
1036            }
1037            Iterator i = listCods.iterator();
1038            while (i.hasNext())
1039            {
1040                CollectionDescriptor cod = (CollectionDescriptor) i.next();
1041    
1042                // if CASCADE_NONE was set, do nothing with referenced objects
1043                if (cod.getCascadingStore() != ObjectReferenceDescriptor.CASCADE_NONE)
1044                {
1045                    Object referencedObjects = cod.getPersistentField().get(obj);
1046                    if (cod.isMtoNRelation())
1047                    {
1048                        storeAndLinkMtoN(false, obj, cod, referencedObjects, insert);
1049                    }
1050                    else
1051                    {
1052                        storeAndLinkOneToMany(false, obj, cod, referencedObjects, insert);
1053                    }
1054    
1055                    // BRJ: only when auto-update = object (CASCADE_OBJECT)
1056                    //
1057                    if ((cod.getCascadingStore() == ObjectReferenceDescriptor.CASCADE_OBJECT)
1058                            && (referencedObjects instanceof ManageableCollection))
1059                    {
1060                        ((ManageableCollection) referencedObjects).afterStore(this);
1061                    }
1062                }
1063            }
1064        }
1065    
1066        /**
1067         * Store/Link m:n collection references.
1068         *
1069         * @param obj real object the reference starts
1070         * @param cod {@link CollectionDescriptor} of the real object
1071         * @param referencedObjects the referenced objects ({@link ManageableCollection} or Collection or Array) or null
1072         * @param insert flag for insert operation
1073         */
1074        private void storeAndLinkMtoN(boolean onlyLink, Object obj, CollectionDescriptor cod,
1075                                      Object referencedObjects, boolean insert)
1076        {
1077            /*
1078            - if the collection is a collectionproxy and it's not already loaded
1079            no need to perform an update on the referenced objects
1080            - on insert we link and insert the referenced objects, because the proxy
1081            collection maybe "inherited" from the object before the PK was replaced
1082            */
1083            if(insert || !(referencedObjects instanceof CollectionProxy
1084                            && !((CollectionProxy) referencedObjects).isLoaded()))
1085            {
1086                // if referenced objects are null, assign empty list
1087                if(referencedObjects == null)
1088                {
1089                    referencedObjects = Collections.EMPTY_LIST;
1090                }
1091                /*
1092                NOTE: Take care of referenced objects, they could be of type Collection or
1093                an Array or of type ManageableCollection, thus it is not guaranteed that we
1094                can cast to Collection!!!
1095    
1096                if we store an object with m:n reference and no references could be
1097                found, we remove all entires of given object in indirection table
1098                */
1099                Iterator referencedObjectsIterator;
1100    
1101                if(!onlyLink && cod.getCascadingStore() == ObjectReferenceDescriptor.CASCADE_OBJECT)
1102                {
1103                    referencedObjectsIterator = BrokerHelper.getCollectionIterator(referencedObjects);
1104                    while (referencedObjectsIterator.hasNext())
1105                    {
1106                        store(referencedObjectsIterator.next());
1107                    }
1108                }
1109    
1110                Collection existingMtoNKeys;
1111                if(!insert)
1112                {
1113                    existingMtoNKeys = mtoNBroker.getMtoNImplementor(cod, obj);
1114                    // we can't reuse iterator
1115                    referencedObjectsIterator = BrokerHelper.getCollectionIterator(referencedObjects);
1116                    // remove all entries in indirection table which not be part of referenced objects
1117                    mtoNBroker.deleteMtoNImplementor(cod, obj, referencedObjectsIterator, existingMtoNKeys);
1118                }
1119                else
1120                {
1121                    existingMtoNKeys = Collections.EMPTY_LIST;
1122                }
1123                // we can't reuse iterator
1124                referencedObjectsIterator = BrokerHelper.getCollectionIterator(referencedObjects);
1125                while (referencedObjectsIterator.hasNext())
1126                {
1127                    Object refObj = referencedObjectsIterator.next();
1128                    // Now store indirection record
1129                    // BRJ: this could cause integrity problems because
1130                    // obj may not be stored depending on auto-update
1131                    mtoNBroker.storeMtoNImplementor(cod, obj, refObj, existingMtoNKeys);
1132                }
1133            }
1134        }
1135    
1136        /**
1137         * Store/Link 1:n collection references.
1138         *
1139         * @param obj real object the reference starts
1140         * @param linkOnly if true the referenced objects will only be linked (FK set, no reference store).
1141         * Reference store setting in descriptor will be ignored in this case
1142         * @param cod {@link CollectionDescriptor} of the real object
1143         * @param referencedObjects the referenced objects ({@link ManageableCollection} or Collection or Array) or null
1144         * @param insert flag for insert operation
1145         */
1146        private void storeAndLinkOneToMany(boolean linkOnly, Object obj, CollectionDescriptor cod,
1147                                           Object referencedObjects, boolean insert)
1148        {
1149            if(referencedObjects == null)
1150            {
1151                return;
1152            }
1153            /*
1154            Only make sense to perform (link or/and store) real referenced objects
1155            or materialized collection proxy objects, because on unmaterialized collection
1156            nothing has changed.
1157    
1158            - if the collection is a collectionproxy and it's not already loaded
1159            no need to perform an update on the referenced objects
1160            - on insert we link and insert the referenced objects, because the proxy
1161            collection maybe "inherited" from the object before the PK was replaced
1162            */
1163            if(insert || !(referencedObjects instanceof CollectionProxyDefaultImpl
1164                            && !((CollectionProxyDefaultImpl) referencedObjects).isLoaded()))
1165            {
1166                Iterator it = BrokerHelper.getCollectionIterator(referencedObjects);
1167                Object refObj;
1168                while(it.hasNext())
1169                {
1170                    refObj = it.next();
1171                    /*
1172                    TODO: Check this!
1173                    arminw:
1174                    When it's necessary to 'link' (set the FK) the 1:n reference objects?
1175                    1. set FK in refObj if it is materialized
1176                    2. if the referenced object is a proxy AND the main object needs insert
1177                    we have to materialize the real object, because the user may move a collection
1178                    of proxy objects from object A to new object B. In this case we have to replace the
1179                    FK in the proxy object with new key of object B.
1180                    */
1181                    if(insert || getProxyFactory().isMaterialized(refObj))
1182                    {
1183                        ClassDescriptor refCld = getClassDescriptor(getProxyFactory().getRealClass(refObj));
1184                        // get the real object before linking
1185                        refObj = getProxyFactory().getRealObject(refObj);
1186                        link(refObj, refCld, cod, obj, insert);
1187                        // if enabled cascade store and not only link, store the refObj
1188                        if(!linkOnly && cod.getCascadingStore() == ObjectReferenceDescriptor.CASCADE_OBJECT)
1189                        {
1190                            store(refObj);
1191                        }
1192                    }
1193                }
1194            }
1195        }
1196    
1197        /**
1198         * Assign FK value to target object by reading PK values of referenced object.
1199         *
1200         * @param targetObject real (non-proxy) target object
1201         * @param cld {@link ClassDescriptor} of the real target object
1202         * @param rds An {@link ObjectReferenceDescriptor} or {@link CollectionDescriptor}
1203         * associated with the real object.
1204         * @param referencedObject referenced object or proxy
1205         * @param insert Show if "linking" is done while insert or update.
1206         */
1207        public void link(Object targetObject, ClassDescriptor cld, ObjectReferenceDescriptor rds, Object referencedObject, boolean insert)
1208        {
1209            // MBAIRD: we have 'disassociated' this object from the referenced object,
1210            // the object represented by the reference descriptor is now null, so set
1211            // the fk in the target object to null.
1212            // arminw: if an insert was done and ref object was null, we should allow
1213            // to pass FK fields of main object (maybe only the FK fields are set)
1214            if (referencedObject == null)
1215            {
1216                /*
1217                arminw:
1218                if update we set FK fields to 'null', because reference was disassociated
1219                We do nothing on insert, maybe only the FK fields of main object (without
1220                materialization of the reference object) are set by the user
1221                */
1222                if(!insert)
1223                {
1224                    unlinkFK(targetObject, cld, rds);
1225                }
1226            }
1227            else
1228            {
1229                setFKField(targetObject, cld, rds, referencedObject);
1230            }
1231        }
1232    
1233        /**
1234         * Unkink FK fields of target object.
1235         *
1236         * @param targetObject real (non-proxy) target object
1237         * @param cld {@link ClassDescriptor} of the real target object
1238         * @param rds An {@link ObjectReferenceDescriptor} or {@link CollectionDescriptor}
1239         * associated with the real object.
1240         */
1241        public void unlinkFK(Object targetObject, ClassDescriptor cld, ObjectReferenceDescriptor rds)
1242        {
1243            setFKField(targetObject, cld, rds, null);
1244        }
1245    
1246        /**
1247         * Set the FK value on the target object, extracted from the referenced object. If the referenced object was
1248         * <i>null</i> the FK values were set to <i>null</i>, expect when the FK field was declared as PK.
1249         *
1250         * @param targetObject real (non-proxy) target object
1251         * @param cld {@link ClassDescriptor} of the real target object
1252         * @param rds An {@link ObjectReferenceDescriptor} or {@link CollectionDescriptor}
1253         * @param referencedObject The referenced object or <i>null</i>
1254         */
1255        private void setFKField(Object targetObject, ClassDescriptor cld, ObjectReferenceDescriptor rds, Object referencedObject)
1256        {
1257            ValueContainer[] refPkValues;
1258            FieldDescriptor fld;
1259            FieldDescriptor[] objFkFields = rds.getForeignKeyFieldDescriptors(cld);
1260            if (objFkFields == null)
1261            {
1262                throw new PersistenceBrokerException("No foreign key fields defined for class '"+cld.getClassNameOfObject()+"'");
1263            }
1264            if(referencedObject == null)
1265            {
1266                refPkValues = null;
1267            }
1268            else
1269            {
1270                Class refClass = proxyFactory.getRealClass(referencedObject);
1271                ClassDescriptor refCld = getClassDescriptor(refClass);
1272                refPkValues = brokerHelper.getKeyValues(refCld, referencedObject, false);
1273            }
1274            for (int i = 0; i < objFkFields.length; i++)
1275            {
1276                fld = objFkFields[i];
1277                /*
1278                arminw:
1279                we set the FK value when the extracted PK fields from the referenced object are not null at all
1280                or if null, the FK field was not a PK field of target object too.
1281                Should be ok, because the values of the extracted PK field values should never be null and never
1282                change, so it doesn't matter if the target field is a PK too.
1283                */
1284                if(refPkValues != null || !fld.isPrimaryKey())
1285                {
1286                    fld.getPersistentField().set(targetObject, refPkValues != null ? refPkValues[i].getValue(): null);
1287                }
1288            }
1289        }
1290    
1291        /**
1292         * Assign FK value of main object with PK values of the reference object.
1293         *
1294         * @param obj real object with reference (proxy) object (or real object with set FK values on insert)
1295         * @param cld {@link ClassDescriptor} of the real object
1296         * @param rds An {@link ObjectReferenceDescriptor} of real object.
1297         * @param insert Show if "linking" is done while insert or update.
1298         */
1299        public void linkOneToOne(Object obj, ClassDescriptor cld, ObjectReferenceDescriptor rds, boolean insert)
1300        {
1301            storeAndLinkOneToOne(true, obj, cld, rds, true);
1302        }
1303    
1304        /**
1305         * Assign FK value to all n-side objects referenced by given object.
1306         *
1307         * @param obj real object with 1:n reference
1308         * @param cod {@link CollectionDescriptor} of referenced 1:n objects
1309         * @param insert flag signal insert operation, false signals update operation
1310         */
1311        public void linkOneToMany(Object obj, CollectionDescriptor cod, boolean insert)
1312        {
1313            Object referencedObjects = cod.getPersistentField().get(obj);
1314            storeAndLinkOneToMany(true, obj, cod,referencedObjects, insert);
1315        }
1316    
1317        /**
1318         * Assign FK values and store entries in indirection table
1319         * for all objects referenced by given object.
1320         *
1321         * @param obj real object with 1:n reference
1322         * @param cod {@link CollectionDescriptor} of referenced 1:n objects
1323         * @param insert flag signal insert operation, false signals update operation
1324         */
1325        public void linkMtoN(Object obj, CollectionDescriptor cod, boolean insert)
1326        {
1327            Object referencedObjects = cod.getPersistentField().get(obj);
1328            storeAndLinkMtoN(true, obj, cod, referencedObjects, insert);
1329        }
1330    
1331        public void unlinkXtoN(Object obj, CollectionDescriptor col)
1332        {
1333            if(col.isMtoNRelation())
1334            {
1335                // if this is a m:n mapped table, remove entries from indirection table
1336                mtoNBroker.deleteMtoNImplementor(col, obj);
1337            }
1338            else
1339            {
1340                Object collectionObject = col.getPersistentField().get(obj);
1341                if (collectionObject != null)
1342                {
1343                    Iterator colIterator = BrokerHelper.getCollectionIterator(collectionObject);
1344                    ClassDescriptor cld = null;
1345                    while (colIterator.hasNext())
1346                    {
1347                        Object target = colIterator.next();
1348                        if(cld == null) cld = getClassDescriptor(getProxyFactory().getRealClass(target));
1349                        unlinkFK(target, cld, col);
1350                    }
1351                }
1352            }
1353        }
1354    
1355        /**
1356         * Retrieve all References (also Collection-attributes) of a given instance.
1357         * Loading is forced, even if the collection- and reference-descriptors differ.
1358         * @param pInstance the persistent instance to work with
1359         */
1360        public void retrieveAllReferences(Object pInstance) throws PersistenceBrokerException
1361        {
1362            if (logger.isDebugEnabled())
1363            {
1364                    logger.debug("Manually retrieving all references for object " + serviceIdentity().buildIdentity(pInstance));
1365            }
1366            ClassDescriptor cld = getClassDescriptor(pInstance.getClass());
1367            getInternalCache().enableMaterializationCache();
1368            // to avoid problems with circular references, locally cache the current object instance
1369            Identity oid = serviceIdentity().buildIdentity(pInstance);
1370    //        boolean needLocalRemove = false;
1371            if(getInternalCache().doLocalLookup(oid) == null)
1372            {
1373                getInternalCache().doInternalCache(oid, pInstance, MaterializationCache.TYPE_TEMP);
1374    //            needLocalRemove = true;
1375            }
1376            try
1377            {
1378                referencesBroker.retrieveReferences(pInstance, cld, true);
1379                referencesBroker.retrieveCollections(pInstance, cld, true);
1380    // arminw: should no longer needed since we use TYPE_TEMP for this kind of objects
1381    //            // do locally remove the object to avoid problems with object state detection (insert/update),
1382    //            // because objects found in the cache detected as 'old' means 'update'
1383    //            if(needLocalRemove) getInternalCache().doLocalRemove(oid);
1384                getInternalCache().disableMaterializationCache();
1385            }
1386            catch(RuntimeException e)
1387            {
1388                getInternalCache().doLocalClear();
1389                throw e;
1390            }
1391        }
1392    
1393        /**
1394         * retrieve a single reference- or collection attribute
1395         * of a persistent instance.
1396         * @param pInstance the persistent instance
1397         * @param pAttributeName the name of the Attribute to load
1398         */
1399        public void retrieveReference(Object pInstance, String pAttributeName) throws PersistenceBrokerException
1400        {
1401            if (logger.isDebugEnabled())
1402            {
1403                    logger.debug("Retrieving reference named ["+pAttributeName+"] on object of type ["+
1404                                pInstance.getClass().getName()+"]");
1405            }
1406            ClassDescriptor cld = getClassDescriptor(pInstance.getClass());
1407            CollectionDescriptor cod = cld.getCollectionDescriptorByName(pAttributeName);
1408            getInternalCache().enableMaterializationCache();
1409            // to avoid problems with circular references, locally cache the current object instance
1410            Identity oid = serviceIdentity().buildIdentity(pInstance);
1411            boolean needLocalRemove = false;
1412            if(getInternalCache().doLocalLookup(oid) == null)
1413            {
1414                getInternalCache().doInternalCache(oid, pInstance, MaterializationCache.TYPE_TEMP);
1415                needLocalRemove = true;
1416            }
1417            try
1418            {
1419                if (cod != null)
1420                {
1421                    referencesBroker.retrieveCollection(pInstance, cld, cod, true);
1422                }
1423                else
1424                {
1425                    ObjectReferenceDescriptor ord = cld.getObjectReferenceDescriptorByName(pAttributeName);
1426                    if (ord != null)
1427                    {
1428                        referencesBroker.retrieveReference(pInstance, cld, ord, true);
1429                    }
1430                    else
1431                    {
1432                        throw new PersistenceBrokerException("did not find attribute " + pAttributeName +
1433                                " for class " + pInstance.getClass().getName());
1434                    }
1435                }
1436                // do locally remove the object to avoid problems with object state detection (insert/update),
1437                // because objects found in the cache detected as 'old' means 'update'
1438                if(needLocalRemove) getInternalCache().doLocalRemove(oid);
1439                getInternalCache().disableMaterializationCache();
1440            }
1441            catch(RuntimeException e)
1442            {
1443                getInternalCache().doLocalClear();
1444                throw e;
1445            }
1446        }
1447    
1448        /**
1449         * Check if the references of the specified object have enabled
1450         * the <em>refresh</em> attribute and refresh the reference if set <em>true</em>.
1451         *
1452         * @throws PersistenceBrokerException if there is a error refreshing collections or references
1453         * @param obj The object to check.
1454         * @param oid The {@link Identity} of the object.
1455         * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the object.
1456         */
1457        public void checkRefreshRelationships(Object obj, Identity oid, ClassDescriptor cld)
1458        {
1459            Iterator iter;
1460            CollectionDescriptor cds;
1461            ObjectReferenceDescriptor rds;
1462            // to avoid problems with circular references, locally cache the current object instance
1463            Object tmp = getInternalCache().doLocalLookup(oid);
1464            if(tmp != null && getInternalCache().isEnabledMaterialisationCache())
1465            {
1466                /*
1467                arminw: This should fix OJB-29, infinite loops on bidirectional 1:1 relations with
1468                refresh attribute 'true' for both references. OJB now assume that the object is already
1469                refreshed when it's cached in the materialisation cache
1470                */
1471                return;
1472            }
1473            try
1474            {
1475                getInternalCache().enableMaterializationCache();
1476                if(tmp == null)
1477                {
1478                    getInternalCache().doInternalCache(oid, obj, MaterializationCache.TYPE_TEMP);
1479                }
1480                if(logger.isDebugEnabled()) logger.debug("Refresh relationships for " + oid);
1481                iter = cld.getCollectionDescriptors().iterator();
1482                while (iter.hasNext())
1483                {
1484                    cds = (CollectionDescriptor) iter.next();
1485                    if (cds.isRefresh())
1486                    {
1487                        referencesBroker.retrieveCollection(obj, cld, cds, false);
1488                    }
1489                }
1490                iter = cld.getObjectReferenceDescriptors().iterator();
1491                while (iter.hasNext())
1492                {
1493                    rds = (ObjectReferenceDescriptor) iter.next();
1494                    if (rds.isRefresh())
1495                    {
1496                        referencesBroker.retrieveReference(obj, cld, rds, false);
1497                    }
1498                }
1499                getInternalCache().disableMaterializationCache();
1500            }
1501            catch(RuntimeException e)
1502            {
1503                getInternalCache().doLocalClear();
1504                throw e;
1505            }
1506        }
1507    
1508        /**
1509         * retrieve a collection of type collectionClass matching the Query query
1510         *
1511         * @see org.apache.ojb.broker.PersistenceBroker#getCollectionByQuery(Class, Query)
1512         */
1513        public ManageableCollection getCollectionByQuery(Class collectionClass, Query query)
1514                throws PersistenceBrokerException
1515        {
1516            return referencesBroker.getCollectionByQuery(collectionClass, query, false);
1517        }
1518    
1519        /**
1520         * retrieve a collection of itemClass Objects matching the Query query
1521         */
1522        public Collection getCollectionByQuery(Query query) throws PersistenceBrokerException
1523        {
1524            return referencesBroker.getCollectionByQuery(query, false);
1525        }
1526    
1527        /**
1528         * Retrieve an plain object (without populated references) by it's identity
1529         * from the database
1530         *
1531         * @param cld the real {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the object to refresh
1532         * @param oid the {@link org.apache.ojb.broker.Identity} of the object
1533         * @return A new plain object read from the database or <em>null</em> if not found
1534         * @throws ClassNotPersistenceCapableException
1535         */
1536        private Object getPlainDBObject(ClassDescriptor cld, Identity oid) throws ClassNotPersistenceCapableException
1537        {
1538            Object newObj = null;
1539    
1540            // Class is NOT an Interface: it has a directly mapped table and we lookup this table first:
1541            if (!cld.isInterface())
1542            {
1543                // 1. try to retrieve skalar fields from directly mapped table columns
1544                newObj = dbAccess.materializeObject(cld, oid);
1545            }
1546    
1547            // if we did not find the object yet AND if the cld represents an Extent,
1548            // we can lookup all tables of the extent classes:
1549            if (newObj == null && cld.isExtent())
1550            {
1551                Iterator extents = getDescriptorRepository().getAllConcreteSubclassDescriptors(cld).iterator();
1552    
1553                while (extents.hasNext())
1554                {
1555                    ClassDescriptor extCld = (ClassDescriptor) extents.next();
1556    
1557                    newObj = dbAccess.materializeObject(extCld, oid);
1558                    if (newObj != null)
1559                    {
1560                        break;
1561                    }
1562                }
1563            }
1564            return newObj;
1565        }
1566        
1567        
1568        /**
1569         * Retrieve an full materialized (dependent on the metadata settings)
1570         * object by it's identity from the database, as well as caching the
1571         * object
1572         *
1573         * @param oid The {@link org.apache.ojb.broker.Identity} of the object to for
1574         * @return A new object read from the database or <em>null</em> if not found
1575         * @throws ClassNotPersistenceCapableException
1576         */
1577        private Object getDBObject(Identity oid) throws ClassNotPersistenceCapableException
1578        {
1579            Class c = oid.getObjectsRealClass();
1580    
1581            if (c == null)
1582            {
1583                logger.info("Real class for used Identity object is 'null', use top-level class instead");
1584                c = oid.getObjectsTopLevelClass();
1585            }
1586    
1587            ClassDescriptor cld = getClassDescriptor(c);
1588            Object newObj = getPlainDBObject(cld, oid);
1589    
1590            // loading references is useful only when the Object could be found in db:
1591            if (newObj != null)
1592            {
1593                if (oid.getObjectsRealClass() == null)
1594                {
1595                    oid.setObjectsRealClass(newObj.getClass());
1596                }
1597    
1598                /*
1599                 * synchronize on newObj so the ODMG-layer can take a snapshot only of
1600                 * fully cached (i.e. with all references + collections) objects
1601                 */
1602                synchronized (newObj)
1603                {
1604                    objectCache.enableMaterializationCache();
1605                    try
1606                    {
1607                        // cache object immediately , so that references
1608                        // can be established from referenced Objects back to this Object
1609                        objectCache.doInternalCache(oid, newObj, ObjectCacheInternal.TYPE_NEW_MATERIALIZED);
1610    
1611                        /*
1612                         * Chris Lewington: can cause problems with multiple objects
1613                         * mapped to one table, as follows:
1614                         *
1615                         * if the class searched on does not match the retrieved
1616                         * class, eg a search on an OID retrieves a row but it could
1617                         * be a different class (OJB gets all column values),
1618                         * then trying to resolve references will fail as the object
1619                         * will not match the Class Descriptor.
1620                         *
1621                         * To be safe, get the descriptor of the retrieved object
1622                         * BEFORE resolving refs
1623                         */
1624                        ClassDescriptor newObjCld = getClassDescriptor(newObj.getClass());
1625                        // don't force loading of references:
1626                        final boolean unforced = false;
1627    
1628                        // 2. retrieve non-skalar fields that contain objects retrievable from other tables
1629                        referencesBroker.retrieveReferences(newObj, newObjCld, unforced);
1630                        // 3. retrieve collection fields from foreign-key related tables:
1631                        referencesBroker.retrieveCollections(newObj, newObjCld, unforced);
1632                        objectCache.disableMaterializationCache();
1633                    }
1634                    catch(RuntimeException e)
1635                    {
1636                        objectCache.doLocalClear();
1637                        throw e;
1638                    }
1639                }
1640            }
1641    
1642            return newObj;
1643        }
1644    
1645        /**
1646         * returns an Iterator that iterates Objects of class c if calling the .next()
1647         * method. The Elements returned come from a SELECT ... WHERE Statement
1648         * that is defined by the Query query.
1649         * If itemProxy is null, no proxies are used.
1650         */
1651        public Iterator getIteratorByQuery(Query query) throws PersistenceBrokerException
1652        {
1653            Class itemClass = query.getSearchClass();
1654            ClassDescriptor cld = getClassDescriptor(itemClass);
1655            return getIteratorFromQuery(query, cld);
1656        }
1657    
1658        /**
1659         * Get an extent aware Iterator based on the Query
1660         *
1661         * @param query
1662         * @param cld the ClassDescriptor
1663         * @return OJBIterator
1664         */
1665        protected OJBIterator getIteratorFromQuery(Query query, ClassDescriptor cld) throws PersistenceBrokerException
1666        {
1667            RsIteratorFactory factory = RsIteratorFactoryImpl.getInstance();
1668            OJBIterator result = getRsIteratorFromQuery(query, cld, factory);
1669    
1670            if (query.usePaging())
1671            {
1672                result = new PagingIterator(result, query.getStartAtIndex(), query.getEndAtIndex());
1673            }
1674            return result;
1675        }
1676    
1677        public Object getObjectByIdentity(Identity id) throws PersistenceBrokerException
1678        {
1679            objectCache.enableMaterializationCache();
1680            Object result = null;
1681            try
1682            {
1683                result = doGetObjectByIdentity(id);
1684                objectCache.disableMaterializationCache();
1685            }
1686            catch(RuntimeException e)
1687            {
1688                // catch runtime exc. to guarantee clearing of internal buffer on failure
1689                objectCache.doLocalClear();
1690                throw e;
1691            }
1692            return result;
1693        }
1694    
1695        /**
1696         * Internal used method to retrieve object based on Identity.
1697         *
1698         * @param id
1699         * @return
1700         * @throws PersistenceBrokerException
1701         */
1702        public Object doGetObjectByIdentity(Identity id) throws PersistenceBrokerException
1703        {
1704            if (logger.isDebugEnabled()) logger.debug("getObjectByIdentity " + id);
1705    
1706            // check if object is present in ObjectCache:
1707            Object obj = objectCache.lookup(id);
1708            // only perform a db lookup if necessary (object not cached yet)
1709            if (obj == null)
1710            {
1711                obj = getDBObject(id);
1712            }
1713            else
1714            {
1715                ClassDescriptor cld = getClassDescriptor(obj.getClass());
1716                // if specified in the ClassDescriptor the instance must be refreshed
1717                if (cld.isAlwaysRefresh())
1718                {
1719                    refreshInstance(obj, id, cld);
1720                }
1721                // now refresh all references
1722                checkRefreshRelationships(obj, id, cld);
1723            }
1724    
1725            // Invoke events on PersistenceBrokerAware instances and listeners
1726            AFTER_LOOKUP_EVENT.setTarget(obj);
1727            fireBrokerEvent(AFTER_LOOKUP_EVENT);
1728            AFTER_LOOKUP_EVENT.setTarget(null);
1729    
1730            //logger.info("RETRIEVING object " + obj);
1731            return obj;
1732        }
1733    
1734        /**
1735         * refresh all primitive typed attributes of a cached instance
1736         * with the current values from the database.
1737         * refreshing of reference and collection attributes is not done
1738         * here.
1739         * @param cachedInstance the cached instance to be refreshed
1740         * @param oid the Identity of the cached instance
1741         * @param cld the ClassDescriptor of cachedInstance
1742         */
1743        private void refreshInstance(Object cachedInstance, Identity oid, ClassDescriptor cld)
1744        {
1745            // read in fresh copy from the db, but do not cache it
1746            Object freshInstance = getPlainDBObject(cld, oid);
1747    
1748            // update all primitive typed attributes
1749            FieldDescriptor[] fields = cld.getFieldDescriptions();
1750            FieldDescriptor fmd;
1751            PersistentField fld;
1752            for (int i = 0; i < fields.length; i++)
1753            {
1754                fmd = fields[i];
1755                fld = fmd.getPersistentField();
1756                fld.set(cachedInstance, fld.get(freshInstance));
1757            }
1758        }
1759    
1760        /**
1761         * retrieve an Object by query
1762         * I.e perform a SELECT ... FROM ... WHERE ...  in an RDBMS
1763         */
1764        public Object getObjectByQuery(Query query) throws PersistenceBrokerException
1765        {
1766            Object result = null;
1767            if (query instanceof QueryByIdentity)
1768            {
1769                // example obj may be an entity or an Identity
1770                Object obj = query.getExampleObject();
1771                if (obj instanceof Identity)
1772                {
1773                    Identity oid = (Identity) obj;
1774                    result = getObjectByIdentity(oid);
1775                }
1776                else
1777                {
1778                    // TODO: This workaround doesn't allow 'null' for PK fields
1779                    if (!serviceBrokerHelper().hasNullPKField(getClassDescriptor(obj.getClass()), obj))
1780                    {
1781                        Identity oid = serviceIdentity().buildIdentity(obj);
1782                        result = getObjectByIdentity(oid);
1783                    }
1784                }
1785            }
1786            else
1787            {
1788                Class itemClass = query.getSearchClass();
1789                ClassDescriptor cld = getClassDescriptor(itemClass);
1790                /*
1791                use OJB intern Iterator, thus we are able to close used
1792                resources instantly
1793                */
1794                OJBIterator it = getIteratorFromQuery(query, cld);
1795                /*
1796                arminw:
1797                patch by Andre Clute, instead of taking the first found result
1798                try to get the first found none null result.
1799                He wrote:
1800                I have a situation where an item with a certain criteria is in my
1801                database twice -- once deleted, and then a non-deleted version of it.
1802                When I do a PB.getObjectByQuery(), the RsIterator get's both results
1803                from the database, but the first row is the deleted row, so my RowReader
1804                filters it out, and do not get the right result.
1805                */
1806                try
1807                {
1808                    while (result==null && it.hasNext())
1809                    {
1810                        result = it.next();
1811                    }
1812                } // make sure that we close the used resources
1813                finally
1814                {
1815                    if(it != null) it.releaseDbResources();
1816                }
1817            }
1818            return result;
1819        }
1820    
1821        /**
1822         * returns an Enumeration of PrimaryKey Objects for objects of class DataClass.
1823         * The Elements returned come from a SELECT ... WHERE Statement
1824         * that is defined by the fields and their coresponding values of listFields
1825         * and listValues.
1826         * Useful for EJB Finder Methods...
1827         * @param primaryKeyClass the pk class for the searched objects
1828         * @param query the query
1829         */
1830        public Enumeration getPKEnumerationByQuery(Class primaryKeyClass, Query query) throws PersistenceBrokerException
1831        {
1832            if (logger.isDebugEnabled()) logger.debug("getPKEnumerationByQuery " + query);
1833    
1834            query.setFetchSize(1);
1835            ClassDescriptor cld = getClassDescriptor(query.getSearchClass());
1836            return new PkEnumeration(query, cld, primaryKeyClass, this);
1837        }
1838    
1839        /**
1840         * Makes object obj persistent in the underlying persistence system.
1841         * E.G. by INSERT INTO ... or UPDATE ...  in an RDBMS.
1842         * The ObjectModification parameter can be used to determine whether INSERT or update is to be used.
1843         * This functionality is typically called from transaction managers, that
1844         * track which objects have to be stored. If the object is an unmaterialized
1845         * proxy the method return immediately.
1846         */
1847        public void store(Object obj, ObjectModification mod) throws PersistenceBrokerException
1848        {
1849            obj = extractObjectToStore(obj);
1850            // null for unmaterialized Proxy
1851            if (obj == null)
1852            {
1853                return;
1854            }
1855    
1856            ClassDescriptor cld = getClassDescriptor(obj.getClass());
1857            // this call ensures that all autoincremented primary key attributes are filled
1858            Identity oid = serviceIdentity().buildIdentity(cld, obj);
1859            // select flag for insert / update selection by checking the ObjectModification
1860            if (mod.needsInsert())
1861            {
1862                store(obj, oid, cld, true);
1863            }
1864            else if (mod.needsUpdate())
1865            {
1866                store(obj, oid, cld, false);
1867            }
1868            /*
1869            arminw
1870            TODO: Why we need this behaviour? What about 1:1 relations?
1871            */
1872            else
1873            {
1874                // just store 1:n and m:n associations
1875                storeCollections(obj, cld, mod.needsInsert());
1876            }
1877        }
1878    
1879        /**
1880         * I pulled this out of internal store so that when doing multiple table
1881         * inheritance, i can recurse this function.
1882         *
1883         * @param obj
1884         * @param cld
1885         * @param oid   BRJ: what is it good for ???
1886         * @param insert
1887         * @param ignoreReferences
1888         */
1889        private void storeToDb(Object obj, ClassDescriptor cld, Identity oid, boolean insert, boolean ignoreReferences)
1890        {
1891            // 1. link and store 1:1 references
1892            storeReferences(obj, cld, insert, ignoreReferences);
1893    
1894            Object[] pkValues = oid.getPrimaryKeyValues();
1895            if (!serviceBrokerHelper().assertValidPksForStore(cld.getPkFields(), pkValues))
1896            {
1897                // BRJ: fk values may be part of pk, but the are not known during
1898                // creation of Identity. so we have to get them here
1899                pkValues = serviceBrokerHelper().getKeyValues(cld, obj);
1900                if (!serviceBrokerHelper().assertValidPksForStore(cld.getPkFields(), pkValues))
1901                {
1902                    String append = insert ? " on insert" : " on update" ;
1903                    throw new PersistenceBrokerException("assertValidPkFields failed for Object of type: " + cld.getClassNameOfObject() + append);
1904                }
1905            }
1906    
1907            // get super class cld then store it with the object
1908            /*
1909            now for multiple table inheritance
1910            1. store super classes, topmost parent first
1911            2. go down through heirarchy until current class
1912            3. todo: store to full extent?
1913    
1914    // arminw: TODO: The extend-attribute feature dosn't work, should we remove this stuff?
1915            This if-clause will go up the inheritance heirarchy to store all the super classes.
1916            The id for the top most super class will be the id for all the subclasses too
1917             */
1918            if(cld.getSuperClass() != null)
1919            {
1920    
1921                ClassDescriptor superCld = getDescriptorRepository().getDescriptorFor(cld.getSuperClass());
1922                storeToDb(obj, superCld, oid, insert);
1923                // arminw: why this?? I comment out this section
1924                // storeCollections(obj, cld.getCollectionDescriptors(), insert);
1925            }
1926    
1927            // 2. store primitive typed attributes (Or is THIS step 3 ?)
1928            // if obj not present in db use INSERT
1929            if (insert)
1930            {
1931                dbAccess.executeInsert(cld, obj);
1932                if(oid.isTransient())
1933                {
1934                    // Create a new Identity based on the current set of primary key values.
1935                    oid = serviceIdentity().buildIdentity(cld, obj);
1936                }
1937            }
1938            // else use UPDATE
1939            else
1940            {
1941                try
1942                {
1943                    dbAccess.executeUpdate(cld, obj);
1944                }
1945                catch(OptimisticLockException e)
1946                {
1947                    // ensure that the outdated object be removed from cache
1948                    objectCache.remove(oid);
1949                    throw e;
1950                }
1951            }
1952            // cache object for symmetry with getObjectByXXX()
1953            // Add the object to the cache.
1954            objectCache.doInternalCache(oid, obj, ObjectCacheInternal.TYPE_WRITE);
1955            // 3. store 1:n and m:n associations
1956            if(!ignoreReferences) storeCollections(obj, cld, insert);
1957        }
1958    
1959        /**
1960         * I pulled this out of internal store so that when doing multiple table
1961         * inheritance, i can recurse this function.
1962         *
1963         * @param obj
1964         * @param cld
1965         * @param oid   BRJ: what is it good for ???
1966         * @param insert
1967         */
1968        private void storeToDb(Object obj, ClassDescriptor cld, Identity oid, boolean insert)
1969        {
1970            storeToDb(obj, cld, oid, insert, false);
1971        }
1972    
1973        /**
1974         * returns true if the broker is currently running a transaction.
1975         * @return boolean
1976         */
1977        public boolean isInTransaction()
1978        {
1979            // return this.connectionManager.isInLocalTransaction();
1980            return inTransaction;
1981        }
1982    
1983        public void setInTransaction(boolean inTransaction)
1984        {
1985            this.inTransaction = inTransaction;
1986        }
1987    
1988        /**
1989         * @see org.apache.ojb.broker.PersistenceBroker#removeFromCache
1990         */
1991        public void removeFromCache(Object objectOrIdentity) throws PersistenceBrokerException
1992        {
1993            Identity identity;
1994            if (objectOrIdentity instanceof Identity)
1995            {
1996                identity = (Identity)objectOrIdentity;
1997            }
1998            else
1999            {
2000                identity = serviceIdentity().buildIdentity(objectOrIdentity);
2001            }
2002            objectCache.remove(identity);
2003        }
2004    
2005        /**
2006         * returns a ClassDescriptor for the persistence capable class clazz.
2007         * throws a PersistenceBrokerException if clazz is not persistence capable,
2008         * i.e. if clazz is not defined in the DescriptorRepository.
2009         */
2010        public ClassDescriptor getClassDescriptor(Class clazz) throws PersistenceBrokerException
2011        {
2012            return descriptorRepository.getDescriptorFor(clazz);
2013        }
2014    
2015        public boolean hasClassDescriptor(Class clazz)
2016        {
2017            return descriptorRepository.hasDescriptorFor(clazz);
2018        }
2019    
2020        /**
2021         * clears the brokers internal cache.
2022         * removing is recursive. That is referenced Objects are also
2023         * removed from the cache, if the auto-retrieve flag is set
2024         * for obj.getClass() in the metadata repository.
2025         *
2026         */
2027        public void clearCache() throws PersistenceBrokerException
2028        {
2029            objectCache.clear();
2030        }
2031    
2032        /**
2033         * @see org.apache.ojb.broker.PersistenceBroker#getTopLevelClass
2034         */
2035        public Class getTopLevelClass(Class clazz) throws PersistenceBrokerException
2036        {
2037            try
2038            {
2039                return descriptorRepository.getTopLevelClass(clazz);
2040            }
2041            catch (ClassNotPersistenceCapableException e)
2042            {
2043                throw new PersistenceBrokerException(e);
2044            }
2045        }
2046    
2047        /**
2048         * @see org.apache.ojb.broker.PersistenceBroker#getCount(Query)
2049         */
2050        public int getCount(Query query) throws PersistenceBrokerException
2051        {
2052            Query countQuery = serviceBrokerHelper().getCountQuery(query);
2053            Iterator iter;
2054            int result = 0;
2055    
2056            if (logger.isDebugEnabled()) logger.debug("getCount " + countQuery.getSearchClass() + ", " + countQuery);
2057    
2058            iter = getReportQueryIteratorByQuery(countQuery);
2059            try
2060            {
2061                while (iter.hasNext())
2062                {
2063                    Object[] row = (Object[]) iter.next();
2064                    result += ((Number) row[0]).intValue();
2065                }
2066            }
2067            finally
2068            {
2069                if (iter instanceof OJBIterator)
2070                {
2071                    ((OJBIterator) iter).releaseDbResources();
2072                }
2073            }
2074    
2075            return result;
2076        }
2077    
2078        /**
2079         * Get an Iterator based on the ReportQuery
2080         *
2081         * @param query
2082         * @return Iterator
2083         */
2084        public Iterator getReportQueryIteratorByQuery(Query query) throws PersistenceBrokerException
2085        {
2086            ClassDescriptor cld = getClassDescriptor(query.getSearchClass());
2087            return getReportQueryIteratorFromQuery(query, cld);
2088        }
2089    
2090        /**
2091         * Get an extent aware RsIterator based on the Query
2092         *
2093         * @param query
2094         * @param cld
2095         * @param factory the Factory for the RsIterator
2096         * @return OJBIterator
2097         */
2098        private OJBIterator getRsIteratorFromQuery(Query query, ClassDescriptor cld, RsIteratorFactory factory)
2099            throws PersistenceBrokerException
2100        {
2101            query.setFetchSize(1);
2102            if (query instanceof QueryBySQL)
2103            {
2104                if(logger.isDebugEnabled()) logger.debug("Creating SQL-RsIterator for class ["+cld.getClassNameOfObject()+"]");
2105                return factory.createRsIterator((QueryBySQL) query, cld, this);
2106            }
2107    
2108            if (!cld.isExtent() || !query.getWithExtents())
2109            {
2110                // no extents just use the plain vanilla RsIterator
2111                if(logger.isDebugEnabled()) logger.debug("Creating RsIterator for class ["+cld.getClassNameOfObject()+"]");
2112    
2113                return factory.createRsIterator(query, cld, this);
2114            }
2115    
2116            if(logger.isDebugEnabled()) logger.debug("Creating ChainingIterator for class ["+cld.getClassNameOfObject()+"]");
2117    
2118            ChainingIterator chainingIter = new ChainingIterator();
2119    
2120            // BRJ: add base class iterator
2121            if (!cld.isInterface())
2122            {
2123                if(logger.isDebugEnabled()) logger.debug("Adding RsIterator for class ["+cld.getClassNameOfObject()+"] to ChainingIterator");
2124    
2125                chainingIter.addIterator(factory.createRsIterator(query, cld, this));
2126            }
2127    
2128            Iterator extents = getDescriptorRepository().getAllConcreteSubclassDescriptors(cld).iterator();
2129            while (extents.hasNext())
2130            {
2131                ClassDescriptor extCld = (ClassDescriptor) extents.next();
2132    
2133                // read same table only once
2134                if (chainingIter.containsIteratorForTable(extCld.getFullTableName()))
2135                {
2136                    if(logger.isDebugEnabled()) logger.debug("Skipping class ["+extCld.getClassNameOfObject()+"]");
2137                }
2138                else
2139                {
2140                    if(logger.isDebugEnabled()) logger.debug("Adding RsIterator of class ["+extCld.getClassNameOfObject()+"] to ChainingIterator");
2141    
2142                    // add the iterator to the chaining iterator.
2143                    chainingIter.addIterator(factory.createRsIterator(query, extCld, this));
2144                }
2145            }
2146    
2147            return chainingIter;
2148        }
2149    
2150        /**
2151         * Get an extent aware Iterator based on the ReportQuery
2152         *
2153         * @param query
2154         * @param cld
2155         * @return OJBIterator
2156         */
2157        private OJBIterator getReportQueryIteratorFromQuery(Query query, ClassDescriptor cld) throws PersistenceBrokerException
2158        {
2159            RsIteratorFactory factory = ReportRsIteratorFactoryImpl.getInstance();
2160            OJBIterator result = getRsIteratorFromQuery(query, cld, factory);
2161    
2162            if (query.usePaging())
2163            {
2164                result = new PagingIterator(result, query.getStartAtIndex(), query.getEndAtIndex());
2165            }
2166    
2167            return result;
2168        }
2169    
2170        /**
2171         * @see org.odbms.ObjectContainer#query()
2172         */
2173        public org.odbms.Query query()
2174        {
2175            return new org.apache.ojb.soda.QueryImpl(this);
2176        }
2177    
2178        /**
2179         * @return DescriptorRepository
2180         */
2181        public DescriptorRepository getDescriptorRepository()
2182        {
2183            return descriptorRepository;
2184        }
2185    
2186        protected void finalize()
2187        {
2188            if (!isClosed)
2189            {
2190                close();
2191            }
2192        }
2193    
2194        /**
2195         * clean up the maps for reuse by the next transaction.
2196         */
2197        private void clearRegistrationLists()
2198        {
2199            nowStoring.clear();
2200            objectCache.doLocalClear();
2201            deletedDuringTransaction.clear();
2202            /*
2203            arminw:
2204            for better performance I don't register MtoNBroker as listner,
2205            so use this method to reset on commit/rollback
2206            */
2207            mtoNBroker.reset();
2208        }
2209        
2210    
2211    
2212        /**
2213         * @see org.apache.ojb.broker.PersistenceBroker#deleteMtoNImplementor
2214         */
2215        public void deleteMtoNImplementor(MtoNImplementor m2nImpl) throws PersistenceBrokerException
2216        {
2217            mtoNBroker.deleteMtoNImplementor(m2nImpl);
2218        }
2219    
2220        /**
2221         * @see org.apache.ojb.broker.PersistenceBroker#addMtoNImplementor
2222         */
2223        public void addMtoNImplementor(MtoNImplementor m2n) throws PersistenceBrokerException
2224        {
2225                    mtoNBroker.storeMtoNImplementor(m2n);
2226        }
2227    
2228        public ProxyFactory getProxyFactory() {
2229            return proxyFactory;
2230        }
2231        
2232        /**
2233         * Creates a proxy instance.
2234         * 
2235         * @param baseClassForProxy  The base class that the Proxy should extend. For dynamic Proxies, the method of 
2236         *                           generation is dependent on the ProxyFactory implementation.
2237         * @param realSubjectsIdentity The identity of the subject
2238         * @return An instance of the proxy subclass
2239         * @throws PersistenceBrokerException If there is an error creating the proxy object
2240         */
2241        public Object createProxy(Class baseClassForProxy, Identity realSubjectsIdentity)
2242        {
2243            try
2244            {
2245                // the invocation handler manages all delegation stuff
2246                IndirectionHandler handler     = getProxyFactory().createIndirectionHandler(pbKey, realSubjectsIdentity);
2247    
2248                // the proxy simply provides the interface of the real subject
2249                if (VirtualProxy.class.isAssignableFrom(baseClassForProxy))
2250                {
2251                    Constructor constructor = baseClassForProxy.getDeclaredConstructor(new Class[]{ IndirectionHandler.class });
2252                    return constructor.newInstance(new Object[]{ handler });
2253                }
2254                else
2255                {                
2256                    return getProxyFactory().createProxy(baseClassForProxy,handler);
2257                }
2258    
2259                
2260            }
2261            catch (Exception ex)
2262            {
2263                throw new PersistenceBrokerException("Unable to create proxy using class:"+baseClassForProxy.getName(), ex);
2264            }
2265        }
2266        
2267        
2268    }