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