001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.orm.toplink;
018:
019: import java.util.ArrayList;
020: import java.util.Arrays;
021: import java.util.Collection;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Vector;
025:
026: import oracle.toplink.exceptions.TopLinkException;
027: import oracle.toplink.expressions.Expression;
028: import oracle.toplink.queryframework.Call;
029: import oracle.toplink.queryframework.DatabaseQuery;
030: import oracle.toplink.queryframework.ReadObjectQuery;
031: import oracle.toplink.sessions.ObjectCopyingPolicy;
032: import oracle.toplink.sessions.Session;
033: import oracle.toplink.sessions.UnitOfWork;
034:
035: import org.springframework.dao.DataAccessException;
036: import org.springframework.dao.InvalidDataAccessApiUsageException;
037: import org.springframework.orm.ObjectRetrievalFailureException;
038: import org.springframework.util.Assert;
039: import org.springframework.util.StringUtils;
040:
041: /**
042: * Helper class that simplifies TopLink data access code, and converts
043: * TopLinkExceptions into unchecked DataAccessExceptions, following the
044: * <code>org.springframework.dao</code> exception hierarchy.
045: *
046: * <p>The central method is <code>execute</code>, supporting TopLink access code
047: * implementing the {@link TopLinkCallback} interface. It provides TopLink Session
048: * handling such that neither the TopLinkCallback implementation nor the calling
049: * code needs to explicitly care about retrieving/closing TopLink Sessions,
050: * or handling Session lifecycle exceptions. For typical single step actions,
051: * there are various convenience methods (read, readAll, merge, delete, etc).
052: *
053: * <p>Can be used within a service implementation via direct instantiation
054: * with a SessionFactory reference, or get prepared in an application context
055: * and given to services as bean reference. Note: The SessionFactory should
056: * always be configured as bean in the application context, in the first case
057: * given to the service directly, in the second case to the prepared template.
058: *
059: * <p>This class can be considered as direct alternative to working with the raw
060: * TopLink Session API (through <code>SessionFactoryUtils.getSession()</code>).
061: * The major advantage is its automatic conversion to DataAccessExceptions, the
062: * major disadvantage that no checked application exceptions can get thrown from
063: * within data access code. Corresponding checks and the actual throwing of such
064: * exceptions can often be deferred to after callback execution, though.
065: *
066: * <p>{@link LocalSessionFactoryBean} is the preferred way of obtaining a reference
067: * to a specific TopLink SessionFactory. It will usually be configured to
068: * create ClientSessions for a ServerSession held by it, allowing for seamless
069: * multi-threaded execution. The Spring application context will manage its lifecycle,
070: * initializing and shutting down the factory as part of the application.
071: *
072: * <p>Thanks to Slavik Markovich for implementing the initial TopLink support prototype!
073: *
074: * @author Juergen Hoeller
075: * @author <a href="mailto:james.x.clark@oracle.com">James Clark</a>
076: * @since 1.2
077: * @see #setSessionFactory
078: * @see TopLinkCallback
079: * @see oracle.toplink.sessions.Session
080: * @see TopLinkInterceptor
081: * @see LocalSessionFactoryBean
082: * @see TopLinkTransactionManager
083: * @see org.springframework.transaction.jta.JtaTransactionManager
084: */
085: public class TopLinkTemplate extends TopLinkAccessor implements
086: TopLinkOperations {
087:
088: private boolean allowCreate = true;
089:
090: /**
091: * Create a new TopLinkTemplate instance.
092: */
093: public TopLinkTemplate() {
094: }
095:
096: /**
097: * Create a new TopLinkTemplate instance.
098: */
099: public TopLinkTemplate(SessionFactory sessionFactory) {
100: setSessionFactory(sessionFactory);
101: afterPropertiesSet();
102: }
103:
104: /**
105: * Create a new TopLinkTemplate instance.
106: * @param allowCreate if a new Session should be created if no thread-bound found
107: */
108: public TopLinkTemplate(SessionFactory sessionFactory,
109: boolean allowCreate) {
110: setSessionFactory(sessionFactory);
111: setAllowCreate(allowCreate);
112: afterPropertiesSet();
113: }
114:
115: /**
116: * Set if a new Session should be created when no transactional Session
117: * can be found for the current thread.
118: * <p>TopLinkTemplate is aware of a corresponding Session bound to the
119: * current thread, for example when using TopLinkTransactionManager.
120: * If allowCreate is true, a new non-transactional Session will be created
121: * if none found, which needs to be closed at the end of the operation.
122: * If false, an IllegalStateException will get thrown in this case.
123: * @see SessionFactoryUtils#getSession(SessionFactory, boolean)
124: */
125: public void setAllowCreate(boolean allowCreate) {
126: this .allowCreate = allowCreate;
127: }
128:
129: /**
130: * Return if a new Session should be created if no thread-bound found.
131: */
132: public boolean isAllowCreate() {
133: return this .allowCreate;
134: }
135:
136: public Object execute(TopLinkCallback action)
137: throws DataAccessException {
138: Assert.notNull(action, "Callback object must not be null");
139:
140: Session session = SessionFactoryUtils.getSession(
141: getSessionFactory(), this .allowCreate);
142: try {
143: return action.doInTopLink(session);
144: } catch (TopLinkException ex) {
145: throw convertTopLinkAccessException(ex);
146: } catch (RuntimeException ex) {
147: // callback code threw application exception
148: throw ex;
149: } finally {
150: SessionFactoryUtils.releaseSession(session,
151: getSessionFactory());
152: }
153: }
154:
155: public List executeFind(TopLinkCallback action)
156: throws DataAccessException {
157: Object result = execute(action);
158: if (result != null && !(result instanceof List)) {
159: throw new InvalidDataAccessApiUsageException(
160: "Result object returned from TopLinkCallback isn't a List: ["
161: + result + "]");
162: }
163: return (List) result;
164: }
165:
166: //-------------------------------------------------------------------------
167: // Convenience methods for executing generic queries
168: //-------------------------------------------------------------------------
169:
170: public Object executeNamedQuery(Class entityClass, String queryName)
171: throws DataAccessException {
172: return executeNamedQuery(entityClass, queryName, null, false);
173: }
174:
175: public Object executeNamedQuery(Class entityClass,
176: String queryName, boolean enforceReadOnly)
177: throws DataAccessException {
178:
179: return executeNamedQuery(entityClass, queryName, null,
180: enforceReadOnly);
181: }
182:
183: public Object executeNamedQuery(Class entityClass,
184: String queryName, Object[] args) throws DataAccessException {
185:
186: return executeNamedQuery(entityClass, queryName, args, false);
187: }
188:
189: public Object executeNamedQuery(final Class entityClass,
190: final String queryName, final Object[] args,
191: final boolean enforceReadOnly) throws DataAccessException {
192:
193: return execute(new SessionReadCallback(enforceReadOnly) {
194: protected Object readFromSession(Session session)
195: throws TopLinkException {
196: if (args != null) {
197: return session.executeQuery(queryName, entityClass,
198: new Vector(Arrays.asList(args)));
199: } else {
200: return session.executeQuery(queryName, entityClass,
201: new Vector());
202: }
203: }
204: });
205: }
206:
207: public Object executeQuery(DatabaseQuery query)
208: throws DataAccessException {
209: return executeQuery(query, null, false);
210: }
211:
212: public Object executeQuery(DatabaseQuery query,
213: boolean enforceReadOnly) throws DataAccessException {
214: return executeQuery(query, null, enforceReadOnly);
215: }
216:
217: public Object executeQuery(DatabaseQuery query, Object[] args)
218: throws DataAccessException {
219: return executeQuery(query, args, false);
220: }
221:
222: public Object executeQuery(final DatabaseQuery query,
223: final Object[] args, final boolean enforceReadOnly)
224: throws DataAccessException {
225:
226: return execute(new SessionReadCallback(enforceReadOnly) {
227: protected Object readFromSession(Session session)
228: throws TopLinkException {
229: if (args != null) {
230: return session.executeQuery(query, new Vector(
231: Arrays.asList(args)));
232: } else {
233: return session.executeQuery(query);
234: }
235: }
236: });
237: }
238:
239: //-------------------------------------------------------------------------
240: // Convenience methods for reading a specific set of objects
241: //-------------------------------------------------------------------------
242:
243: public List readAll(Class entityClass) throws DataAccessException {
244: return readAll(entityClass, false);
245: }
246:
247: public List readAll(final Class entityClass,
248: final boolean enforceReadOnly) throws DataAccessException {
249: return executeFind(new SessionReadCallback(enforceReadOnly) {
250: protected Object readFromSession(Session session)
251: throws TopLinkException {
252: return session.readAllObjects(entityClass);
253: }
254: });
255: }
256:
257: public List readAll(Class entityClass, Expression expression)
258: throws DataAccessException {
259: return readAll(entityClass, expression, false);
260: }
261:
262: public List readAll(final Class entityClass,
263: final Expression expression, final boolean enforceReadOnly)
264: throws DataAccessException {
265:
266: return executeFind(new SessionReadCallback(enforceReadOnly) {
267: protected Object readFromSession(Session session)
268: throws TopLinkException {
269: return session.readAllObjects(entityClass, expression);
270: }
271: });
272: }
273:
274: public List readAll(Class entityClass, Call call)
275: throws DataAccessException {
276: return readAll(entityClass, call, false);
277: }
278:
279: public List readAll(final Class entityClass, final Call call,
280: final boolean enforceReadOnly) throws DataAccessException {
281:
282: return executeFind(new SessionReadCallback(enforceReadOnly) {
283: protected Object readFromSession(Session session)
284: throws TopLinkException {
285: return session.readAllObjects(entityClass, call);
286: }
287: });
288: }
289:
290: public Object read(Class entityClass, Expression expression)
291: throws DataAccessException {
292: return read(entityClass, expression, false);
293: }
294:
295: public Object read(final Class entityClass,
296: final Expression expression, final boolean enforceReadOnly)
297: throws DataAccessException {
298:
299: return execute(new SessionReadCallback(enforceReadOnly) {
300: protected Object readFromSession(Session session)
301: throws TopLinkException {
302: return session.readObject(entityClass, expression);
303: }
304: });
305: }
306:
307: public Object read(Class entityClass, Call call)
308: throws DataAccessException {
309: return read(entityClass, call, false);
310: }
311:
312: public Object read(final Class entityClass, final Call call,
313: final boolean enforceReadOnly) throws DataAccessException {
314:
315: return execute(new SessionReadCallback(enforceReadOnly) {
316: protected Object readFromSession(Session session)
317: throws TopLinkException {
318: return session.readObject(entityClass, call);
319: }
320: });
321: }
322:
323: //-------------------------------------------------------------------------
324: // Convenience methods for reading an individual object by id
325: //-------------------------------------------------------------------------
326:
327: public Object readById(Class entityClass, Object id)
328: throws DataAccessException {
329: return readById(entityClass, id, false);
330: }
331:
332: public Object readById(Class entityClass, Object id,
333: boolean enforceReadOnly) throws DataAccessException {
334: return readById(entityClass, new Object[] { id },
335: enforceReadOnly);
336: }
337:
338: public Object readById(Class entityClass, Object[] keys)
339: throws DataAccessException {
340: return readById(entityClass, keys, false);
341: }
342:
343: public Object readById(final Class entityClass,
344: final Object[] keys, final boolean enforceReadOnly)
345: throws DataAccessException {
346:
347: Assert.isTrue(keys != null && keys.length > 0,
348: "Non-empty keys or id is required");
349:
350: ReadObjectQuery query = new ReadObjectQuery(entityClass);
351: query.setSelectionKey(new Vector(Arrays.asList(keys)));
352: Object result = executeQuery(query, enforceReadOnly);
353:
354: if (result == null) {
355: Object identifier = (keys.length == 1 ? keys[0]
356: : StringUtils.arrayToCommaDelimitedString(keys));
357: throw new ObjectRetrievalFailureException(entityClass,
358: identifier);
359: }
360: return result;
361: }
362:
363: public Object readAndCopy(Class entityClass, Object id)
364: throws DataAccessException {
365: return readAndCopy(entityClass, id, false);
366: }
367:
368: public Object readAndCopy(Class entityClass, Object id,
369: boolean enforceReadOnly) throws DataAccessException {
370:
371: Object entity = readById(entityClass, id, enforceReadOnly);
372: return copy(entity);
373: }
374:
375: public Object readAndCopy(Class entityClass, Object[] keys)
376: throws DataAccessException {
377: return readAndCopy(entityClass, keys, false);
378: }
379:
380: public Object readAndCopy(Class entityClass, Object[] keys,
381: boolean enforceReadOnly) throws DataAccessException {
382:
383: Object entity = readById(entityClass, keys, enforceReadOnly);
384: return copy(entity);
385: }
386:
387: //-------------------------------------------------------------------------
388: // Convenience methods for copying and refreshing objects
389: //-------------------------------------------------------------------------
390:
391: public Object copy(Object entity) throws DataAccessException {
392: ObjectCopyingPolicy copyingPolicy = new ObjectCopyingPolicy();
393: copyingPolicy.cascadeAllParts();
394: copyingPolicy.setShouldResetPrimaryKey(false);
395: return copy(entity, copyingPolicy);
396: }
397:
398: public Object copy(final Object entity,
399: final ObjectCopyingPolicy copyingPolicy)
400: throws DataAccessException {
401:
402: return execute(new TopLinkCallback() {
403: public Object doInTopLink(Session session)
404: throws TopLinkException {
405: return session.copyObject(entity, copyingPolicy);
406: }
407: });
408: }
409:
410: public List copyAll(Collection entities) throws DataAccessException {
411: ObjectCopyingPolicy copyingPolicy = new ObjectCopyingPolicy();
412: copyingPolicy.cascadeAllParts();
413: copyingPolicy.setShouldResetPrimaryKey(false);
414: return copyAll(entities, copyingPolicy);
415: }
416:
417: public List copyAll(final Collection entities,
418: final ObjectCopyingPolicy copyingPolicy)
419: throws DataAccessException {
420:
421: return (List) execute(new TopLinkCallback() {
422: public Object doInTopLink(Session session)
423: throws TopLinkException {
424: List result = new ArrayList(entities.size());
425: for (Iterator it = entities.iterator(); it.hasNext();) {
426: Object entity = it.next();
427: result.add(session
428: .copyObject(entity, copyingPolicy));
429: }
430: return result;
431: }
432: });
433: }
434:
435: public Object refresh(Object entity) throws DataAccessException {
436: return refresh(entity, false);
437: }
438:
439: public Object refresh(final Object entity,
440: final boolean enforceReadOnly) throws DataAccessException {
441: return execute(new SessionReadCallback(enforceReadOnly) {
442: protected Object readFromSession(Session session)
443: throws TopLinkException {
444: return session.refreshObject(entity);
445: }
446: });
447: }
448:
449: public List refreshAll(Collection entities)
450: throws DataAccessException {
451: return refreshAll(entities, false);
452: }
453:
454: public List refreshAll(final Collection entities,
455: final boolean enforceReadOnly) throws DataAccessException {
456: return (List) execute(new SessionReadCallback(enforceReadOnly) {
457: protected Object readFromSession(Session session)
458: throws TopLinkException {
459: List result = new ArrayList(entities.size());
460: for (Iterator it = entities.iterator(); it.hasNext();) {
461: Object entity = it.next();
462: result.add(session.refreshObject(entity));
463: }
464: return result;
465: }
466: });
467: }
468:
469: //-------------------------------------------------------------------------
470: // Convenience methods for persisting and deleting objects
471: //-------------------------------------------------------------------------
472:
473: public Object register(final Object entity) {
474: return execute(new UnitOfWorkCallback() {
475: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
476: throws TopLinkException {
477: return unitOfWork.registerObject(entity);
478: }
479: });
480: }
481:
482: public List registerAll(final Collection entities) {
483: return (List) execute(new UnitOfWorkCallback() {
484: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
485: throws TopLinkException {
486: return unitOfWork.registerAllObjects(entities);
487: }
488: });
489: }
490:
491: public void registerNew(final Object entity) {
492: execute(new UnitOfWorkCallback() {
493: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
494: throws TopLinkException {
495: return unitOfWork.registerNewObject(entity);
496: }
497: });
498: }
499:
500: public Object registerExisting(final Object entity) {
501: return execute(new UnitOfWorkCallback() {
502: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
503: throws TopLinkException {
504: return unitOfWork.registerExistingObject(entity);
505: }
506: });
507: }
508:
509: public Object merge(final Object entity) throws DataAccessException {
510: return execute(new UnitOfWorkCallback() {
511: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
512: throws TopLinkException {
513: return unitOfWork.mergeClone(entity);
514: }
515: });
516: }
517:
518: public Object deepMerge(final Object entity)
519: throws DataAccessException {
520: return execute(new UnitOfWorkCallback() {
521: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
522: throws TopLinkException {
523: return unitOfWork.deepMergeClone(entity);
524: }
525: });
526: }
527:
528: public Object shallowMerge(final Object entity)
529: throws DataAccessException {
530: return execute(new UnitOfWorkCallback() {
531: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
532: throws TopLinkException {
533: return unitOfWork.shallowMergeClone(entity);
534: }
535: });
536: }
537:
538: public Object mergeWithReferences(final Object entity)
539: throws DataAccessException {
540: return execute(new UnitOfWorkCallback() {
541: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
542: throws TopLinkException {
543: return unitOfWork.mergeCloneWithReferences(entity);
544: }
545: });
546: }
547:
548: public void delete(final Object entity) throws DataAccessException {
549: execute(new UnitOfWorkCallback() {
550: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
551: throws TopLinkException {
552: return unitOfWork.deleteObject(entity);
553: }
554: });
555: }
556:
557: public void deleteAll(final Collection entities)
558: throws DataAccessException {
559: execute(new UnitOfWorkCallback() {
560: protected Object doInUnitOfWork(UnitOfWork unitOfWork)
561: throws TopLinkException {
562: unitOfWork.deleteAllObjects(entities);
563: return null;
564: }
565: });
566: }
567:
568: }
|