1 // $Id: AbstractEntityManagerImpl.java 14812 2008-06-25 10:07:04Z hardy.ferentschik $
2 /*
3 * JBoss, the OpenSource EJB server Distributable under LGPL license. See terms of license at
4 * gnu.org.
5 */
6 package org.hibernate.ejb;
7
8 import java.io.IOException;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12 import java.util.Map;
13
14 import javax.persistence.EntityNotFoundException;
15 import javax.persistence.EntityTransaction;
16 import javax.persistence.FlushModeType;
17 import javax.persistence.LockModeType;
18 import javax.persistence.NoResultException;
19 import javax.persistence.NonUniqueResultException;
20 import javax.persistence.OptimisticLockException;
21 import javax.persistence.PersistenceContextType;
22 import javax.persistence.PersistenceException;
23 import javax.persistence.Query;
24 import javax.persistence.TransactionRequiredException;
25 import javax.persistence.spi.PersistenceUnitTransactionType;
26 import javax.transaction.Status;
27 import javax.transaction.Synchronization;
28 import javax.transaction.SystemException;
29 import javax.transaction.TransactionManager;
30
31 import org.hibernate.AssertionFailure;
32 import org.hibernate.FlushMode;
33 import org.hibernate.HibernateException;
34 import org.hibernate.LockMode;
35 import org.hibernate.MappingException;
36 import org.hibernate.ObjectDeletedException;
37 import org.hibernate.ObjectNotFoundException;
38 import org.hibernate.QueryException;
39 import org.hibernate.SQLQuery;
40 import org.hibernate.Session;
41 import org.hibernate.StaleObjectStateException;
42 import org.hibernate.StaleStateException;
43 import org.hibernate.Transaction;
44 import org.hibernate.TransientObjectException;
45 import org.hibernate.TypeMismatchException;
46 import org.hibernate.UnresolvableObjectException;
47 import org.hibernate.cfg.Environment;
48 import org.hibernate.ejb.transaction.JoinableCMTTransaction;
49 import org.hibernate.ejb.util.ConfigurationHelper;
50 import org.hibernate.engine.SessionFactoryImplementor;
51 import org.hibernate.engine.SessionImplementor;
52 import org.hibernate.proxy.HibernateProxy;
53 import org.hibernate.transaction.TransactionFactory;
54 import org.hibernate.util.CollectionHelper;
55 import org.hibernate.util.JTAHelper;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60 * @author <a href="mailto:gavin@hibernate.org">Gavin King</a>
61 * @author Emmanuel Bernard
62 */
63 @SuppressWarnings("unchecked")
64 public abstract class AbstractEntityManagerImpl implements HibernateEntityManagerImplementor, Serializable {
65 private static final Logger log = LoggerFactory.getLogger( AbstractEntityManagerImpl.class );
66
67 protected transient TransactionImpl tx = new TransactionImpl( this );
68 protected PersistenceContextType persistenceContextType;
69 private FlushModeType flushModeType = FlushModeType.AUTO;
70 private PersistenceUnitTransactionType transactionType;
71 private Map properties;
72
73 protected AbstractEntityManagerImpl(
74 PersistenceContextType type, PersistenceUnitTransactionType transactionType, Map properties) {
75 this.persistenceContextType = type;
76 this.transactionType = transactionType;
77 this.properties = properties != null ? properties : CollectionHelper.EMPTY_MAP;
78 }
79
80 protected void postInit() {
81 //register in Sync if needed
82 if ( PersistenceUnitTransactionType.JTA.equals( transactionType ) ) joinTransaction( true );
83 Object flushMode = properties.get( "org.hibernate.flushMode" );
84 if (flushMode != null) {
85 getSession().setFlushMode( ConfigurationHelper.getFlushMode( flushMode ) );
86 }
87 this.properties = null;
88 }
89
90 public Query createQuery(String ejbqlString) {
91 //adjustFlushMode();
92 try {
93 return new QueryImpl( getSession().createQuery( ejbqlString ), this );
94 }
95 catch (HibernateException he) {
96 throwPersistenceException( he );
97 return null;
98 }
99 }
100
101 public Query createNamedQuery(String name) {
102 //adjustFlushMode();
103 org.hibernate.Query namedQuery;
104 try {
105 namedQuery = getSession().getNamedQuery( name );
106 }
107 catch (MappingException e) {
108 throw new IllegalArgumentException("Named query not found: " + name);
109 }
110 try {
111 return new QueryImpl( namedQuery, this );
112 }
113 catch (HibernateException he) {
114 throwPersistenceException( he );
115 return null;
116 }
117 }
118
119 public Query createNativeQuery(String sqlString) {
120 //adjustFlushMode();
121 try {
122 SQLQuery q = getSession().createSQLQuery( sqlString );
123 return new QueryImpl( q, this );
124 }
125 catch (HibernateException he) {
126 throwPersistenceException( he );
127 return null;
128 }
129 }
130
131 public Query createNativeQuery(String sqlString, Class resultClass) {
132 //adjustFlushMode();
133 try {
134 SQLQuery q = getSession().createSQLQuery( sqlString );
135 q.addEntity( "alias1", resultClass.getName(), LockMode.READ );
136 return new QueryImpl( q, this );
137 }
138 catch (HibernateException he) {
139 throwPersistenceException( he );
140 return null;
141 }
142 }
143
144 public Query createNativeQuery(String sqlString, String resultSetMapping) {
145 //adjustFlushMode();
146 try {
147 SQLQuery q = getSession().createSQLQuery( sqlString );
148 q.setResultSetMapping( resultSetMapping );
149 return new QueryImpl( q, this );
150 }
151 catch (HibernateException he) {
152 throwPersistenceException( he );
153 return null;
154 }
155 }
156
157 @SuppressWarnings("unchecked")
158 public <T> T getReference(Class<T> entityClass, Object primaryKey) {
159 //adjustFlushMode();
160 try {
161 return (T) getSession().load( entityClass, (Serializable) primaryKey );
162 }
163 catch (MappingException e) {
164 throw new IllegalArgumentException( e.getMessage(), e );
165 }
166 catch (TypeMismatchException e ) {
167 throw new IllegalArgumentException( e.getMessage(), e );
168 }
169 catch (ClassCastException e) {
170 throw new IllegalArgumentException( e.getMessage(), e );
171 }
172 catch (HibernateException he) {
173 throwPersistenceException( he );
174 return null;
175 }
176 }
177
178 @SuppressWarnings("unchecked")
179 public <A> A find(Class<A> entityClass, Object primaryKey) {
180 //adjustFlushMode();
181 try {
182 return (A) getSession().get( entityClass, (Serializable) primaryKey );
183 }
184 catch (ObjectDeletedException e) {
185 //the spec is silent about people doing remove() find() on the same PC
186 return null;
187 }
188 catch (ObjectNotFoundException e) {
189 //should not happen on the entity itself with get
190 throw new IllegalArgumentException( e.getMessage(), e );
191 }
192 catch (MappingException e) {
193 throw new IllegalArgumentException( e.getMessage(), e );
194 }
195 catch (TypeMismatchException e ) {
196 throw new IllegalArgumentException( e.getMessage(), e );
197 }
198 catch (ClassCastException e) {
199 throw new IllegalArgumentException( e.getMessage(), e );
200 }
201 catch (HibernateException he) {
202 throwPersistenceException( he );
203 return null;
204 }
205 }
206
207 private void checkTransactionNeeded() {
208 if ( persistenceContextType == PersistenceContextType.TRANSACTION && ! isTransactionInProgress() ) {
209 //no need to mark as rollback, no tx in progress
210 throw new TransactionRequiredException(
211 "no transaction is in progress for a TRANSACTION type persistence context"
212 );
213 }
214 }
215
216 public void persist(Object entity) {
217 checkTransactionNeeded();
218 //adjustFlushMode();
219 try {
220 getSession().persist( entity );
221 }
222 catch (MappingException e) {
223 throw new IllegalArgumentException( e.getMessage() );
224 }
225 catch (HibernateException he) {
226 throwPersistenceException( he );
227 }
228 }
229
230 @SuppressWarnings("unchecked")
231 public <A> A merge(A entity) {
232 checkTransactionNeeded();
233 //adjustFlushMode();
234 try {
235 return (A) getSession().merge( entity );
236 }
237 catch (ObjectDeletedException sse) {
238 throw new IllegalArgumentException( sse );
239 }
240 catch (MappingException e) {
241 throw new IllegalArgumentException( e.getMessage(), e );
242 }
243 catch (HibernateException he) {
244 throwPersistenceException( he );
245 return null;
246 }
247 }
248
249 public void remove(Object entity) {
250 checkTransactionNeeded();
251 //adjustFlushMode();
252 try {
253 getSession().delete( entity );
254 }
255 catch (MappingException e) {
256 throw new IllegalArgumentException( e.getMessage(), e );
257 }
258 catch (HibernateException he) {
259 throwPersistenceException( he );
260 }
261 }
262
263 public void refresh(Object entity) {
264 checkTransactionNeeded();
265 //adjustFlushMode();
266 try {
267 if ( ! getSession().contains( entity ) ) {
268 throw new IllegalArgumentException( "Entity not managed" );
269 }
270 getSession().refresh( entity );
271 }
272 catch (MappingException e) {
273 throw new IllegalArgumentException( e.getMessage(), e );
274 }
275 catch (HibernateException he) {
276 throwPersistenceException( he );
277 }
278 }
279
280 public boolean contains(Object entity) {
281 try {
282 if ( entity != null
283 && ! ( entity instanceof HibernateProxy )
284 && getSession().getSessionFactory().getClassMetadata( entity.getClass() ) == null ) {
285 throw new IllegalArgumentException( "Not an entity:" + entity.getClass() );
286 }
287 return getSession().contains( entity );
288 }
289 catch (MappingException e) {
290 throw new IllegalArgumentException( e.getMessage(), e );
291 }
292 catch (HibernateException he) {
293 throwPersistenceException( he );
294 return false;
295 }
296 }
297
298 public void flush() {
299 try {
300 if ( ! isTransactionInProgress() ) {
301 throw new TransactionRequiredException( "no transaction is in progress" );
302 }
303 //adjustFlushMode();
304 getSession().flush();
305 }
306 catch (HibernateException he) {
307 throwPersistenceException( he );
308 }
309 }
310
311 /**
312 * return a Session
313 * @throws IllegalStateException if the entity manager is closed
314 */
315 public abstract Session getSession();
316
317 /**
318 * Return a Session (even if the entity manager is closed
319 */
320 protected abstract Session getRawSession();
321
322 public EntityTransaction getTransaction() {
323 if ( transactionType == PersistenceUnitTransactionType.JTA ) {
324 throw new IllegalStateException( "A JTA EntityManager cannot use getTransaction()" );
325 }
326 return tx;
327 }
328
329 public void setFlushMode(FlushModeType flushModeType) {
330 this.flushModeType = flushModeType;
331 if ( flushModeType == FlushModeType.AUTO ) {
332 getSession().setFlushMode( FlushMode.AUTO );
333 }
334 else if ( flushModeType == FlushModeType.COMMIT ) {
335 getSession().setFlushMode( FlushMode.COMMIT );
336 }
337 else {
338 throw new AssertionFailure( "Unknown FlushModeType: " + flushModeType );
339 }
340 }
341
342 public void clear() {
343 //adjustFlushMode();
344 try {
345 getSession().clear();
346 }
347 catch (HibernateException he) {
348 throwPersistenceException( he );
349 }
350 }
351
352 public FlushModeType getFlushMode() {
353 FlushMode mode = getSession().getFlushMode();
354 if ( mode == FlushMode.AUTO ) {
355 this.flushModeType = FlushModeType.AUTO;
356 }
357 else if ( mode == FlushMode.COMMIT ) {
358 this.flushModeType = FlushModeType.COMMIT;
359 }
360 // else if ( mode == FlushMode.NEVER ) {
361 // if ( PersistenceContextType.EXTENDED == persistenceContextType && !isTransactionInProgress() ) {
362 // //we are in flushMode none for EXTENDED
363 // return flushMode;
364 // }
365 // else {
366 // return null; //TODO exception?
367 // }
368 // }
369 else {
370 return null; //TODO exception?
371 }
372 //otherwise this is an unknown mode for EJB3
373 return flushModeType;
374 }
375
376 public void lock(Object entity, LockModeType lockMode) {
377 try {
378 if ( ! isTransactionInProgress() ) {
379 throw new TransactionRequiredException( "no transaction is in progress" );
380 }
381 //adjustFlushMode();
382 if ( !contains( entity ) ) throw new IllegalArgumentException( "entity not in the persistence context" );
383 getSession().lock( entity, getLockMode( lockMode ) );
384 }
385 catch (HibernateException he) {
386 throwPersistenceException( he );
387 }
388 }
389
390 private LockMode getLockMode(LockModeType lockMode) {
391 switch ( lockMode ) {
392 case READ:
393 return LockMode.UPGRADE; //assuming we are on read-commited and we need to prevent non repeteable read
394 case WRITE:
395 return LockMode.FORCE;
396 default:
397 throw new AssertionFailure( "Unknown LockModeType: " + lockMode );
398 }
399 }
400
401 public boolean isTransactionInProgress() {
402 return ( (SessionImplementor) getRawSession() ).isTransactionInProgress();
403 }
404
405 protected void markAsRollback() {
406 log.debug( "mark transaction for rollback");
407 if ( tx.isActive() ) {
408 tx.setRollbackOnly();
409 }
410 else {
411 //no explicit use of the tx. boudaries methods
412 if ( PersistenceUnitTransactionType.JTA == transactionType ) {
413 TransactionManager transactionManager =
414 ( (SessionFactoryImplementor) getRawSession().getSessionFactory() ).getTransactionManager();
415 if ( transactionManager == null ) {
416 throw new PersistenceException(
417 "Using a JTA persistence context wo setting hibernate.transaction.manager_lookup_class"
418 );
419 }
420 try {
421 transactionManager.setRollbackOnly();
422 }
423 catch (SystemException e) {
424 throw new PersistenceException( "Unable to set the JTA transaction as RollbackOnly", e );
425 }
426 }
427 }
428 }
429
430 public void joinTransaction() {
431 joinTransaction( false );
432 }
433
434 private void joinTransaction(boolean ignoreNotJoining) {
435 //set the joined status
436 getSession().isOpen(); //for sync
437 if ( transactionType == PersistenceUnitTransactionType.JTA ) {
438 try {
439 log.debug( "Looking for a JTA transaction to join" );
440 final Session session = getSession();
441 final Transaction transaction = session.getTransaction();
442 if ( transaction != null && transaction instanceof JoinableCMTTransaction ) {
443 //can't handle it if not a joinnable transaction
444 final JoinableCMTTransaction joinableCMTTransaction = (JoinableCMTTransaction) transaction;
445
446 if ( joinableCMTTransaction.getStatus() == JoinableCMTTransaction.JoinStatus.JOINED ) {
447 log.debug( "Transaction already joined" );
448 return; //no-op
449 }
450 joinableCMTTransaction.markForJoined();
451 session.isOpen(); //register to the Tx
452 if ( joinableCMTTransaction.getStatus() == JoinableCMTTransaction.JoinStatus.NOT_JOINED ) {
453 if ( ignoreNotJoining ) {
454 log.debug( "No JTA transaction found" );
455 return;
456 }
457 else {
458 throw new TransactionRequiredException(
459 "No active JTA transaction on joinTransaction call"
460 );
461 }
462 }
463 else
464 if ( joinableCMTTransaction.getStatus() == JoinableCMTTransaction.JoinStatus.MARKED_FOR_JOINED ) {
465 throw new AssertionFailure( "Transaction MARKED_FOR_JOINED after isOpen() call" );
466 }
467 //flush before completion and
468 //register clear on rollback
469 log.trace( "Adding flush() and close() synchronization" );
470 joinableCMTTransaction.registerSynchronization(
471 new Synchronization() {
472 public void beforeCompletion() {
473 boolean flush = false;
474 TransactionFactory.Context ctx = null;
475 try {
476 ctx = (TransactionFactory.Context) session;
477 JoinableCMTTransaction joinable = (JoinableCMTTransaction) session.getTransaction();
478 javax.transaction.Transaction transaction = joinable.getTransaction();
479 if ( transaction == null )
480 log.warn( "Transaction not available on beforeCompletionPhase: assuming valid" );
481 flush = !ctx.isFlushModeNever() &&
482 //ctx.isFlushBeforeCompletionEnabled() &&
483 //TODO probably make it ! isFlushBeforecompletion()
484 ( transaction == null || !JTAHelper.isRollback( transaction.getStatus() ) );
485 //transaction == null workaround a JBoss TMBug
486 }
487 catch (SystemException se) {
488 log.error( "could not determine transaction status", se );
489 //throwPersistenceException will mark the transaction as rollbacked
490 throwPersistenceException(
491 new PersistenceException(
492 "could not determine transaction status in beforeCompletion()",
493 se
494 )
495 );
496 }
497 catch (HibernateException he) {
498 throwPersistenceException( he );
499 }
500
501 try {
502 if ( flush ) {
503 log.trace( "automatically flushing session" );
504 ctx.managedFlush();
505 }
506 else {
507 log.trace( "skipping managed flushing" );
508 }
509 }
510 catch (RuntimeException re) {
511 //throwPersistenceException will mark the transaction as rollbacked
512 if ( re instanceof HibernateException ) {
513 throwPersistenceException( (HibernateException) re );
514 }
515 else {
516 throwPersistenceException( new PersistenceException( re ) );
517 }
518 }
519 }
520
521 public void afterCompletion(int status) {
522 try {
523 if ( Status.STATUS_ROLLEDBACK == status
524 && transactionType == PersistenceUnitTransactionType.JTA ) {
525 if ( session.isOpen() ) {
526 session.clear();
527 }
528 }
529 if ( session.isOpen() ) {
530 //only reset if the session is opened since you can't get the Transaction otherwise
531 JoinableCMTTransaction joinable = (JoinableCMTTransaction) session.getTransaction();
532 joinable.resetStatus();
533 }
534 }
535 catch (HibernateException e) {
536 throwPersistenceException( e );
537 }
538 }
539 }
540 );
541 }
542 else {
543 log.warn( "Cannot join transaction: do not override {}", Environment.TRANSACTION_STRATEGY );
544 }
545 }
546 catch (HibernateException he) {
547 throwPersistenceException( he );
548 }
549 }
550 else {
551 if ( !ignoreNotJoining ) log.warn( "Calling joinTransaction() on a non JTA EntityManager" );
552 }
553 }
554
555 /**
556 * returns the underlying session
557 */
558 public Object getDelegate() {
559 return getSession();
560 }
561
562 ;
563
564 private void writeObject(ObjectOutputStream oos) throws IOException {
565 oos.defaultWriteObject();
566 }
567
568 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
569 ois.defaultReadObject();
570 tx = new TransactionImpl( this );
571 }
572
573 public void throwPersistenceException(PersistenceException e) {
574 if ( ! ( e instanceof NoResultException || e instanceof NonUniqueResultException ) ) {
575 try {
576 markAsRollback();
577 }
578 catch (Exception ne) {
579 //we do not want the subsequent exception to swallow the original one
580 log.error( "Unable to mark for rollback on PersistenceException: ", ne);
581 }
582 }
583 throw e;
584 }
585
586 public void throwPersistenceException(HibernateException e) {
587 if ( e instanceof StaleStateException ) {
588 PersistenceException pe = wrapStaleStateException( (StaleStateException) e );
589 throwPersistenceException( pe );
590 }
591 else if ( e instanceof ObjectNotFoundException ) {
592 throwPersistenceException( new EntityNotFoundException( e.getMessage() ) );
593 }
594 else if ( e instanceof org.hibernate.NonUniqueResultException ) {
595 throwPersistenceException( new NonUniqueResultException( e.getMessage() ) );
596 }
597 else if ( e instanceof UnresolvableObjectException ) {
598 throwPersistenceException( new EntityNotFoundException( e.getMessage() ) );
599 }
600 else if ( e instanceof QueryException ) {
601 throw new IllegalArgumentException( e );
602 }
603 else if ( e instanceof TransientObjectException ) {
604 try {
605 markAsRollback();
606 }
607 catch (Exception ne) {
608 //we do not want the subsequent exception to swallow the original one
609 log.error( "Unable to mark for rollback on TransientObjectException: ", ne);
610 }
611 throw new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules
612 }
613 else {
614 throwPersistenceException( new PersistenceException( e ) );
615 }
616 }
617
618 public PersistenceException wrapStaleStateException(StaleStateException e) {
619 PersistenceException pe;
620 if ( e instanceof StaleObjectStateException ) {
621 StaleObjectStateException sose = (StaleObjectStateException) e;
622 Serializable identifier = sose.getIdentifier();
623 if (identifier != null) {
624 Object entity = getRawSession().load( sose.getEntityName(), identifier );
625 if ( entity instanceof Serializable ) {
626 //avoid some user errors regarding boundary crossing
627 pe = new OptimisticLockException( null, e, entity );
628 }
629 else {
630 pe = new OptimisticLockException( e );
631 }
632 }
633 else {
634 pe = new OptimisticLockException( e );
635 }
636 }
637 else {
638 pe = new OptimisticLockException( e );
639 }
640 return pe;
641 }
642 }