001    package org.apache.ojb.otm.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 org.apache.ojb.broker.*;
019    import org.apache.ojb.broker.cache.ObjectCache;
020    import org.apache.ojb.broker.core.proxy.ProxyHelper;
021    import org.apache.ojb.broker.accesslayer.OJBIterator;
022    import org.apache.ojb.broker.metadata.ClassDescriptor;
023    import org.apache.ojb.broker.query.Query;
024    import org.apache.ojb.broker.query.ReportQuery;
025    import org.apache.ojb.broker.util.configuration.ConfigurationException;
026    import org.apache.ojb.broker.util.configuration.Configurator;
027    import org.apache.ojb.odmg.oql.EnhancedOQLQuery;
028    import org.apache.ojb.odmg.oql.OQLQueryImpl;
029    import org.apache.ojb.otm.EditingContext;
030    import org.apache.ojb.otm.OTMConnection;
031    import org.apache.ojb.otm.copy.ObjectCopyStrategy;
032    import org.apache.ojb.otm.lock.LockType;
033    import org.apache.ojb.otm.lock.LockingException;
034    import org.odmg.ODMGRuntimeException;
035    import org.odmg.OQLQuery;
036    
037    import java.util.Collection;
038    import java.util.Iterator;
039    import java.util.ListIterator;
040    import java.util.ArrayList;
041    
042    /**
043     * 
044     * <javadoc>
045     * 
046     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird </a>
047     * @author <a href="mailto:rraghuram@hotmail.com">Raghu Rajah </a>
048     * @version $Id: BaseConnection.java,v 1.1 2007-08-24 22:17:37 ewestfal Exp $
049     *  
050     */
051    public abstract class BaseConnection implements OTMConnection
052    {
053    
054        private PersistenceBroker _pb;
055        private Transaction _tx;
056        private ConcreteEditingContext _editingContext;
057        private Configurator m_configurator;
058    
059        /**
060         * Constructor for BaseConnection.
061         *  
062         */
063        public BaseConnection(PBKey pbKey)
064        {
065            _pb = PersistenceBrokerFactory.createPersistenceBroker(pbKey);
066            m_configurator = PersistenceBrokerFactory.getConfigurator();
067        }
068    
069        public void close()
070        {
071            _pb.close();
072            _pb = null;
073        }
074    
075        public boolean isClosed()
076        {
077            if (_pb == null)
078                return true;
079            else
080                return _pb.isClosed();
081        }
082    
083        public PersistenceBroker getKernelBroker()
084        {
085            return _pb;
086        }
087    
088        public void setTransaction(Transaction transaction)
089        {
090            if (transaction == null)
091            {
092                _editingContext = null;
093            }
094            else if (_tx != null)
095            {
096                throw new IllegalStateException("OTMConnection is already bound to the transacaction "
097                        + _tx);
098            }
099            else
100            {
101                _editingContext = new ConcreteEditingContext(transaction, _pb);
102            }
103            _tx = transaction;
104        }
105    
106        public Transaction getTransaction()
107        {
108            return _tx;
109        }
110    
111        //////////////////////////////////////
112        // OTMConnection protocol
113        //////////////////////////////////////
114    
115        /**
116         * @see org.apache.ojb.otm.OTMConnection#getObjectByIdentity(Identity, int)
117         */
118        public Object getObjectByIdentity(Identity oid, int lock) throws LockingException
119        {
120            checkTransaction("getObjectByIdentity");
121            Object userObject;
122            Object cacheObject;
123    
124            cacheObject = _pb.getObjectByIdentity(oid);
125            if (cacheObject == null)
126            {
127                // Possibly the object was inserted in this transaction
128                // and was not stored to database yet
129                userObject = _editingContext.lookup(oid);
130            }
131            else
132            {
133                userObject = getUserObject(oid, cacheObject);
134                // userObject from editing context may be proxy
135                userObject = ProxyHelper.getRealObject(userObject);
136                _editingContext.insert(oid, userObject, lock);
137            }
138            return userObject;
139        }
140    
141        private void checkTransaction(String methodBeingCalled)
142        {
143            if (null == _tx)
144            {
145                throw new TransactionNotInProgressException(
146                        methodBeingCalled
147                                + " requires a valid transaction. Please make sure you have created a new transaction, and called begin() on it.");
148            }
149            if (!_tx.isInProgress())
150            {
151                throw new TransactionNotInProgressException(methodBeingCalled
152                        + " cannot be called before transaction begin() is called");
153            }
154        }
155    
156        /**
157         * @see org.apache.ojb.otm.OTMConnection#getObjectByIdentity(Identity)
158         */
159        public Object getObjectByIdentity(Identity oid) throws LockingException
160        {
161            return getObjectByIdentity(oid, LockType.READ_LOCK);
162        }
163    
164        /**
165         * @param query The query to execute
166         * @return an Iterator that iterates Objects. The returned objects are locked for read.
167         */
168        public Iterator getIteratorByQuery(Query query)
169        {
170            return getIteratorByQuery(query, LockType.READ_LOCK);
171        }
172    
173        /**
174         * @param query The query to execute
175         * @param lock the lock that need to be acquired on the object Possible values are:
176         *            LockType.NO_LOCK (aka read only) - changes to the object will not be written to
177         *            database; LockType.READ_LOCK (aka optimistic lock) - changes to the object will
178         *            be written to the database, in this case the lock will be automatically upgraded
179         *            to the write lock on transaction commit; LockType.WRITE_LOCK (aka pessimistic
180         *            lock) - changes to the object will be written to the database.
181         * @return an Iterator that iterates Objects of class c if calling the .next() method. The
182         *         returned objects are locked with the given lock value.
183         */
184        public Iterator getIteratorByQuery(Query query, int lock)
185        {
186            checkTransaction("getIteratorByQuery");
187            return new OTMIterator((OJBIterator) _pb.getIteratorByQuery(query), lock, null);
188        }
189    
190        /**
191         * @param query The OQL query to execute. Use this method if you don't want to load all the
192         *            collection at once as OQLQuery.execute() does.
193         * @return an Iterator that iterates Objects. The returned objects are locked for read.
194         */
195        public Iterator getIteratorByOQLQuery(OQLQuery query)
196        {
197            return getIteratorByOQLQuery(query, LockType.READ_LOCK);
198        }
199    
200        /**
201         * @param query The OQL query to execute. Use this method if you don't want to load all the
202         *            collection at once as OQLQuery.execute() does.
203         * @return an Iterator that iterates Objects. The returned objects are locked for read.
204         */
205        public Iterator getIteratorByOQLQuery(OQLQuery query, int lock)
206        {
207            checkTransaction("getIteratorByOQLQuery");
208            if (query instanceof OTMOQLQueryImpl)
209            {
210                OTMOQLQueryImpl q = (OTMOQLQueryImpl) query;
211                return new OTMIterator((OJBIterator) _pb.getIteratorByQuery(q.getQuery()), lock, q);
212            }
213            else
214            {
215                throw new IllegalArgumentException("The OQLQuery where created not via OTM API");
216            }
217        }
218    
219        /**
220         * @param query The query to execute
221         * @param lock the lock that need to be acquired on the object Possible values are:
222         *            LockType.NO_LOCK (aka read only) - changes to the object will not be written to
223         *            database; LockType.READ_LOCK (aka optimistic lock) - changes to the object will
224         *            be written to the database, in this case the lock will be automatically upgraded
225         *            to the write lock on transaction commit; LockType.WRITE_LOCK (aka pessimistic
226         *            lock) - changes to the object will be written to the database.
227         * @return an Iterator that iterates Objects of class c if calling the .next() method. The
228         *         returned objects are locked with the given lock value.
229         */
230        public Collection getCollectionByQuery(Query query, int lock)
231        {
232            checkTransaction("getCollectionByQuery");
233            Collection col = _pb.getCollectionByQuery(query);
234            Collection result = createCollectionOfTheSameClass(col);
235            for (Iterator it = col.iterator(); it.hasNext();)
236            {
237                result.add(insertObject(it.next(), lock));
238            }
239            return result;
240        }
241    
242        /**
243         * @param query The query to execute
244         * @return an Iterator that iterates Objects of class c if calling the .next() method. The
245         *         returned objects are locked for read.
246         */
247        public Collection getCollectionByQuery(Query query)
248        {
249            return getCollectionByQuery(query, LockType.READ_LOCK);
250        }
251    
252        /**
253         * Get the identity of the object
254         *
255         * @param object The object
256         * @return the identity of the object
257         */
258        public Identity getIdentity(Object object)
259        {
260            return new Identity(object, _pb);
261        }
262    
263        /**
264         * Get the class descriptor
265         *
266         * @param clazz The class
267         * @return the descriptor of the class
268         */
269        public ClassDescriptor getDescriptorFor(Class clazz)
270        {
271            return _pb.getClassDescriptor(clazz);
272        }
273    
274        /**
275         * @see org.apache.ojb.otm.OTMConnection#invalidate(Identity)
276         */
277        public void invalidate(Identity oid) throws LockingException
278        {
279            if (null == _tx)
280            {
281                throw new TransactionNotInProgressException(
282                        "invalidate requires a valid transaction. Please make sure you have created a new transaction, and called begin() on it.");
283            }
284            // mark as invalidated in the editing context, if it's found there
285            _editingContext.insert(oid, null, LockType.READ_LOCK);
286    
287            // remove from the cache
288            _pb.serviceObjectCache().remove(oid);
289    
290        }
291    
292        /**
293         * @see org.apache.ojb.otm.OTMConnection#serviceObjectCache()
294         */
295        public ObjectCache serviceObjectCache()
296        {
297            return _pb.serviceObjectCache();
298        }
299    
300        /**
301         * TODO remove all from editing context.
302         *
303         * @throws LockingException
304         */
305        public void invalidateAll() throws LockingException
306        {
307            _pb.serviceObjectCache().clear();
308        }
309    
310        /**
311         * @see org.apache.ojb.otm.OTMConnection#lockForWrite(Object)
312         */
313        public void lockForWrite(Object object) throws LockingException
314        {
315            checkTransaction("lockForWrite");
316            makePersistent(object);
317        }
318    
319        /**
320         * @see org.apache.ojb.otm.OTMConnection#makePersistent(Object)
321         */
322        public void makePersistent(Object userObject) throws LockingException
323        {
324            checkTransaction("makePersistent");
325            Identity oid = new Identity(userObject, _pb);
326            Object cacheObject = _pb.getObjectByIdentity(oid);
327    
328            if ((cacheObject != null) && (_editingContext.lookup(oid) == null))
329            {
330                // The object exists in the database, but is not yet in the editing
331                // context, so we need to put it to the editing context in its
332                // old state, then we will put the modified userObject.
333                // This will allow the editing context to find changes
334                ObjectCopyStrategy copyStrategy = _tx.getKit().getCopyStrategy(oid);
335                Object origUserObject = copyStrategy.copy(cacheObject, _pb);
336                _editingContext.insert(oid, origUserObject, LockType.WRITE_LOCK);
337            }
338            _editingContext.insert(oid, userObject, LockType.WRITE_LOCK);
339        }
340    
341        /**
342         * @see org.apache.ojb.otm.OTMConnection#deletePersistent(Object)
343         */
344        public void deletePersistent(Object userObject) throws LockingException
345        {
346            checkTransaction("deletePersistent");
347            Identity oid = new Identity(userObject, _pb);
348            Object cacheObject = _pb.getObjectByIdentity(oid);
349            if (cacheObject == null)
350            {
351                // Possibly the object was inserted in this transaction
352                // and was not stored to database yet, so we simply remove it
353                // from editing context.
354                _editingContext.remove(oid);
355            }
356            else
357            {
358                if (_editingContext.lookup(oid) == null)
359                {
360                    // The object exists in the database, but is not yet in the editing
361                    // context, so we need to put it to the editing context
362                    ObjectCopyStrategy copyStrategy = _tx.getKit().getCopyStrategy(oid);
363                    Object origUserObject = copyStrategy.copy(cacheObject, _pb);
364                    _editingContext.insert(oid, origUserObject, LockType.WRITE_LOCK);
365                }
366                _editingContext.deletePersistent(oid, userObject);
367            }
368        }
369    
370        /**
371         * @see org.apache.ojb.otm.OTMConnection#refresh(Object)
372         */
373        public void refresh(Object userObject)
374        {
375            checkTransaction("refresh");
376            Identity oid = new Identity(userObject, _pb);
377            _editingContext.refresh(oid, userObject);
378        }
379    
380        public EditingContext getEditingContext()
381        {
382            return _editingContext;
383        }
384    
385        public EnhancedOQLQuery newOQLQuery()
386        {
387            return newOQLQuery(LockType.READ_LOCK);
388        }
389    
390        public EnhancedOQLQuery newOQLQuery(int lock)
391        {
392            checkTransaction("newOQLQuery");
393            OQLQueryImpl query = new OTMOQLQueryImpl(_pb.getPBKey(), lock);
394            try
395            {
396                m_configurator.configure(query);
397            }
398            catch (ConfigurationException e)
399            {
400                throw new ODMGRuntimeException("Error in configuration of OQLQueryImpl instance: "
401                        + e.getMessage());
402            }
403            return query;
404        }
405    
406        public int getCount(Query query)
407        {
408            checkTransaction("getCount");
409            return _pb.getCount(query);
410        }
411    
412        private Object insertObject(Object cacheObject, int lock)
413        {
414            Object ctxObject;
415            Identity oid;
416            Object userObject;
417    
418    
419            oid = getIdentity(cacheObject);
420            userObject = getUserObject(oid, cacheObject);
421            try
422            {
423                _editingContext.insert(oid, userObject, lock);
424            }
425            catch (LockingException ex)
426            {
427                throw new LockingPassthruException(ex);
428            }
429    
430            return userObject;
431        }
432    
433        /**
434         * Get user object (from the editing context) with the given oid.
435         * If not found, then create it as a copy of cacheObject.
436         * User object and cache object must be separate.
437         * @param oid The identity
438         * @param cacheObject the object for user
439         */
440        private Object getUserObject(Identity oid, Object cacheObject)
441        {
442            Object userObject = _editingContext.lookup(oid);
443    
444            if (userObject == null)
445            {
446                ObjectCopyStrategy copyStrategy = _tx.getKit().getCopyStrategy(oid);
447                userObject = copyStrategy.copy(cacheObject, _pb);
448            }
449            return userObject;
450        }
451    
452        private Collection createCollectionOfTheSameClass(Collection col)
453        {
454            try
455            {
456                return (Collection) col.getClass().newInstance();
457            }
458            catch (Throwable ex)
459            {
460                return new ArrayList();
461            }
462        }
463    
464        ///////////////////////////////////////
465        // Transaction Notifications
466        ///////////////////////////////////////
467    
468        /**
469         *
470         * Notification issued by the driving transaction to begin this transaction
471         *
472         */
473        public abstract void transactionBegin() throws TransactionException;
474    
475        /**
476         *
477         * Prepare for a commit. As part of a two phase commit protocol of the transaction.
478         *
479         */
480        public abstract void transactionPrepare() throws TransactionException;
481    
482        /**
483         *
484         * Notification issued by the driving transaction to commit resources held by this connection.
485         *
486         */
487        public abstract void transactionCommit() throws TransactionException;
488    
489        /**
490         * 
491         * Notification issued by the driving transaction to rollback resources held by this
492         * connection.
493         *  
494         */
495        public abstract void transactionRollback() throws TransactionException;
496    
497        ///////////////////////////////////////
498        // Inner classes
499        ///////////////////////////////////////
500    
501        private class OTMIterator implements OJBIterator
502        {
503            private final OJBIterator _it;
504            private final int _lock;
505            private final OTMOQLQueryImpl _oqlQuery;
506    
507            OTMIterator(OJBIterator it, int lock, OTMOQLQueryImpl oqlQuery)
508            {
509                _it = it;
510                _lock = lock;
511                _oqlQuery = oqlQuery;
512            }
513    
514            public boolean hasNext()
515            {
516                boolean res = _it.hasNext();
517    
518                // once the result set is finished, close it
519                if (!res)
520                {
521                    done();
522                }
523    
524                return res;
525            }
526    
527            public Object next()
528            {
529                Object object = _it.next();
530                object = insertObject(object, _lock);
531                return object;
532            }
533    
534            public void remove()
535            {
536                throw new UnsupportedOperationException();
537            }
538    
539            public void done()
540            {
541                releaseDbResources();
542                if (_oqlQuery != null)
543                {
544                    _oqlQuery.resetBindIterator();
545                }
546            }
547    
548            protected void finalize()
549            {
550                done();
551            }
552    
553            /*
554             * (non-Javadoc)
555             * 
556             * @see org.apache.ojb.broker.accesslayer.OJBIterator#absolute(int)
557             */
558            public boolean absolute(int row) throws PersistenceBrokerException
559            {
560                return _it.absolute(row);
561            }
562    
563            /*
564             * (non-Javadoc)
565             * 
566             * @see org.apache.ojb.broker.accesslayer.OJBIterator#fullSize()
567             */
568            public int fullSize() throws PersistenceBrokerException
569            {
570                return _it.fullSize();
571            }
572    
573            /*
574             * (non-Javadoc)
575             * 
576             * @see org.apache.ojb.broker.accesslayer.OJBIterator#relative(int)
577             */
578            public boolean relative(int row) throws PersistenceBrokerException
579            {
580                return _it.relative(row);
581            }
582    
583            /*
584             * (non-Javadoc)
585             * 
586             * @see org.apache.ojb.broker.accesslayer.OJBIterator#releaseDbResources()
587             */
588            public void releaseDbResources()
589            {
590                _it.releaseDbResources();
591            }
592    
593            /*
594             * (non-Javadoc)
595             * 
596             * @see org.apache.ojb.broker.accesslayer.OJBIterator#size()
597             */
598            public int size() throws PersistenceBrokerException
599            {
600                return _it.size();
601            }
602    
603            /**
604             * @see org.apache.ojb.broker.accesslayer.OJBIterator#disableLifeCycleEvents()
605             */
606            public void disableLifeCycleEvents()
607            {
608                _it.disableLifeCycleEvents();
609            }
610        }
611    
612        private class OTMOQLQueryImpl extends OQLQueryImpl
613        {
614            int _lock;
615    
616            public OTMOQLQueryImpl(PBKey key, int lock)
617            {
618                super(key);
619                _lock = lock;
620            }
621    
622            /**
623             * Execute the query. After executing a query, the parameter list is reset.
624             *
625             * @return The object that represents the result of the query. The returned data, whatever
626             *         its OQL type, is encapsulated into an object. For instance, when OQL returns an
627             *         integer, the result is put into an <code>Integer</code> object. When OQL
628             *         returns a collection (literal or object), the result is always a Java collection
629             *         object of the same kind (for instance, a <code>DList</code>).
630             * @exception org.odmg.QueryException An exception has occurred while executing the query.
631             */
632            public Object execute() throws org.odmg.QueryException
633            {
634                Collection result;
635                Iterator iter = null;
636                Query query = getQuery();
637    
638                try
639                {
640                    if (!(query instanceof ReportQuery))
641                    {
642                        Collection res0 = _pb.getCollectionByQuery(query);
643                        result = createCollectionOfTheSameClass(res0);
644                        for (iter = res0.iterator(); iter.hasNext();)
645                        {
646                            result.add(insertObject(iter.next(), _lock));
647                        }
648                    }
649                    else
650                    {
651                        result = new ArrayList();
652                        iter = _pb.getReportQueryIteratorByQuery(query);
653                        while (iter.hasNext())
654                        {
655                            Object[] res = (Object[]) iter.next();
656    
657                            if (res.length == 1)
658                            {
659                                if (res[0] != null) // skip null values
660                                {
661                                    result.add(res[0]);
662                                }
663                            }
664                            else
665                            {
666                                // skip null tuples
667                                for (int i = 0; i < res.length; i++)
668                                {
669                                    if (res[i] != null)
670                                    {
671                                        result.add(res);
672                                        break;
673                                    }
674                                }
675                            }
676                        }
677                    }
678                    resetBindIterator();
679                }
680                finally
681                {
682                    if ((iter != null) && (iter instanceof OJBIterator))
683                    {
684                        ((OJBIterator) iter).releaseDbResources();
685                    }
686                }
687                return result;
688            }
689    
690            void resetBindIterator()
691            {
692                // reset iterator to start of list so we can reuse this query
693                ListIterator it = getBindIterator();
694                while (it.hasPrevious())
695                {
696                    it.previous();
697                }
698            }
699        }
700    
701    }