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