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 }