mirror of
https://github.com/jlengrand/helidon.git
synced 2026-03-10 08:21:17 +00:00
Addressing several rollback-related issues (#1146)
Addresses several JPA rollback-related issues. Signed-off-by: Laird Nelson <laird.nelson@oracle.com>
This commit is contained in:
@@ -108,12 +108,44 @@ public final class ReferenceCountedContext implements AlterableContext {
|
||||
* @see Contextual#destroy(Object, CreationalContext)
|
||||
*
|
||||
* @see #get(Contextual, CreationalContext)
|
||||
*/
|
||||
public int decrementReferenceCount(final Contextual<?> c) {
|
||||
return this.decrementReferenceCount(c, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the reference count of the contextual instance, if
|
||||
* any, associated with the combination of the current thread and
|
||||
* the supplied {@link Contextual}, {@linkplain
|
||||
* Contextual#destroy(Object, CreationalContext) destroying} the
|
||||
* instance if and only if the reference count becomes less than
|
||||
* or equal to zero, and returns the resulting reference count.
|
||||
*
|
||||
* <p>Most users will never have to call this method.</p>
|
||||
*
|
||||
* @param c the {@link Contextual} whose instance's reference
|
||||
* count should be decremented; may be {@code null} in which case
|
||||
* no action will be taken and {@code 0} will be returned
|
||||
*
|
||||
* @param amount the amount by which to decrement; must be greater
|
||||
* than or (trivially) equal to {@code 0}
|
||||
*
|
||||
* @return the resulting reference count
|
||||
*
|
||||
* @exception IllegalArgumentException if {@code amount} is less
|
||||
* than {@code 0}
|
||||
*
|
||||
* @see Contextual#destroy(Object, CreationalContext)
|
||||
*
|
||||
* @see #get(Contextual, CreationalContext)
|
||||
*
|
||||
* @see #decrementReferenceCount(Contextual)
|
||||
*/
|
||||
public int decrementReferenceCount(final Contextual<?> c) {
|
||||
public int decrementReferenceCount(final Contextual<?> c, final int amount) {
|
||||
final int returnValue;
|
||||
if (c == null) {
|
||||
if (amount < 0) {
|
||||
throw new IllegalArgumentException("amount < 0: " + amount);
|
||||
} else if (c == null) {
|
||||
returnValue = 0;
|
||||
} else {
|
||||
final Map<?, ? extends Instance<?>> instances = ALL_INSTANCES.get().get(this);
|
||||
@@ -128,7 +160,7 @@ public final class ReferenceCountedContext implements AlterableContext {
|
||||
// will cause c.destroy(theObject,
|
||||
// creationalContext) to be called if needed; no
|
||||
// need to do it explicitly here.
|
||||
returnValue = instance.decrementReferenceCount();
|
||||
returnValue = instance.decrementReferenceCount(amount);
|
||||
if (returnValue <= 0) {
|
||||
instances.remove(c);
|
||||
}
|
||||
@@ -138,6 +170,42 @@ public final class ReferenceCountedContext implements AlterableContext {
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference count of the contextual instance, if
|
||||
* any, associated with the combination of the current thread and
|
||||
* the supplied {@link Contextual}.
|
||||
*
|
||||
* <p>Most users will never have to call this method.</p>
|
||||
*
|
||||
* <p>This method never returns a negative number.</p>
|
||||
*
|
||||
* @param c the {@link Contextual} whose instance's reference
|
||||
* count should be returned; may be {@code null} in which case
|
||||
* {@code 0} will be returned
|
||||
*
|
||||
* @return the reference count in question; never a negative
|
||||
* number
|
||||
*/
|
||||
public int getReferenceCount(final Contextual<?> c) {
|
||||
final int returnValue;
|
||||
if (c == null) {
|
||||
returnValue = 0;
|
||||
} else {
|
||||
final Map<?, ? extends Instance<?>> instances = ALL_INSTANCES.get().get(this);
|
||||
if (instances == null) {
|
||||
returnValue = 0;
|
||||
} else {
|
||||
final Instance<?> instance = instances.get(c);
|
||||
if (instance == null) {
|
||||
returnValue = 0;
|
||||
} else {
|
||||
returnValue = Math.max(0, instance.getReferenceCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the {@link #decrementReferenceCount(Contextual)} method
|
||||
* with the supplied {@link Contextual}, destroying it if and only
|
||||
@@ -350,7 +418,7 @@ public final class ReferenceCountedContext implements AlterableContext {
|
||||
* #get()} invocations will throw {@link
|
||||
* IllegalStateException}.</p>
|
||||
*
|
||||
* @see #decrementReferenceCount()
|
||||
* @see #decrementReferenceCount(int)
|
||||
*
|
||||
* @see #get()
|
||||
*/
|
||||
@@ -423,7 +491,7 @@ public final class ReferenceCountedContext implements AlterableContext {
|
||||
*
|
||||
* @exception IllegalStateException if the reference count
|
||||
* internally is less than zero, such as when an invocation of
|
||||
* {@link #decrementReferenceCount()} has resulted in
|
||||
* {@link #decrementReferenceCount(int)} has resulted in
|
||||
* destruction
|
||||
*/
|
||||
private T get() {
|
||||
@@ -435,26 +503,42 @@ public final class ReferenceCountedContext implements AlterableContext {
|
||||
return this.object;
|
||||
}
|
||||
|
||||
private int getReferenceCount() {
|
||||
return this.referenceCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements this {@link Instance}'s reference count, unless
|
||||
* it is {@code 0}, and returns the result.
|
||||
* Decrements this {@link Instance}'s reference count by the
|
||||
* supplied amount, unless it is already {@code 0}, and
|
||||
* returns the result.
|
||||
*
|
||||
* <p>If the result of decrementing the reference count is
|
||||
* {@code 0}, then this {@link Instance}'s contextual instance
|
||||
* is {@linkplain Contextual#destroy(Object,
|
||||
* CreationalContext) destroyed}, and its internal object
|
||||
* reference is set to {@code null}. Subsequent invocations
|
||||
* of {@link #get()} will throw {@link
|
||||
* less than or equal to {@code 0}, then this {@link
|
||||
* Instance}'s contextual instance is {@linkplain
|
||||
* Contextual#destroy(Object, CreationalContext) destroyed},
|
||||
* and its internal object reference is set to {@code null}.
|
||||
* Subsequent invocations of {@link #get()} will throw {@link
|
||||
* IllegalStateException}.</p>
|
||||
*
|
||||
* <p>Internally, if a reference count ever drops below {@code
|
||||
* 0}, it is set to {@code 0}.</p>
|
||||
*
|
||||
* @param amount the amount to decrement by; must be greater
|
||||
* than or (trivially) equal to {@code 0}
|
||||
*
|
||||
* @return the resulting decremented reference count
|
||||
*
|
||||
* @exception IllegalArgumentException if {@code amount} is
|
||||
* less than {@code 0}
|
||||
*
|
||||
* @exception IllegalStateException if the internal reference
|
||||
* count is already less than or equal to zero
|
||||
*/
|
||||
private int decrementReferenceCount() {
|
||||
if (this.referenceCount > 0) {
|
||||
--this.referenceCount;
|
||||
private int decrementReferenceCount(final int amount) {
|
||||
if (amount < 0) {
|
||||
throw new IllegalArgumentException("amount < 0: " + amount);
|
||||
} else if (this.referenceCount > 0) {
|
||||
this.referenceCount = Math.max(0, this.referenceCount - amount);
|
||||
if (this.referenceCount == 0) {
|
||||
this.contextual.destroy(this.object, this.creationalContext);
|
||||
if (this.creationalContext != null) {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package io.helidon.integrations.cdi.referencecountedcontext;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.context.Initialized;
|
||||
import javax.enterprise.context.RequestScoped;
|
||||
@@ -22,6 +24,7 @@ import javax.enterprise.context.control.RequestContextController;
|
||||
import javax.enterprise.event.Observes;
|
||||
import javax.enterprise.inject.se.SeContainer;
|
||||
import javax.enterprise.inject.se.SeContainerInitializer;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -31,6 +34,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@ApplicationScoped
|
||||
@@ -73,11 +77,21 @@ final class TestReferenceCountedContext {
|
||||
assertNotNull(beanManager);
|
||||
assertNotNull(gorpAsParameter);
|
||||
|
||||
System.out.println("*** let's go");
|
||||
final Set<Bean<?>> gorpBeans = beanManager.getBeans(Gorp.class);
|
||||
assertNotNull(gorpBeans);
|
||||
assertEquals(1, gorpBeans.size());
|
||||
@SuppressWarnings("unchecked")
|
||||
final Bean<Gorp> gorpBean = (Bean<Gorp>)gorpBeans.iterator().next();
|
||||
assertNotNull(gorpBean);
|
||||
|
||||
final ReferenceCountedContext context = ReferenceCountedContext.getInstanceFrom(beanManager);
|
||||
assertNotNull(context);
|
||||
assertTrue(context.isActive());
|
||||
|
||||
// Gorp is @ReferenceCounted. It is proxied. Note that we do
|
||||
// not dereference the gorpAsParameter proxy yet, so no
|
||||
// underlying instance is created.
|
||||
assertEquals(0, context.getReferenceCount(gorpBean));
|
||||
|
||||
Gorp gorp = null;
|
||||
try {
|
||||
@@ -86,14 +100,19 @@ final class TestReferenceCountedContext {
|
||||
// this.bean is @RequestScoped. It houses an instance of
|
||||
// Gorp.
|
||||
assertNotNull(this.bean);
|
||||
assertEquals(0, context.getReferenceCount(gorpBean));
|
||||
|
||||
// Here, the gorp acquired is a client proxy. No
|
||||
// contextual instance has been created yet.
|
||||
gorp = this.bean.getGorp();
|
||||
assertNotNull(gorp);
|
||||
assertEquals(0, context.getReferenceCount(gorpBean));
|
||||
|
||||
// This should cause the underlying instance to be
|
||||
// created. It will be the first Gorp instance created
|
||||
// anywhere.
|
||||
// This will cause the underlying instance to be created.
|
||||
// It will be the first Gorp instance created anywhere.
|
||||
// The reference count is 1.
|
||||
assertEquals(1, gorp.getId());
|
||||
assertEquals(1, context.getReferenceCount(gorpBean));
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -103,11 +122,13 @@ final class TestReferenceCountedContext {
|
||||
// reference count of 0. This Gorp reference is therefore
|
||||
// destroyed.
|
||||
controller.deactivate();
|
||||
assertEquals(0, context.getReferenceCount(gorpBean));
|
||||
}
|
||||
|
||||
// Now we kick the proxy. This will cause another instance to
|
||||
// be created. The reference count will bump to 1.
|
||||
assertEquals(2, gorpAsParameter.getId());
|
||||
assertEquals(1, context.getReferenceCount(gorpBean));
|
||||
|
||||
// Next we refer to the Gorp instance that was referred to by
|
||||
// the request-scoped HousingBean. If Gorp had been
|
||||
@@ -117,6 +138,7 @@ final class TestReferenceCountedContext {
|
||||
// reference count to jump, THIS Gorp instance will now be the
|
||||
// same one as gorpAsParameter.
|
||||
assertEquals(2, gorp.getId());
|
||||
assertEquals(2, context.getReferenceCount(gorpBean));
|
||||
|
||||
}
|
||||
|
||||
|
||||
36
integrations/cdi/jpa-cdi/etc/spotbugs/exclude.xml
Normal file
36
integrations/cdi/jpa-cdi/etc/spotbugs/exclude.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<FindBugsFilter
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://github.com/spotbugs/filter/3.1.0"
|
||||
xsi:schemaLocation="https://github.com/spotbugs/filter/3.1.0
|
||||
https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
|
||||
<Match>
|
||||
<Class name="io.helidon.integrations.cdi.jpa.DelegatingQuery"/>
|
||||
<Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<Class name="io.helidon.integrations.cdi.jpa.DelegatingStoredProcedureQuery"/>
|
||||
<Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<Class name="io.helidon.integrations.cdi.jpa.DelegatingTypedQuery"/>
|
||||
<Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"/>
|
||||
</Match>
|
||||
</FindBugsFilter>
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
<properties>
|
||||
<doclint>-syntax</doclint>
|
||||
<spotbugs.exclude>etc/spotbugs/exclude.xml</spotbugs.exclude>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -21,12 +21,37 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.inject.Instance;
|
||||
import javax.enterprise.inject.Vetoed;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/**
|
||||
* A {@link DelegatingEntityManager} created in certain very specific
|
||||
* JPA-mandated transaction-related scenarios.
|
||||
*
|
||||
* <p>Instances of this class are never directly seen by the end user.
|
||||
* Specifically, instances of this class are themselves returned by a
|
||||
* {@link DelegatingEntityManager} implementation's {@link
|
||||
* DelegatingEntityManager#acquireDelegate()} method and only under
|
||||
* appropriate circumstances.</p>
|
||||
*
|
||||
* <p>This class is added as a synthetic bean by the {@link
|
||||
* JpaExtension} class.</p>
|
||||
*
|
||||
* <h2>Implementation Notes</h2>
|
||||
*
|
||||
* <p>Because instances of this class are typically placed in <a
|
||||
* href="https://jakarta.ee/specifications/cdi/2.0/cdi-spec-2.0.html#normal_scope"
|
||||
* target="_parent"><em>normal scopes</em></a>, this class is not
|
||||
* declared {@code final}, but must be treated as if it were.</p>
|
||||
*
|
||||
* <h2>Thread Safety</h2>
|
||||
*
|
||||
* <p>As with all {@link EntityManager} implementations, instances of
|
||||
* this class are not safe for concurrent use by multiple threads.</p>
|
||||
*
|
||||
* @see JpaExtension
|
||||
*/
|
||||
@Vetoed
|
||||
class CdiTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
|
||||
|
||||
@@ -39,6 +64,8 @@ class CdiTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
|
||||
private final Set<? extends Annotation> suppliedQualifiers;
|
||||
|
||||
private final TransactionSupport transactionSupport;
|
||||
|
||||
private EntityManager delegate;
|
||||
|
||||
private boolean closeDelegate;
|
||||
@@ -66,6 +93,7 @@ class CdiTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
super();
|
||||
this.closeDelegate = true;
|
||||
this.instance = null;
|
||||
this.transactionSupport = null;
|
||||
this.suppliedQualifiers = Collections.emptySet();
|
||||
}
|
||||
|
||||
@@ -85,6 +113,7 @@ class CdiTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
final Set<? extends Annotation> suppliedQualifiers) {
|
||||
super();
|
||||
this.instance = Objects.requireNonNull(instance);
|
||||
this.transactionSupport = instance.select(TransactionSupport.class).get();
|
||||
this.suppliedQualifiers = Objects.requireNonNull(suppliedQualifiers);
|
||||
}
|
||||
|
||||
@@ -94,6 +123,37 @@ class CdiTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of this {@link CdiTransactionScopedEntityManager} by
|
||||
* calling the {@link #close()} method.
|
||||
*
|
||||
* <p>If the {@link javax.transaction.TransactionScoped} scope is
|
||||
* behaving the way it should, then this method will be invoked
|
||||
* only when the underlying transaction has finished committing or
|
||||
* rolling back, and in no other situations. It follows that the
|
||||
* current transaction status must be either {@link
|
||||
* TransactionSupport#STATUS_COMMITTED} or {@link
|
||||
* TransactionSupport#STATUS_ROLLEDBACK}.</p>
|
||||
*
|
||||
* <p>This method must not be overridden. It is not {@code final}
|
||||
* only due to CDI restrictions.</p>
|
||||
*
|
||||
* <h2>Thread Safety</h2>
|
||||
*
|
||||
* <p>This method may be (and often is) called by a thread that is
|
||||
* not the CDI container thread, since transactions may roll back
|
||||
* on such a thread.</p>
|
||||
*
|
||||
* @param ignoredInstance the {@link Instance} supplied by the CDI
|
||||
* bean configuration machinery when the transaction scope is
|
||||
* going away; ignored by this method; may be {@code null}
|
||||
*
|
||||
* @see #close()
|
||||
*/
|
||||
void dispose(final Instance<Object> ignoredInstance) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityManager acquireDelegate() {
|
||||
if (this.delegate == null) {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
|
||||
final class ClearingQuery extends DelegatingQuery {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
ClearingQuery(final EntityManager entityManager, final Query delegate) {
|
||||
super(delegate);
|
||||
this.entityManager = Objects.requireNonNull(entityManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public List getResultList() {
|
||||
try {
|
||||
return super.getResultList();
|
||||
} finally {
|
||||
this.entityManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSingleResult() {
|
||||
try {
|
||||
return super.getSingleResult();
|
||||
} finally {
|
||||
this.entityManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.StoredProcedureQuery;
|
||||
|
||||
final class ClearingStoredProcedureQuery extends DelegatingStoredProcedureQuery {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
ClearingStoredProcedureQuery(final EntityManager entityManager, final StoredProcedureQuery delegate) {
|
||||
super(delegate);
|
||||
this.entityManager = Objects.requireNonNull(entityManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public List getResultList() {
|
||||
try {
|
||||
return super.getResultList();
|
||||
} finally {
|
||||
this.entityManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSingleResult() {
|
||||
try {
|
||||
return super.getSingleResult();
|
||||
} finally {
|
||||
this.entityManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
final class ClearingTypedQuery<X> extends DelegatingTypedQuery<X> {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
ClearingTypedQuery(final EntityManager entityManager, final TypedQuery<X> delegate) {
|
||||
super(delegate);
|
||||
this.entityManager = Objects.requireNonNull(entityManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<X> getResultList() {
|
||||
try {
|
||||
return super.getResultList();
|
||||
} finally {
|
||||
this.entityManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X getSingleResult() {
|
||||
try {
|
||||
return super.getSingleResult();
|
||||
} finally {
|
||||
this.entityManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,8 +36,13 @@ import javax.persistence.metamodel.Metamodel;
|
||||
/**
|
||||
* A partial {@link EntityManager} implementation that forwards all
|
||||
* calls to an underlying {@link EntityManager}.
|
||||
*
|
||||
* <h2>Thread Safety</h2>
|
||||
*
|
||||
* <p>As with all {@link EntityManager} implementations, instances of
|
||||
* this class are not safe for concurrent use by multiple threads.</p>
|
||||
*/
|
||||
abstract class DelegatingEntityManager implements EntityManager {
|
||||
abstract class DelegatingEntityManager implements EntityManager, AutoCloseable {
|
||||
|
||||
|
||||
/*
|
||||
@@ -49,6 +54,8 @@ abstract class DelegatingEntityManager implements EntityManager {
|
||||
* The {@link EntityManager} to which all operations will be
|
||||
* forwarded if it is non-{@code null}.
|
||||
*
|
||||
* <p>This field may be {@code null}.</p>
|
||||
*
|
||||
* @see #DelegatingEntityManager(EntityManager)
|
||||
*
|
||||
* @see #delegate()
|
||||
@@ -64,9 +71,13 @@ abstract class DelegatingEntityManager implements EntityManager {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link DelegatingEntityManager}.
|
||||
* Creates a new {@link DelegatingEntityManager} that will
|
||||
* indirectly invoke the {@link #acquireDelegate()} method as part
|
||||
* of each method invocation to acquire its delegate.
|
||||
*
|
||||
* @see #delegate()
|
||||
*
|
||||
* @see #acquireDelegate()
|
||||
*/
|
||||
DelegatingEntityManager() {
|
||||
this(null);
|
||||
@@ -135,7 +146,8 @@ abstract class DelegatingEntityManager implements EntityManager {
|
||||
*
|
||||
* <p>This method is called by the {@link #delegate()} method and
|
||||
* potentially on every method invocation of instances of this
|
||||
* class so implementations of it must be fast.</p>
|
||||
* class so implementations of it should be as fast as
|
||||
* possible.</p>
|
||||
*
|
||||
* <p>Implementations of this method must not call the {@link
|
||||
* #delegate()} method.</p>
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.Parameter;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
abstract class DelegatingQuery implements Query {
|
||||
|
||||
private final Query delegate;
|
||||
|
||||
DelegatingQuery(final Query delegate) {
|
||||
super();
|
||||
this.delegate = Objects.requireNonNull(delegate);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public List getResultList() {
|
||||
return this.delegate.getResultList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getSingleResult() {
|
||||
return this.delegate.getSingleResult();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getMaxResults() {
|
||||
return this.delegate.getMaxResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setMaxResults(final int maxResults) {
|
||||
this.delegate.setMaxResults(maxResults);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getFirstResult() {
|
||||
return this.delegate.getFirstResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setFirstResult(final int firstResult) {
|
||||
this.delegate.setFirstResult(firstResult);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getHints() {
|
||||
return this.delegate.getHints();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Query setHint(final String hintName, final Object value) {
|
||||
this.delegate.setHint(hintName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Parameter<?> getParameter(final int position) {
|
||||
return this.delegate.getParameter(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Parameter<T> getParameter(final int position, final Class<T> type) {
|
||||
return this.delegate.getParameter(position, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameter<?> getParameter(final String name) {
|
||||
return this.delegate.getParameter(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Parameter<T> getParameter(final String name, final Class<T> type) {
|
||||
return this.delegate.getParameter(name, type);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Parameter<?>> getParameters() {
|
||||
return this.delegate.getParameters();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> T getParameterValue(final Parameter<T> parameter) {
|
||||
return this.delegate.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParameterValue(final int position) {
|
||||
return this.delegate.getParameterValue(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParameterValue(final String name) {
|
||||
return this.delegate.getParameterValue(name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isBound(final Parameter<?> parameter) {
|
||||
return this.delegate.isBound(parameter);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> Query setParameter(final Parameter<T> parameter, final T value) {
|
||||
this.delegate.setParameter(parameter, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final Parameter<Calendar> parameter, final Calendar value, final TemporalType temporalType) {
|
||||
this.delegate.setParameter(parameter, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final Parameter<Date> parameter, final Date value, final TemporalType temporalType) {
|
||||
this.delegate.setParameter(parameter, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Query setParameter(final int position, final Calendar value, final TemporalType temporalType) {
|
||||
this.delegate.setParameter(position, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final String name, final Calendar value, final TemporalType temporalType) {
|
||||
this.delegate.setParameter(name, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Query setParameter(final int position, final Date value, final TemporalType temporalType) {
|
||||
this.delegate.setParameter(position, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final String name, final Date value, final TemporalType temporalType) {
|
||||
this.delegate.setParameter(name, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Query setParameter(final int position, final Object value) {
|
||||
this.delegate.setParameter(position, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final String name, final Object value) {
|
||||
this.delegate.setParameter(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FlushModeType getFlushMode() {
|
||||
return this.delegate.getFlushMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setFlushMode(final FlushModeType flushMode) {
|
||||
this.delegate.setFlushMode(flushMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LockModeType getLockMode() {
|
||||
return this.delegate.getLockMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setLockMode(final LockModeType lockMode) {
|
||||
this.delegate.setLockMode(lockMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int executeUpdate() {
|
||||
return this.delegate.executeUpdate();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(final Class<T> cls) {
|
||||
return this.delegate.unwrap(cls);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.Parameter;
|
||||
import javax.persistence.ParameterMode;
|
||||
import javax.persistence.StoredProcedureQuery;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
abstract class DelegatingStoredProcedureQuery extends DelegatingQuery implements StoredProcedureQuery {
|
||||
|
||||
private final StoredProcedureQuery delegate;
|
||||
|
||||
DelegatingStoredProcedureQuery(final StoredProcedureQuery delegate) {
|
||||
super(delegate);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getOutputParameterValue(final int position) {
|
||||
return this.delegate.getOutputParameterValue(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getOutputParameterValue(final String parameterName) {
|
||||
return this.delegate.getOutputParameterValue(parameterName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasMoreResults() {
|
||||
return this.delegate.hasMoreResults();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getUpdateCount() {
|
||||
return this.delegate.getUpdateCount();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public DelegatingStoredProcedureQuery registerStoredProcedureParameter(final int position,
|
||||
final Class type,
|
||||
final ParameterMode mode) {
|
||||
this.delegate.registerStoredProcedureParameter(position, type, mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public DelegatingStoredProcedureQuery registerStoredProcedureParameter(final String parameterName,
|
||||
final Class type,
|
||||
final ParameterMode mode) {
|
||||
this.delegate.registerStoredProcedureParameter(parameterName, type, mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setFlushMode(FlushModeType flushMode) {
|
||||
return (DelegatingStoredProcedureQuery) super.setFlushMode(flushMode);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setHint(final String hintName,
|
||||
final Object value) {
|
||||
return (DelegatingStoredProcedureQuery) super.setHint(hintName, value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> DelegatingStoredProcedureQuery setParameter(final Parameter<T> parameter,
|
||||
final T value) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(parameter, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final Parameter<Calendar> parameter,
|
||||
final Calendar value,
|
||||
final TemporalType temporalType) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(parameter, value, temporalType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final Parameter<Date> parameter,
|
||||
final Date value,
|
||||
final TemporalType temporalType) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(parameter, value, temporalType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final int position,
|
||||
final Object value) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(position, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final int position,
|
||||
final Calendar value,
|
||||
final TemporalType temporalType) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(position, value, temporalType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final int position,
|
||||
final Date value,
|
||||
final TemporalType temporalType) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(position, value, temporalType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final String name,
|
||||
final Object value) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final String name,
|
||||
final Calendar value,
|
||||
final TemporalType temporalType) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(name, value, temporalType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DelegatingStoredProcedureQuery setParameter(final String name,
|
||||
final Date value,
|
||||
final TemporalType temporalType) {
|
||||
return (DelegatingStoredProcedureQuery) super.setParameter(name, value, temporalType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean execute() {
|
||||
return this.delegate.execute();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.Parameter;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
abstract class DelegatingTypedQuery<X> implements TypedQuery<X> {
|
||||
|
||||
private final TypedQuery<X> delegate;
|
||||
|
||||
DelegatingTypedQuery(final TypedQuery<X> delegate) {
|
||||
super();
|
||||
this.delegate = Objects.requireNonNull(delegate);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<X> getResultList() {
|
||||
return this.delegate.getResultList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public X getSingleResult() {
|
||||
return this.delegate.getSingleResult();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getMaxResults() {
|
||||
return this.delegate.getMaxResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setMaxResults(final int maxResults) {
|
||||
this.delegate.setMaxResults(maxResults);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getFirstResult() {
|
||||
return this.delegate.getFirstResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setFirstResult(final int startPosition) {
|
||||
this.delegate.setFirstResult(startPosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getHints() {
|
||||
return this.delegate.getHints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setHint(final String hintName, final Object value) {
|
||||
this.delegate.setHint(hintName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Parameter<?>> getParameters() {
|
||||
return this.delegate.getParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameter<?> getParameter(final String name) {
|
||||
return this.delegate.getParameter(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Parameter<T> getParameter(final String name, final Class<T> type) {
|
||||
return this.delegate.getParameter(name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameter<?> getParameter(final int position) {
|
||||
return this.delegate.getParameter(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Parameter<T> getParameter(final int position, final Class<T> type) {
|
||||
return this.delegate.getParameter(position, type);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> T getParameterValue(final Parameter<T> parameter) {
|
||||
return this.delegate.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParameterValue(final String name) {
|
||||
return this.delegate.getParameterValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParameterValue(final int position) {
|
||||
return this.delegate.getParameterValue(position);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isBound(final Parameter<?> parameter) {
|
||||
return this.delegate.isBound(parameter);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<X> setParameter(final Parameter<T> parameter,
|
||||
final T value) {
|
||||
this.delegate.setParameter(parameter, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final Parameter<Calendar> parameter,
|
||||
final Calendar value,
|
||||
final TemporalType temporalType) {
|
||||
this.delegate.setParameter(parameter, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final Parameter<Date> parameter,
|
||||
final Date value,
|
||||
final TemporalType temporalType) {
|
||||
this.delegate.setParameter(parameter, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final int position,
|
||||
final Object value) {
|
||||
this.delegate.setParameter(position, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final int position,
|
||||
final Calendar value,
|
||||
final TemporalType temporalType) {
|
||||
this.delegate.setParameter(position, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final int position,
|
||||
final Date value,
|
||||
final TemporalType temporalType) {
|
||||
this.delegate.setParameter(position, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final String name,
|
||||
final Object value) {
|
||||
this.delegate.setParameter(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final String name,
|
||||
final Calendar value,
|
||||
final TemporalType temporalType) {
|
||||
this.delegate.setParameter(name, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setParameter(final String name,
|
||||
final Date value,
|
||||
final TemporalType temporalType) {
|
||||
this.delegate.setParameter(name, value, temporalType);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FlushModeType getFlushMode() {
|
||||
return this.delegate.getFlushMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setFlushMode(final FlushModeType flushMode) {
|
||||
this.delegate.setFlushMode(flushMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LockModeType getLockMode() {
|
||||
return this.delegate.getLockMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedQuery<X> setLockMode(final LockModeType lockMode) {
|
||||
this.delegate.setLockMode(lockMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int executeUpdate() {
|
||||
return this.delegate.executeUpdate();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(final Class<T> cls) {
|
||||
return this.delegate.unwrap(cls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.Dependent;
|
||||
import javax.enterprise.context.spi.Context;
|
||||
import javax.enterprise.inject.CreationException;
|
||||
import javax.enterprise.inject.Instance;
|
||||
@@ -68,8 +67,8 @@ final class ExtendedEntityManager extends DelegatingEntityManager {
|
||||
this.suppliedQualifiers = Objects.requireNonNull(suppliedQualifiers);
|
||||
this.beanManager = Objects.requireNonNull(beanManager);
|
||||
this.transactionSupport = Objects.requireNonNull(instance.select(TransactionSupport.class).get());
|
||||
if (!transactionSupport.isActive()) {
|
||||
throw new IllegalArgumentException("!transactionSupport.isActive()");
|
||||
if (!transactionSupport.isEnabled()) {
|
||||
throw new IllegalArgumentException("!transactionSupport.isEnabled()");
|
||||
}
|
||||
this.isSynchronized = !suppliedQualifiers.contains(Unsynchronized.Literal.INSTANCE);
|
||||
assert
|
||||
@@ -252,7 +251,7 @@ final class ExtendedEntityManager extends DelegatingEntityManager {
|
||||
final Bean<?> cdiTransactionScopedEntityManagerBean =
|
||||
(Bean<CdiTransactionScopedEntityManager>) this.beanManager.resolve(cdiTransactionScopedEntityManagerBeans);
|
||||
assert cdiTransactionScopedEntityManagerBean != null;
|
||||
assert !Dependent.class.equals(cdiTransactionScopedEntityManagerBean.getScope());
|
||||
assert context.getScope().equals(cdiTransactionScopedEntityManagerBean.getScope());
|
||||
|
||||
// Using that bean, check the Context to see if there's
|
||||
// already a container-managed EntityManager enrolled in
|
||||
|
||||
@@ -1024,7 +1024,8 @@ public class JpaExtension implements Extension {
|
||||
* #addJpaTransactionScopedEntityManagerBeans(AfterBeanDiscovery,
|
||||
* Set)
|
||||
*/
|
||||
private void addContainerManagedJpaBeans(final AfterBeanDiscovery event, final BeanManager beanManager) {
|
||||
private void addContainerManagedJpaBeans(final AfterBeanDiscovery event, final BeanManager beanManager)
|
||||
throws ReflectiveOperationException {
|
||||
final String cn = JpaExtension.class.getName();
|
||||
final String mn = "addContainerManagedJpaBeans";
|
||||
if (LOGGER.isLoggable(Level.FINER)) {
|
||||
@@ -1110,7 +1111,8 @@ public class JpaExtension implements Extension {
|
||||
}
|
||||
|
||||
private void addCdiTransactionScopedEntityManagerBeans(final AfterBeanDiscovery event,
|
||||
final Set<Annotation> suppliedQualifiers) {
|
||||
final Set<Annotation> suppliedQualifiers)
|
||||
throws ReflectiveOperationException {
|
||||
final String cn = JpaExtension.class.getName();
|
||||
final String mn = "addCdiTransactionScopedEntityManagerBeans";
|
||||
if (LOGGER.isLoggable(Level.FINER)) {
|
||||
@@ -1127,15 +1129,16 @@ public class JpaExtension implements Extension {
|
||||
// @Inject
|
||||
// @ContainerManaged
|
||||
// @CdiTransactionScoped
|
||||
// @Synchronized
|
||||
// @Synchronized // <-- NOTE
|
||||
// @Named("test")
|
||||
// private final EntityManager cdiTransactionScopedEm;
|
||||
//
|
||||
// ...AND:
|
||||
//
|
||||
// @Inject
|
||||
// @ContainerManaged
|
||||
// @CdiTransactionScoped
|
||||
// @Unynchronized
|
||||
// @Unsynchronized // <-- NOTE
|
||||
// @Named("test")
|
||||
// private final EntityManager cdiTransactionScopedEm;
|
||||
final Set<Annotation> qualifiers = new HashSet<>(suppliedQualifiers);
|
||||
@@ -1171,7 +1174,7 @@ public class JpaExtension implements Extension {
|
||||
|
||||
qualifiers.add(Synchronized.Literal.INSTANCE);
|
||||
final Set<Annotation> synchronizedQualifiers = new HashSet<>(qualifiers);
|
||||
event.addBean()
|
||||
event.<CdiTransactionScopedEntityManager>addBean()
|
||||
.addTransitiveTypeClosure(CdiTransactionScopedEntityManager.class)
|
||||
.scope(scope)
|
||||
.addQualifiers(synchronizedQualifiers)
|
||||
@@ -1179,12 +1182,12 @@ public class JpaExtension implements Extension {
|
||||
// On its own line to ease debugging.
|
||||
return new CdiTransactionScopedEntityManager(instance, synchronizedQualifiers);
|
||||
})
|
||||
.disposeWith((em, instance) -> em.close());
|
||||
.disposeWith(CdiTransactionScopedEntityManager::dispose);
|
||||
|
||||
qualifiers.remove(Synchronized.Literal.INSTANCE);
|
||||
qualifiers.add(Unsynchronized.Literal.INSTANCE);
|
||||
final Set<Annotation> unsynchronizedQualifiers = new HashSet<>(qualifiers);
|
||||
event.addBean()
|
||||
event.<CdiTransactionScopedEntityManager>addBean()
|
||||
.addTransitiveTypeClosure(CdiTransactionScopedEntityManager.class)
|
||||
.scope(scope)
|
||||
.addQualifiers(unsynchronizedQualifiers)
|
||||
@@ -1192,7 +1195,7 @@ public class JpaExtension implements Extension {
|
||||
// On its own line to ease debugging.
|
||||
return new CdiTransactionScopedEntityManager(instance, unsynchronizedQualifiers);
|
||||
})
|
||||
.disposeWith((em, instance) -> em.close());
|
||||
.disposeWith(CdiTransactionScopedEntityManager::dispose);
|
||||
}
|
||||
|
||||
if (LOGGER.isLoggable(Level.FINER)) {
|
||||
@@ -1237,8 +1240,9 @@ public class JpaExtension implements Extension {
|
||||
.produceWith(instance -> {
|
||||
// On its own line to ease debugging.
|
||||
return new JpaTransactionScopedEntityManager(instance, suppliedQualifiers);
|
||||
});
|
||||
// (deliberately no disposeWith())
|
||||
})
|
||||
.disposeWith(JpaTransactionScopedEntityManager::dispose);
|
||||
|
||||
|
||||
if (LOGGER.isLoggable(Level.FINER)) {
|
||||
LOGGER.exiting(cn, mn);
|
||||
@@ -1275,7 +1279,15 @@ public class JpaExtension implements Extension {
|
||||
// On its own line to ease debugging.
|
||||
return new NonTransactionalEntityManager(instance, suppliedQualifiers);
|
||||
})
|
||||
.disposeWith((em, instance) -> em.close());
|
||||
// Revisit: ReferenceCountedContext does not
|
||||
// automatically pick up synthetic beans like this
|
||||
// one. So we have to tell it somehow to "work on"
|
||||
// this bean. Right now this bean is in what amounts
|
||||
// to a thread-specific singleton scope. As it
|
||||
// happens, this might actually be OK.
|
||||
.disposeWith((em, instance) -> {
|
||||
em.close();
|
||||
});
|
||||
}
|
||||
|
||||
if (LOGGER.isLoggable(Level.FINER)) {
|
||||
|
||||
@@ -17,24 +17,48 @@ package io.helidon.integrations.cdi.jpa;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.ContextNotActiveException;
|
||||
import javax.enterprise.context.spi.Context;
|
||||
import javax.enterprise.inject.Any;
|
||||
import javax.enterprise.inject.Default;
|
||||
import javax.enterprise.inject.Instance;
|
||||
import javax.enterprise.inject.Vetoed;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.inject.Provider;
|
||||
import javax.persistence.EntityGraph;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.EntityTransaction;
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.StoredProcedureQuery;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaDelete;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.CriteriaUpdate;
|
||||
import javax.persistence.metamodel.Metamodel;
|
||||
|
||||
import io.helidon.integrations.cdi.referencecountedcontext.ReferenceCountedContext;
|
||||
|
||||
/**
|
||||
* A {@link DelegatingEntityManager} that adheres to the JPA
|
||||
* specification's rules for transaction-scoped {@link
|
||||
* EntityManager}s.
|
||||
*
|
||||
* <h2>Thread Safety</h2>
|
||||
*
|
||||
* <p>As with all {@link EntityManager} implementations, instances of
|
||||
* this class are not safe for concurrent use by multiple threads.</p>
|
||||
*/
|
||||
@Vetoed
|
||||
final class JpaTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
|
||||
|
||||
@@ -42,19 +66,22 @@ final class JpaTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
* Instance fields.
|
||||
*/
|
||||
|
||||
|
||||
private final BeanManager beanManager;
|
||||
|
||||
private final Instance<Object> instance;
|
||||
|
||||
private final TransactionSupport transactionSupport;
|
||||
|
||||
private final Annotation[] cdiTransactionScopedEntityManagerSelectionQualifiersArray;
|
||||
private final Bean<NonTransactionalEntityManager> nonTransactionalEntityManagerBean;
|
||||
|
||||
private final Provider<EntityManager> nonTransactionalEntityManagerProvider;
|
||||
private final NonTransactionalEntityManager nonTransactionalEntityManager;
|
||||
|
||||
private final boolean isUnsynchronized;
|
||||
private final int startingReferenceCount;
|
||||
|
||||
private final Bean<?> cdiTransactionScopedEntityManagerOppositeSynchronizationBean;
|
||||
private final Bean<?> oppositeSynchronizationBean;
|
||||
|
||||
private final CdiTransactionScopedEntityManager cdiTransactionScopedEntityManager;
|
||||
|
||||
|
||||
/*
|
||||
@@ -76,14 +103,14 @@ final class JpaTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
super();
|
||||
Objects.requireNonNull(instance);
|
||||
Objects.requireNonNull(suppliedQualifiers);
|
||||
this.transactionSupport = Objects.requireNonNull(instance.select(TransactionSupport.class).get());
|
||||
if (!transactionSupport.isActive()) {
|
||||
throw new IllegalArgumentException("!instance.select(TransacitonSupport.class).get().isActive()");
|
||||
this.transactionSupport = instance.select(TransactionSupport.class).get();
|
||||
if (!transactionSupport.isEnabled()) {
|
||||
throw new IllegalArgumentException("!instance.select(TransactionSupport.class).get().isEnabled()");
|
||||
}
|
||||
|
||||
this.beanManager = instance.select(BeanManager.class).get();
|
||||
|
||||
this.instance = instance;
|
||||
this.isUnsynchronized = suppliedQualifiers.contains(Unsynchronized.Literal.INSTANCE);
|
||||
|
||||
// This large block is devoted to honoring the slightly odd
|
||||
// provision in the JPA specification that there be only one
|
||||
@@ -93,10 +120,10 @@ final class JpaTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
// SynchronizationType.UNSYNCHRONIZED. Which one it is is
|
||||
// determined by who "gets there first". That is, by spec, if
|
||||
// someone requests a synchronized transaction scoped entity
|
||||
// manager, then the entity manager that is established for
|
||||
// the transaction will happen to be synchronized. If someone
|
||||
// manager, then the EntityManager that is established for the
|
||||
// transaction will happen to be synchronized. If someone
|
||||
// else somehow wants to get their hands on an unsynchronized
|
||||
// transaction scoped entity manager, we have to detect this
|
||||
// transaction scoped EntityManager, we have to detect this
|
||||
// mixed synchronization case and throw an error.
|
||||
//
|
||||
// The mixed synchronization detection has to happen on each
|
||||
@@ -117,44 +144,73 @@ final class JpaTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
selectionQualifiers.remove(NonTransactional.Literal.INSTANCE);
|
||||
selectionQualifiers.add(CdiTransactionScoped.Literal.INSTANCE);
|
||||
selectionQualifiers.add(ContainerManaged.Literal.INSTANCE);
|
||||
if (this.isUnsynchronized) {
|
||||
final Annotation[] cdiTransactionScopedEntityManagerSelectionQualifiersArray;
|
||||
if (suppliedQualifiers.contains(Unsynchronized.Literal.INSTANCE)) {
|
||||
selectionQualifiers.remove(Synchronized.Literal.INSTANCE);
|
||||
selectionQualifiers.add(Unsynchronized.Literal.INSTANCE);
|
||||
this.cdiTransactionScopedEntityManagerSelectionQualifiersArray =
|
||||
cdiTransactionScopedEntityManagerSelectionQualifiersArray =
|
||||
selectionQualifiers.toArray(new Annotation[selectionQualifiers.size()]);
|
||||
selectionQualifiers.remove(Unsynchronized.Literal.INSTANCE);
|
||||
selectionQualifiers.add(Synchronized.Literal.INSTANCE);
|
||||
} else {
|
||||
selectionQualifiers.remove(Unsynchronized.Literal.INSTANCE);
|
||||
selectionQualifiers.add(Synchronized.Literal.INSTANCE);
|
||||
this.cdiTransactionScopedEntityManagerSelectionQualifiersArray =
|
||||
cdiTransactionScopedEntityManagerSelectionQualifiersArray =
|
||||
selectionQualifiers.toArray(new Annotation[selectionQualifiers.size()]);
|
||||
selectionQualifiers.remove(Synchronized.Literal.INSTANCE);
|
||||
selectionQualifiers.add(Unsynchronized.Literal.INSTANCE);
|
||||
}
|
||||
final Set<Bean<?>> beans =
|
||||
|
||||
Set<Bean<?>> beans =
|
||||
this.beanManager.getBeans(EntityManager.class,
|
||||
selectionQualifiers.toArray(new Annotation[selectionQualifiers.size()]));
|
||||
assert beans != null;
|
||||
assert beans.size() == 1 : "beans.size() != 1: " + beans;
|
||||
this.cdiTransactionScopedEntityManagerOppositeSynchronizationBean = this.beanManager.resolve(beans);
|
||||
assert this.cdiTransactionScopedEntityManagerOppositeSynchronizationBean != null;
|
||||
this.oppositeSynchronizationBean = Objects.requireNonNull(this.beanManager.resolve(beans));
|
||||
|
||||
// This is a proxy whose scope will be
|
||||
// javax.transaction.TransactionScoped, and therefore will be
|
||||
// lazily "inflated". In other words, the acquisition of this
|
||||
// reference here does not cause a contextual instance to be
|
||||
// created. Invoking any method on it will cause the
|
||||
// contextual instance to be created at that point, including
|
||||
// toString(), so debug with care.
|
||||
this.cdiTransactionScopedEntityManager =
|
||||
instance.select(CdiTransactionScopedEntityManager.class,
|
||||
cdiTransactionScopedEntityManagerSelectionQualifiersArray).get();
|
||||
assert this.cdiTransactionScopedEntityManager.getClass().isSynthetic();
|
||||
|
||||
selectionQualifiers.remove(CdiTransactionScoped.Literal.INSTANCE);
|
||||
selectionQualifiers.remove(ContainerManaged.Literal.INSTANCE);
|
||||
selectionQualifiers.remove(Synchronized.Literal.INSTANCE);
|
||||
selectionQualifiers.remove(Unsynchronized.Literal.INSTANCE);
|
||||
selectionQualifiers.add(NonTransactional.Literal.INSTANCE);
|
||||
this.nonTransactionalEntityManagerProvider =
|
||||
Objects.requireNonNull(instance.select(EntityManager.class,
|
||||
selectionQualifiers.toArray(new Annotation[selectionQualifiers.size()])));
|
||||
assert this.nonTransactionalEntityManagerProvider != null;
|
||||
|
||||
beans = this.beanManager.getBeans(NonTransactionalEntityManager.class,
|
||||
selectionQualifiers.toArray(new Annotation[selectionQualifiers.size()]));
|
||||
assert beans != null;
|
||||
assert beans.size() == 1 : "beans.size() != 1: " + beans;
|
||||
this.nonTransactionalEntityManagerBean = (Bean<NonTransactionalEntityManager>) this.beanManager.resolve(beans);
|
||||
assert this.nonTransactionalEntityManagerBean != null;
|
||||
beans = null;
|
||||
|
||||
final ReferenceCountedContext context = ReferenceCountedContext.getInstanceFrom(this.beanManager);
|
||||
assert context != null;
|
||||
assert context.isActive(); // it's always active
|
||||
this.startingReferenceCount = context.getReferenceCount(this.nonTransactionalEntityManagerBean);
|
||||
|
||||
this.nonTransactionalEntityManager =
|
||||
(NonTransactionalEntityManager) this.beanManager.getReference(this.nonTransactionalEntityManagerBean,
|
||||
NonTransactionalEntityManager.class,
|
||||
this.beanManager.createCreationalContext(null)); // fix
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Acquires and returns a delegate {@link EntityManager}, adhering
|
||||
* to the rules spelled out by the JPA specification around
|
||||
* transaction-scoped entity managers.
|
||||
* transaction-scoped {@link EntityManager}s.
|
||||
*
|
||||
* <p>This method never returns {@code null}.</p>
|
||||
*
|
||||
@@ -173,45 +229,575 @@ final class JpaTransactionScopedEntityManager extends DelegatingEntityManager {
|
||||
@Override
|
||||
protected EntityManager acquireDelegate() {
|
||||
final EntityManager returnValue;
|
||||
if (this.transactionSupport.inTransaction()) {
|
||||
// If we're in a transaction, then we're obligated to see
|
||||
// if there's a transaction-scoped entity manager already
|
||||
// affiliated with the current transaction. If there is,
|
||||
// and its synchronization type doesn't match ours, we're
|
||||
// supposed to throw an exception.
|
||||
final Context context =
|
||||
this.beanManager.getContext(this.cdiTransactionScopedEntityManagerOppositeSynchronizationBean.getScope());
|
||||
assert context != null;
|
||||
assert context.isActive();
|
||||
final Object contextualInstance =
|
||||
context.get(this.cdiTransactionScopedEntityManagerOppositeSynchronizationBean);
|
||||
if (contextualInstance != null) {
|
||||
// The Context in question reported that it has
|
||||
// already created (and is therefore storing) an
|
||||
// instance with the "wrong" synchronization type. We
|
||||
// must throw an exception.
|
||||
throw new PersistenceException(Messages.format("mixedSynchronizationTypes",
|
||||
this.cdiTransactionScopedEntityManagerOppositeSynchronizationBean,
|
||||
contextualInstance));
|
||||
final int status = this.transactionSupport.getStatus();
|
||||
switch (status) {
|
||||
case TransactionSupport.STATUS_ACTIVE:
|
||||
// If we are or were just in a transaction, then we're
|
||||
// obligated to see if there's a transaction-scoped
|
||||
// EntityManager already affiliated with the current
|
||||
// transaction. If there is, and its synchronization type
|
||||
// doesn't match ours, we're supposed to throw an
|
||||
// exception. Remember that the transaction-scoped
|
||||
// context can go inactive at any point due to a rollback
|
||||
// on another thread, and may already be inactive at
|
||||
// *this* point. This also means the status that dropped
|
||||
// us into this case statement may be stale.
|
||||
final Context transactionScopedContext = this.transactionSupport.getContext();
|
||||
if (transactionScopedContext == null || !transactionScopedContext.isActive()) {
|
||||
returnValue = this.nonTransactionalEntityManager;
|
||||
} else {
|
||||
EntityManager candidateReturnValue = this.cdiTransactionScopedEntityManager;
|
||||
try {
|
||||
final Object existingContextualInstance = transactionScopedContext.get(this.oppositeSynchronizationBean);
|
||||
if (existingContextualInstance != null) {
|
||||
// The Context in question reported that it
|
||||
// has already created (and is therefore
|
||||
// storing) an instance with the "wrong"
|
||||
// synchronization type. We must throw an
|
||||
// exception.
|
||||
throw new PersistenceException(Messages.format("mixedSynchronizationTypes",
|
||||
this.oppositeSynchronizationBean,
|
||||
existingContextualInstance));
|
||||
}
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
candidateReturnValue = this.nonTransactionalEntityManager;
|
||||
} finally {
|
||||
returnValue = candidateReturnValue;
|
||||
}
|
||||
}
|
||||
returnValue =
|
||||
this.instance.select(EntityManager.class,
|
||||
this.cdiTransactionScopedEntityManagerSelectionQualifiersArray).get();
|
||||
} else {
|
||||
returnValue = this.nonTransactionalEntityManagerProvider.get();
|
||||
break;
|
||||
default:
|
||||
returnValue = this.nonTransactionalEntityManager;
|
||||
break;
|
||||
}
|
||||
assert returnValue != null;
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an {@link IllegalStateException} when invoked.
|
||||
*
|
||||
* @return nothing
|
||||
*
|
||||
* @exception IllegalStateException when invoked
|
||||
*
|
||||
* @see EntityManager#getTransaction()
|
||||
*/
|
||||
@Override
|
||||
public EntityTransaction getTransaction() {
|
||||
throw new IllegalStateException(Messages.format("jpaTransactionScopedEntityManagerGetTransaction"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an {@link IllegalStateException} when invoked.
|
||||
*
|
||||
* @exception IllegalStateException when invoked
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
// Revisit: Wildfly allows end users to close UNSYNCHRONIZED
|
||||
// Wildfly allows end users to close UNSYNCHRONIZED
|
||||
// container-managed EntityManagers:
|
||||
// https://github.com/wildfly/wildfly/blob/7f80f0150297bbc418a38e7e23da7cf0431f7c28/jpa/subsystem/src/main/java/org/jboss/as/jpa/container/UnsynchronizedEntityManagerWrapper.java#L75-L78
|
||||
// I don't know why. Glassfish does not:
|
||||
// I don't know why. Glassfish does not; it's the reference
|
||||
// application; we follow suit:
|
||||
// https://github.com/javaee/glassfish/blob/f9e1f6361dcc7998cacccb574feef5b70bf84e23/appserver/common/container-common/src/main/java/com/sun/enterprise/container/common/impl/EntityManagerWrapper.java#L752-L761
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException(Messages.format("jpaTransactionScopedEntityManagerClose"));
|
||||
}
|
||||
|
||||
void dispose(final Instance<Object> instance) {
|
||||
final ReferenceCountedContext context = ReferenceCountedContext.getInstanceFrom(this.beanManager);
|
||||
assert context != null;
|
||||
assert context.isActive();
|
||||
final int finalReferenceCount = context.getReferenceCount(this.nonTransactionalEntityManagerBean);
|
||||
context.decrementReferenceCount(this.nonTransactionalEntityManagerBean,
|
||||
finalReferenceCount - this.startingReferenceCount);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Overrides that translate ContextNotActiveException.
|
||||
*
|
||||
* Because the underlying delegate is often a
|
||||
* CdiTransactionScopedEntityManager in
|
||||
* javax.transaction.TransactionScoped scope, and because that
|
||||
* scope's lifetime is equal to the current transaction's
|
||||
* lifetime, and because a transaction can roll back at any point
|
||||
* due to timeout on a background thread, it is *always* possible
|
||||
* that the delegate returned by the delegate() method (and
|
||||
* therefore indirectly by the acquireDelegate() method above) is
|
||||
* essentially invalid: you can take delivery of it but as soon as
|
||||
* you call a method on it it may turn out that the transaction
|
||||
* has rolled back. Your method invocation will thus result in a
|
||||
* ContextNotActiveException.
|
||||
*
|
||||
* In this case we must revert to a non-transactional delegate,
|
||||
* i.e. one whose underlying persistence context is always empty.
|
||||
* The overrides that follow perform this translation.
|
||||
*/
|
||||
|
||||
|
||||
@Override
|
||||
public void persist(final Object entity) {
|
||||
try {
|
||||
super.persist(entity);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.persist(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T merge(final T entity) {
|
||||
try {
|
||||
return super.merge(entity);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.merge(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final Object entity) {
|
||||
try {
|
||||
super.remove(entity);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey) {
|
||||
try {
|
||||
return super.find(entityClass, primaryKey);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.find(entityClass, primaryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey,
|
||||
final Map<String, Object> properties) {
|
||||
try {
|
||||
return super.find(entityClass, primaryKey, properties);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.find(entityClass, primaryKey, properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey,
|
||||
final LockModeType lockMode) {
|
||||
try {
|
||||
return super.find(entityClass, primaryKey, lockMode);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.find(entityClass, primaryKey, lockMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey,
|
||||
final LockModeType lockMode,
|
||||
final Map<String, Object> properties) {
|
||||
try {
|
||||
return super.find(entityClass, primaryKey, lockMode, properties);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.find(entityClass, primaryKey, lockMode, properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getReference(final Class<T> entityClass,
|
||||
final Object primaryKey) {
|
||||
try {
|
||||
return super.getReference(entityClass, primaryKey);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getReference(entityClass, primaryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
try {
|
||||
super.flush();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFlushMode(final FlushModeType flushMode) {
|
||||
try {
|
||||
super.setFlushMode(flushMode);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.setFlushMode(flushMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlushModeType getFlushMode() {
|
||||
try {
|
||||
return super.getFlushMode();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getFlushMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(final Object entity,
|
||||
final LockModeType lockMode) {
|
||||
try {
|
||||
super.lock(entity, lockMode);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.lock(entity, lockMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(final Object entity,
|
||||
final LockModeType lockMode,
|
||||
final Map<String, Object> properties) {
|
||||
try {
|
||||
super.lock(entity, lockMode, properties);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.lock(entity, lockMode, properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(final Object entity) {
|
||||
try {
|
||||
super.refresh(entity);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.refresh(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(final Object entity,
|
||||
final Map<String, Object> properties) {
|
||||
try {
|
||||
super.refresh(entity, properties);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.refresh(entity, properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(final Object entity,
|
||||
final LockModeType lockMode) {
|
||||
try {
|
||||
super.refresh(entity, lockMode);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.refresh(entity, lockMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(final Object entity,
|
||||
final LockModeType lockMode,
|
||||
final Map<String, Object> properties) {
|
||||
try {
|
||||
super.refresh(entity, lockMode, properties);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.refresh(entity, lockMode, properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
try {
|
||||
super.clear();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach(final Object entity) {
|
||||
try {
|
||||
super.detach(entity);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.detach(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final Object entity) {
|
||||
try {
|
||||
return super.contains(entity);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.contains(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LockModeType getLockMode(final Object entity) {
|
||||
try {
|
||||
return super.getLockMode(entity);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getLockMode(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
try {
|
||||
return super.getProperties();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(final String propertyName,
|
||||
final Object propertyValue) {
|
||||
try {
|
||||
super.setProperty(propertyName, propertyValue);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.setProperty(propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createQuery(final String jpql) {
|
||||
try {
|
||||
return super.createQuery(jpql);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createQuery(jpql);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> createQuery(final CriteriaQuery<T> criteriaQuery) {
|
||||
try {
|
||||
return super.createQuery(criteriaQuery);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createQuery(criteriaQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Query createQuery(final CriteriaUpdate criteriaUpdate) {
|
||||
try {
|
||||
return super.createQuery(criteriaUpdate);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createQuery(criteriaUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Query createQuery(final CriteriaDelete criteriaDelete) {
|
||||
try {
|
||||
return super.createQuery(criteriaDelete);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createQuery(criteriaDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> createQuery(final String jpql, final Class<T> resultClass) {
|
||||
try {
|
||||
return super.createQuery(jpql, resultClass);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createQuery(jpql, resultClass);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createNamedQuery(final String sql) {
|
||||
try {
|
||||
return super.createNamedQuery(sql);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createNamedQuery(sql);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> createNamedQuery(final String sql, final Class<T> resultClass) {
|
||||
try {
|
||||
return super.createNamedQuery(sql, resultClass);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createNamedQuery(sql, resultClass);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createNativeQuery(final String sql) {
|
||||
try {
|
||||
return super.createNativeQuery(sql);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createNativeQuery(sql);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Query createNativeQuery(final String sql, final Class resultClass) {
|
||||
try {
|
||||
return super.createNativeQuery(sql, resultClass);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createNativeQuery(sql, resultClass);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createNativeQuery(final String sql, final String resultSetMapping) {
|
||||
try {
|
||||
return super.createNativeQuery(sql, resultSetMapping);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createNativeQuery(sql, resultSetMapping);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProcedureQuery createNamedStoredProcedureQuery(final String procedureName) {
|
||||
try {
|
||||
return super.createNamedStoredProcedureQuery(procedureName);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createNamedStoredProcedureQuery(procedureName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProcedureQuery createStoredProcedureQuery(final String procedureName) {
|
||||
try {
|
||||
return super.createStoredProcedureQuery(procedureName);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createStoredProcedureQuery(procedureName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public StoredProcedureQuery createStoredProcedureQuery(final String procedureName,
|
||||
final Class... resultClasses) {
|
||||
try {
|
||||
return super.createStoredProcedureQuery(procedureName, resultClasses);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createStoredProcedureQuery(procedureName, resultClasses);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProcedureQuery createStoredProcedureQuery(final String procedureName,
|
||||
final String... resultSetMappings) {
|
||||
try {
|
||||
return super.createStoredProcedureQuery(procedureName, resultSetMappings);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createStoredProcedureQuery(procedureName, resultSetMappings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinTransaction() {
|
||||
try {
|
||||
super.joinTransaction();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
this.nonTransactionalEntityManager.joinTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJoinedToTransaction() {
|
||||
try {
|
||||
return super.isJoinedToTransaction();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.isJoinedToTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(final Class<T> c) {
|
||||
try {
|
||||
return super.unwrap(c);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.unwrap(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDelegate() {
|
||||
try {
|
||||
return super.getDelegate();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getDelegate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
try {
|
||||
return super.isOpen();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.isOpen();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManagerFactory getEntityManagerFactory() {
|
||||
try {
|
||||
return super.getEntityManagerFactory();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getEntityManagerFactory();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CriteriaBuilder getCriteriaBuilder() {
|
||||
try {
|
||||
return super.getCriteriaBuilder();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getCriteriaBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metamodel getMetamodel() {
|
||||
try {
|
||||
return super.getMetamodel();
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getMetamodel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> EntityGraph<T> createEntityGraph(final Class<T> rootType) {
|
||||
try {
|
||||
return super.createEntityGraph(rootType);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createEntityGraph(rootType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityGraph<?> createEntityGraph(final String graphName) {
|
||||
try {
|
||||
return super.createEntityGraph(graphName);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.createEntityGraph(graphName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityGraph<?> getEntityGraph(final String graphName) {
|
||||
try {
|
||||
return super.getEntityGraph(graphName);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getEntityGraph(graphName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<EntityGraph<? super T>> getEntityGraphs(final Class<T> entityClass) {
|
||||
try {
|
||||
return super.getEntityGraphs(entityClass);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
return this.nonTransactionalEntityManager.getEntityGraphs(entityClass);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import javax.enterprise.context.ContextNotActiveException;
|
||||
import javax.enterprise.context.spi.Context;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.transaction.SystemException;
|
||||
import javax.transaction.TransactionManager;
|
||||
import javax.transaction.TransactionScoped;
|
||||
|
||||
/**
|
||||
@@ -31,9 +32,18 @@ import javax.transaction.TransactionScoped;
|
||||
*
|
||||
* <p>See the exclusion stanzas in {@code META-INF/beans.xml} for more
|
||||
* details.</p>
|
||||
*
|
||||
* <h2>Thread Safety</h2>
|
||||
*
|
||||
* <p>Instances of this class are not safe for concurrent use by
|
||||
* multiple threads.</p>
|
||||
*
|
||||
* @see TransactionSupport
|
||||
*
|
||||
* @see NoTransactionSupport
|
||||
*/
|
||||
@ApplicationScoped
|
||||
class JtaTransactionSupport implements TransactionSupport {
|
||||
final class JtaTransactionSupport implements TransactionSupport {
|
||||
|
||||
|
||||
/*
|
||||
@@ -41,17 +51,11 @@ class JtaTransactionSupport implements TransactionSupport {
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A {@link Provider} of {@link BeanManager} instances.
|
||||
*
|
||||
* <p>This field will normally not be {@code null} but if the
|
||||
* {@link #JtaSupport()} constructor is used, then it will be
|
||||
* {@code null} (and the {@link JtaTransactionSupport} instance so
|
||||
* constructed will be non-functional.</p>
|
||||
*
|
||||
* @see #JtaSupport()
|
||||
*/
|
||||
private final Provider<BeanManager> beanManagerProvider;
|
||||
private final BeanManager beanManager;
|
||||
|
||||
private Context transactionScopedContext;
|
||||
|
||||
private final TransactionManager transactionManager;
|
||||
|
||||
|
||||
/*
|
||||
@@ -59,42 +63,24 @@ class JtaTransactionSupport implements TransactionSupport {
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new, <strong>nonfunctional</strong> {@link
|
||||
* JtaTransactionSupport}.
|
||||
*
|
||||
* <p>This constructor exists only to conform with section 3.15 of
|
||||
* the CDI specification.</p>
|
||||
*
|
||||
* @deprecated This constructor exists only to conform with
|
||||
* section 3.15 of the CDI specification; please use the {@link
|
||||
* #JtaTransactionSupport(Provider)} constructor instead.
|
||||
*
|
||||
* @see
|
||||
* #JtaTransactionSupport(Provider)
|
||||
*
|
||||
* @see <a
|
||||
* href="http://docs.jboss.org/cdi/spec/1.2/cdi-spec.html#unproxyable">Section
|
||||
* 3.15 of the CDI 2.0 specification</a>
|
||||
*/
|
||||
@Deprecated
|
||||
JtaTransactionSupport() {
|
||||
super();
|
||||
this.beanManagerProvider = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link JtaTransactionSupport}.
|
||||
*
|
||||
* @param beanManagerProvider a {@link Provider} of {@link
|
||||
* BeanManager} instances; must not be {@code null}
|
||||
* @param beanManager a {@link BeanManager}; must not be {@code
|
||||
* null}
|
||||
*
|
||||
* @exception NullPointerException
|
||||
* @param transactionManager a {@link TransactionManager}; must
|
||||
* not be {@code null}
|
||||
*
|
||||
* @exception NullPointerException if either {@code beanManager}
|
||||
* or {@code transactionManager} is {@code null}
|
||||
*/
|
||||
@Inject
|
||||
JtaTransactionSupport(final Provider<BeanManager> beanManagerProvider) {
|
||||
private JtaTransactionSupport(final BeanManager beanManager,
|
||||
final TransactionManager transactionManager) {
|
||||
super();
|
||||
this.beanManagerProvider = Objects.requireNonNull(beanManagerProvider);
|
||||
this.beanManager = Objects.requireNonNull(beanManager);
|
||||
this.transactionManager = Objects.requireNonNull(transactionManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,13 +95,13 @@ class JtaTransactionSupport implements TransactionSupport {
|
||||
* @return {@code true} when invoked
|
||||
*/
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Context} that supports JTA transactions, if
|
||||
* there is one and it is active.
|
||||
* there is one and it is active at the moment of invocation.
|
||||
*
|
||||
* <p>This method may return {@code null}.</p>
|
||||
*
|
||||
@@ -126,32 +112,39 @@ class JtaTransactionSupport implements TransactionSupport {
|
||||
@Override
|
||||
public Context getContext() {
|
||||
final Context returnValue;
|
||||
final BeanManager beanManager = this.beanManagerProvider.get();
|
||||
if (beanManager == null) {
|
||||
returnValue = null;
|
||||
} else {
|
||||
Context temp = null;
|
||||
if (this.transactionScopedContext == null) {
|
||||
try {
|
||||
temp = beanManager.getContext(TransactionScoped.class);
|
||||
this.transactionScopedContext = this.beanManager.getContext(TransactionScoped.class);
|
||||
} catch (final ContextNotActiveException contextNotActiveException) {
|
||||
temp = null;
|
||||
this.transactionScopedContext = null;
|
||||
} finally {
|
||||
returnValue = temp;
|
||||
returnValue = this.transactionScopedContext;
|
||||
}
|
||||
} else if (this.transactionScopedContext.isActive()) {
|
||||
returnValue = this.transactionScopedContext;
|
||||
} else {
|
||||
returnValue = null;
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the return value of {@link
|
||||
* #getContext()} is non-{@code null}.
|
||||
* Returns the {@linkplain TransactionManager#getStatus() current
|
||||
* status} of the current transaction, if available.
|
||||
*
|
||||
* @return {@code true} if the return value of {@link
|
||||
* #getContext()} is non-{@code null}; {@code false} otherwise
|
||||
* @return the {@linkplain TransactionManager#getStatus() current
|
||||
* status} of the current transaction, if available
|
||||
*
|
||||
* @exception IllegalStateException if there was a problem
|
||||
* acquiring status
|
||||
*/
|
||||
@Override
|
||||
public boolean inTransaction() {
|
||||
return this.getContext() != null;
|
||||
public int getStatus() {
|
||||
try {
|
||||
return this.transactionManager.getStatus();
|
||||
} catch (final SystemException systemException) {
|
||||
throw new IllegalStateException(systemException.getMessage(), systemException);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ import javax.enterprise.context.spi.Context;
|
||||
*
|
||||
* <p>See the exclusion stanzas in {@code META-INF/beans.xml} for more
|
||||
* details.</p>
|
||||
*
|
||||
* @see TransactionSupport
|
||||
*
|
||||
* @see JtaTransactionSupport
|
||||
*/
|
||||
@ApplicationScoped
|
||||
final class NoTransactionSupport implements TransactionSupport {
|
||||
@@ -60,7 +64,7 @@ final class NoTransactionSupport implements TransactionSupport {
|
||||
* @return {@code false} when invoked
|
||||
*/
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -75,13 +79,15 @@ final class NoTransactionSupport implements TransactionSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code false} when invoked.
|
||||
* Returns {@link TransactionSupport#STATUS_NO_TRANSACTION} when
|
||||
* invoked.
|
||||
*
|
||||
* @return {@code false} when invoked
|
||||
* @return {@link TransactionSupport#STATUS_NO_TRANSACTION} when
|
||||
* invoked
|
||||
*/
|
||||
@Override
|
||||
public boolean inTransaction() {
|
||||
return false;
|
||||
public int getStatus() {
|
||||
return STATUS_NO_TRANSACTION;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,22 +20,49 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.inject.Instance;
|
||||
import javax.enterprise.inject.Vetoed;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.StoredProcedureQuery;
|
||||
import javax.persistence.TransactionRequiredException;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
|
||||
/**
|
||||
* A {@link DelegatingEntityManager} that is definitionally not backed
|
||||
* by an extended persistence context and that assumes there is no JTA
|
||||
* transaction in effect.
|
||||
*
|
||||
* <p>This class obeys the requirements imposed upon it by sections
|
||||
* 3.3.2, 3.3.3 and 7.6.2 of the JPA 2.2 specification.</p>
|
||||
*
|
||||
* <p>Instances of this class are never directly seen by the end user.
|
||||
* Specifically, instances of this class are themselves returned by a
|
||||
* {@link DelegatingEntityManager} implementation's {@link
|
||||
* #acquireDelegate()} method.</p>
|
||||
* #acquireDelegate()} method and only under appropriate
|
||||
* circumstances.</p>
|
||||
*
|
||||
* <p>This class is added as a synthetic bean by the {@link
|
||||
* JpaExtension} class.</p>
|
||||
*
|
||||
* <h2>Implementation Notes</h2>
|
||||
*
|
||||
* <p>Because instances of this class are typically placed in <a
|
||||
* href="https://jakarta.ee/specifications/cdi/2.0/cdi-spec-2.0.html#normal_scope"
|
||||
* target="_parent"><em>normal scopes</em></a>, this class is not
|
||||
* declared {@code final}, but must be treated as if it were.</p>
|
||||
*
|
||||
* <h2>Thread Safety</h2>
|
||||
*
|
||||
* <p>As with all {@link EntityManager} implementations, instances of
|
||||
* this class are not safe for concurrent use by multiple threads.</p>
|
||||
*
|
||||
* @see JpaExtension
|
||||
*/
|
||||
final class NonTransactionalEntityManager extends DelegatingEntityManager {
|
||||
@Vetoed
|
||||
class NonTransactionalEntityManager extends DelegatingEntityManager {
|
||||
|
||||
|
||||
/*
|
||||
@@ -67,19 +94,134 @@ final class NonTransactionalEntityManager extends DelegatingEntityManager {
|
||||
|
||||
|
||||
/**
|
||||
* Throws a {@link PersistenceException} when invoked, because it
|
||||
* will never be invoked in the normal course of events.
|
||||
* Throws a {@link PersistenceException} when invoked, because
|
||||
* this method will never be invoked in the normal course of
|
||||
* events.
|
||||
*
|
||||
* @return a non-{@code null} {@link EntityManager}, but this will
|
||||
* never happen
|
||||
*
|
||||
* @exception PersistenceException when invoked
|
||||
*
|
||||
* @see #delegate()
|
||||
*/
|
||||
@Override
|
||||
protected EntityManager acquireDelegate() {
|
||||
throw new PersistenceException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> createNamedQuery(final String name,
|
||||
final Class<T> resultClass) {
|
||||
return new ClearingTypedQuery<>(this, super.createNamedQuery(name, resultClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> createQuery(final CriteriaQuery<T> criteriaQuery) {
|
||||
return new ClearingTypedQuery<>(this, super.createQuery(criteriaQuery));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> createQuery(final String jpql,
|
||||
final Class<T> resultClass) {
|
||||
return new ClearingTypedQuery<>(this, super.createQuery(jpql, resultClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey,
|
||||
final Map<String, Object> properties) {
|
||||
final T returnValue = super.find(entityClass, primaryKey, properties);
|
||||
this.clear();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey) {
|
||||
final T returnValue = super.find(entityClass, primaryKey);
|
||||
this.clear();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createNamedQuery(final String name) {
|
||||
return new ClearingQuery(this, super.createNamedQuery(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Query createNativeQuery(final String sql,
|
||||
final Class resultClass) {
|
||||
return new ClearingQuery(this, super.createNativeQuery(sql, resultClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createNativeQuery(final String sql,
|
||||
final String resultSetMapping) {
|
||||
return new ClearingQuery(this, super.createNativeQuery(sql, resultSetMapping));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createNativeQuery(final String sql) {
|
||||
return new ClearingQuery(this, super.createNativeQuery(sql));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createQuery(final String jpql) {
|
||||
return new ClearingQuery(this, super.createQuery(jpql));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getReference(final Class<T> entityClass,
|
||||
final Object primaryKey) {
|
||||
final T returnValue = super.getReference(entityClass, primaryKey);
|
||||
this.clear();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProcedureQuery createNamedStoredProcedureQuery(final String name) {
|
||||
return new ClearingStoredProcedureQuery(this, super.createNamedStoredProcedureQuery(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProcedureQuery createStoredProcedureQuery(final String procedureName) {
|
||||
return new ClearingStoredProcedureQuery(this, super.createStoredProcedureQuery(procedureName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public StoredProcedureQuery createStoredProcedureQuery(final String procedureName,
|
||||
final Class... resultClasses) {
|
||||
return new ClearingStoredProcedureQuery(this, super.createStoredProcedureQuery(procedureName, resultClasses));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProcedureQuery createStoredProcedureQuery(final String procedureName,
|
||||
final String... resultSetMappings) {
|
||||
return new ClearingStoredProcedureQuery(this, super.createStoredProcedureQuery(procedureName, resultSetMappings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code false} when invoked.
|
||||
*
|
||||
* @return {@code false} in all cases
|
||||
*/
|
||||
public boolean isJoinedToTransaction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link TransactionRequiredException} when invoked.
|
||||
*
|
||||
* @exception TransactionRequiredException when invoked
|
||||
*/
|
||||
@Override
|
||||
public void joinTransaction() {
|
||||
throw new TransactionRequiredException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link TransactionRequiredException} when invoked.
|
||||
*
|
||||
@@ -176,6 +318,78 @@ final class NonTransactionalEntityManager extends DelegatingEntityManager {
|
||||
throw new TransactionRequiredException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey,
|
||||
final LockModeType lockMode) {
|
||||
if (lockMode != null && !lockMode.equals(LockModeType.NONE)) {
|
||||
throw new TransactionRequiredException();
|
||||
}
|
||||
final T returnValue = super.find(entityClass, primaryKey, lockMode);
|
||||
this.clear();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(final Class<T> entityClass,
|
||||
final Object primaryKey,
|
||||
final LockModeType lockMode,
|
||||
final Map<String, Object> properties) {
|
||||
if (lockMode != null && !lockMode.equals(LockModeType.NONE)) {
|
||||
throw new TransactionRequiredException();
|
||||
}
|
||||
final T returnValue = super.find(entityClass, primaryKey, lockMode, properties);
|
||||
this.clear();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link TransactionRequiredException} when invoked.
|
||||
*
|
||||
* @param entity ignored
|
||||
*
|
||||
* @param lockMode ignored
|
||||
*
|
||||
* @exception TransactionRequiredException when invoked
|
||||
*/
|
||||
@Override
|
||||
public void lock(final Object entity,
|
||||
final LockModeType lockMode) {
|
||||
throw new TransactionRequiredException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link TransactionRequiredException} when invoked.
|
||||
*
|
||||
* @param entity ignored
|
||||
*
|
||||
* @param lockMode ignored
|
||||
*
|
||||
* @param properties ignored
|
||||
*
|
||||
* @exception TransactionRequiredException when invoked
|
||||
*/
|
||||
@Override
|
||||
public void lock(final Object entity,
|
||||
final LockModeType lockMode,
|
||||
final Map<String, Object> properties) {
|
||||
throw new TransactionRequiredException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link TransactionRequiredException} when invoked.
|
||||
*
|
||||
* @param entity ignored
|
||||
*
|
||||
* @return nothing
|
||||
*
|
||||
* @exception TransactionRequiredException when invoked
|
||||
*/
|
||||
@Override
|
||||
public LockModeType getLockMode(final Object entity) {
|
||||
throw new TransactionRequiredException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link TransactionRequiredException} when invoked.
|
||||
*
|
||||
|
||||
@@ -27,32 +27,113 @@ import javax.enterprise.context.spi.Context;
|
||||
*/
|
||||
interface TransactionSupport {
|
||||
|
||||
|
||||
/*
|
||||
* Static fields.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_ACTIVE}.
|
||||
*/
|
||||
int STATUS_ACTIVE = 0;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_MARKED_ROLLBACK}.
|
||||
*/
|
||||
int STATUS_MARKED_ROLLBACK = 1;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_PREPARED}.
|
||||
*/
|
||||
int STATUS_PREPARED = 2;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_COMMITTED}.
|
||||
*/
|
||||
int STATUS_COMMITTED = 3;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_ROLLEDBACK}.
|
||||
*/
|
||||
int STATUS_ROLLEDBACK = 4;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_UNKNOWN}.
|
||||
*/
|
||||
int STATUS_UNKNOWN = 5;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_NO_TRANSACTION}.
|
||||
*/
|
||||
int STATUS_NO_TRANSACTION = 6;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_PREPARING}.
|
||||
*/
|
||||
int STATUS_PREPARING = 7;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_COMMITTING}.
|
||||
*/
|
||||
int STATUS_COMMITTING = 8;
|
||||
|
||||
/**
|
||||
* Equal in value and meaning to {@link
|
||||
* javax.transaction.Status#STATUS_ROLLING_BACK}.
|
||||
*/
|
||||
int STATUS_ROLLING_BACK = 9;
|
||||
|
||||
|
||||
/*
|
||||
* Method signatures.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@code true} if JTA facilities are available.
|
||||
*
|
||||
* @return {@code true} if JTA facilities are available; {@code
|
||||
* false} otherwise
|
||||
*/
|
||||
boolean isActive();
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Returns the {@linkplain Context#isActive() active} {@link
|
||||
* Context}, if any, that supports JTA facilities, or {@code null}
|
||||
* Context}, if any, that supports JTA facilities at the moment of
|
||||
* invocation, or {@code null}.
|
||||
*
|
||||
* <p>Implementations of this method may return {@code null}.</p>
|
||||
* <p>Implementations of this method may, and often do, return
|
||||
* {@code null}.</p>
|
||||
*
|
||||
* <p>The {@link Context} returned by implementations of this
|
||||
* method may become active or inactive at any moment.</p>
|
||||
*
|
||||
* @return the {@link Context}, if any, that supports JTA
|
||||
* facilities, or {@code null}
|
||||
*
|
||||
* @see Context#isActive()
|
||||
*/
|
||||
Context getContext();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a JTA transaction is currently in
|
||||
* effect.
|
||||
* Returns a constant indicating the current transaction status.
|
||||
*
|
||||
* @return {@code true} if a JTA transaction is currently in
|
||||
* effect; {@code false} otherwise
|
||||
* <p>Implementations of this method must return {@link
|
||||
* #STATUS_NO_TRANSACTION} ({@code 6}) if JTA is not supported.</p>
|
||||
*
|
||||
* @return a JTA {@link javax.transaction.Status} constant
|
||||
* indicating the current transaction status
|
||||
*/
|
||||
boolean inTransaction();
|
||||
int getStatus();
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
ambiguousPersistenceUnitInfo = \
|
||||
There was at least one @PersistenceContext- or @PersistenceUnit-annotated injection point that did not specify a value for \
|
||||
its unitName element, and the following persistence units were found: {0}
|
||||
closedStatus = \
|
||||
closed (status: {0})
|
||||
closedNotActive = \
|
||||
closedNotActive ({0})
|
||||
jpaTransactionScopedEntityManagerClose = \
|
||||
You cannot close a container-managed EntityManager.
|
||||
jpaTransactionScopedEntityManagerGetTransaction = \
|
||||
You cannot call getTransaction() on a container-managed EntityManager.
|
||||
mixedSynchronizationTypes = \
|
||||
An instance of CdiTransactionScopedEntityManager is already affiliated with the current transaction, but has a different \
|
||||
synchronization type than that desired. Instigating bean: {0}; existing contextual instance: {1}
|
||||
@@ -33,3 +41,7 @@ preexistingExtendedEntityManager = \
|
||||
resourceLocalPersistenceUnitDisallowed = \
|
||||
The persistence unit {0} has a PersistenceUnitTransactionType of PersistenceUnitTransactionType.RESOURCE_LOCAL, but \
|
||||
container-managed persistence units must have a PersistenceUnitTransactionType of PersistenceUnitTransactionType.JTA.
|
||||
unexpectedCurrentStatus = \
|
||||
Unexpected status: {0}
|
||||
unexpectedPriorStatus = \
|
||||
Unexpected status: {0}
|
||||
|
||||
@@ -52,7 +52,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
@DataSourceDefinition(
|
||||
name = "test",
|
||||
className = "org.h2.jdbcx.JdbcDataSource",
|
||||
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
|
||||
url = "jdbc:h2:mem:test",
|
||||
serverName = "",
|
||||
properties = {
|
||||
"user=sa"
|
||||
|
||||
@@ -47,6 +47,7 @@ public class Author implements Serializable {
|
||||
@Column(name = "NAME",
|
||||
insertable = true,
|
||||
nullable = false,
|
||||
unique = true,
|
||||
updatable = true)
|
||||
private String name;
|
||||
|
||||
@@ -71,5 +72,31 @@ public class Author implements Serializable {
|
||||
public void setName(final String name) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final Object name = this.getName();
|
||||
return name == null ? 0 : name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (other == this) {
|
||||
return true;
|
||||
} else if (other instanceof Author) {
|
||||
final Author her = (Author) other;
|
||||
final Object name = this.getName();
|
||||
if (name == null) {
|
||||
if (her.getName() != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!name.equals(her.getName())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
@DataSourceDefinition(
|
||||
name = "chirp",
|
||||
className = "org.h2.jdbcx.JdbcDataSource",
|
||||
url = "jdbc:h2:mem:chirp;INIT=RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
url = "jdbc:h2:mem:chirp;INIT=SET TRACE_LEVEL_FILE=4\\;RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
serverName = "",
|
||||
properties = {
|
||||
"user=sa"
|
||||
|
||||
@@ -50,7 +50,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
@DataSourceDefinition(
|
||||
name = "chirp",
|
||||
className = "org.h2.jdbcx.JdbcDataSource",
|
||||
url = "jdbc:h2:mem:chirp;INIT=RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
url = "jdbc:h2:mem:chirp;INIT=SET TRACE_LEVEL_FILE=4\\;RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
serverName = "",
|
||||
properties = {
|
||||
"user=sa"
|
||||
|
||||
@@ -15,20 +15,31 @@
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa.chirp;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.sql.DataSourceDefinition;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.context.BeforeDestroyed;
|
||||
import javax.enterprise.context.ContextNotActiveException;
|
||||
import javax.enterprise.context.spi.Context;
|
||||
import javax.enterprise.event.Observes;
|
||||
import javax.enterprise.inject.se.SeContainer;
|
||||
import javax.enterprise.inject.se.SeContainerInitializer;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.persistence.EntityExistsException;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.PersistenceContextType;
|
||||
import javax.persistence.SynchronizationType;
|
||||
import javax.persistence.TransactionRequiredException;
|
||||
import javax.sql.DataSource;
|
||||
import javax.transaction.HeuristicMixedException;
|
||||
import javax.transaction.HeuristicRollbackException;
|
||||
import javax.transaction.NotSupportedException;
|
||||
@@ -36,13 +47,16 @@ import javax.transaction.RollbackException;
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.SystemException;
|
||||
import javax.transaction.TransactionManager;
|
||||
import javax.transaction.TransactionScoped;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@@ -50,7 +64,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
@DataSourceDefinition(
|
||||
name = "chirp",
|
||||
className = "org.h2.jdbcx.JdbcDataSource",
|
||||
url = "jdbc:h2:mem:chirp;INIT=RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
url = "jdbc:h2:mem:chirp;INIT=SET TRACE_LEVEL_FILE=4\\;RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
serverName = "",
|
||||
properties = {
|
||||
"user=sa"
|
||||
@@ -66,6 +80,10 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
|
||||
@Inject
|
||||
private TransactionManager transactionManager;
|
||||
|
||||
@Inject
|
||||
@Named("chirp")
|
||||
private DataSource dataSource;
|
||||
|
||||
@PersistenceContext(
|
||||
type = PersistenceContextType.TRANSACTION,
|
||||
@@ -126,6 +144,10 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
return this.transactionManager;
|
||||
}
|
||||
|
||||
DataSource getDataSource() {
|
||||
return this.dataSource;
|
||||
}
|
||||
|
||||
private void onShutdown(@Observes @BeforeDestroyed(ApplicationScoped.class) final Object event,
|
||||
final TransactionManager tm) throws SystemException {
|
||||
// If an assertion fails, or some other error happens in the
|
||||
@@ -155,9 +177,14 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
HeuristicRollbackException,
|
||||
NotSupportedException,
|
||||
RollbackException,
|
||||
SQLException,
|
||||
SystemException
|
||||
{
|
||||
|
||||
// Get a BeanManager for later use.
|
||||
final BeanManager beanManager = this.cdiContainer.getBeanManager();
|
||||
assertNotNull(beanManager);
|
||||
|
||||
// Get a CDI contextual reference to this test instance. It
|
||||
// is important to use "self" in this test instead of "this".
|
||||
final TestJpaTransactionScopedSynchronizedEntityManager self =
|
||||
@@ -170,6 +197,10 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
assertNotNull(em);
|
||||
assertTrue(em.isOpen());
|
||||
|
||||
// Get a DataSource for JPA-independent testing and assertions.
|
||||
final DataSource dataSource = self.getDataSource();
|
||||
assertNotNull(dataSource);
|
||||
|
||||
// We haven't started any kind of transaction yet and we
|
||||
// aren't testing anything using
|
||||
// the @javax.transaction.Transactional annotation so there is
|
||||
@@ -180,7 +211,7 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
// Create a JPA entity and try to insert it. This should fail
|
||||
// because according to JPA a TransactionRequiredException
|
||||
// will be thrown.
|
||||
final Author author = new Author("Abraham Lincoln");
|
||||
Author author = new Author("Abraham Lincoln");
|
||||
try {
|
||||
em.persist(author);
|
||||
fail("A TransactionRequiredException should have been thrown");
|
||||
@@ -192,8 +223,16 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
// scenes and use it to start a Transaction.
|
||||
final TransactionManager tm = self.getTransactionManager();
|
||||
assertNotNull(tm);
|
||||
tm.setTransactionTimeout(60 * 20); // TODO: set to 20 minutes for debugging purposes only
|
||||
tm.begin();
|
||||
|
||||
// Grab the TransactionScoped context while the transaction is
|
||||
// active. We want to make sure it's active at various
|
||||
// points.
|
||||
final Context transactionScopedContext = beanManager.getContext(TransactionScoped.class);
|
||||
assertNotNull(transactionScopedContext);
|
||||
assertTrue(transactionScopedContext.isActive());
|
||||
|
||||
// Now magically our EntityManager should be joined to it.
|
||||
assertTrue(em.isJoinedToTransaction());
|
||||
|
||||
@@ -201,16 +240,26 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
// is no longer joined to it.
|
||||
tm.rollback();
|
||||
assertFalse(em.isJoinedToTransaction());
|
||||
assertFalse(transactionScopedContext.isActive());
|
||||
|
||||
// Start another transaction and persist our Author.
|
||||
// Start another transaction.
|
||||
tm.begin();
|
||||
assertTrue(transactionScopedContext.isActive());
|
||||
|
||||
// Persist our Author.
|
||||
assertNull(author.getId());
|
||||
em.persist(author);
|
||||
assertTrue(em.contains(author));
|
||||
tm.commit();
|
||||
|
||||
// After the transaction commits, a flush should happen, and
|
||||
// the author is managed, so we should see his ID.
|
||||
assertEquals(Integer.valueOf(1), author.getId());
|
||||
|
||||
// The transaction is over, so our EntityManager is not joined
|
||||
// to one anymore.
|
||||
assertFalse(em.isJoinedToTransaction());
|
||||
assertFalse(transactionScopedContext.isActive());
|
||||
|
||||
// Our PersistenceContextType is TRANSACTION, not EXTENDED, so
|
||||
// the underlying persistence context dies with the
|
||||
@@ -218,6 +267,82 @@ class TestJpaTransactionScopedSynchronizedEntityManager {
|
||||
// should be empty, so the contains() operation should return
|
||||
// false.
|
||||
assertFalse(em.contains(author));
|
||||
|
||||
// Start a transaction.
|
||||
tm.begin();
|
||||
assertTrue(transactionScopedContext.isActive());
|
||||
|
||||
// Remove the Author we successfully committed before. We
|
||||
// have to merge because the author became detached a few
|
||||
// lines above.
|
||||
author = em.merge(author);
|
||||
em.remove(author);
|
||||
assertFalse(em.contains(author));
|
||||
tm.commit();
|
||||
|
||||
assertFalse(em.isJoinedToTransaction());
|
||||
assertFalse(transactionScopedContext.isActive());
|
||||
|
||||
// Note that its ID is still 1.
|
||||
assertEquals(Integer.valueOf(1), author.getId());
|
||||
|
||||
assertDatabaseIsEmpty(dataSource);
|
||||
|
||||
tm.begin();
|
||||
|
||||
try {
|
||||
assertTrue(em.isJoinedToTransaction());
|
||||
assertTrue(transactionScopedContext.isActive());
|
||||
|
||||
// This is interesting. author is now detached, but it
|
||||
// still has a persistent identifier set to 1.
|
||||
em.persist(author);
|
||||
|
||||
// The act of persisting doesn't flush anything, so our id
|
||||
// is still 1.
|
||||
assertEquals(Integer.valueOf(1), author.getId());
|
||||
|
||||
assertTrue(transactionScopedContext.isActive());
|
||||
assertTrue(em.contains(author));
|
||||
assertTrue(em.isJoinedToTransaction());
|
||||
|
||||
// Persisting the same thing again is a no-op.
|
||||
em.persist(author);
|
||||
|
||||
// The act of persisting doesn't flush anything, so our id
|
||||
// is still 1.
|
||||
assertEquals(Integer.valueOf(1), author.getId());
|
||||
|
||||
// Make sure the TransactionContext is active.
|
||||
assertTrue(transactionScopedContext.isActive());
|
||||
assertTrue(em.contains(author));
|
||||
assertTrue(em.isJoinedToTransaction());
|
||||
|
||||
tm.commit();
|
||||
|
||||
// Make sure the TransactionContext is NOT active.
|
||||
assertFalse(em.isJoinedToTransaction());
|
||||
assertFalse(em.contains(author));
|
||||
assertFalse(transactionScopedContext.isActive());
|
||||
|
||||
// Now that the commit and accompanying flush have
|
||||
// happened, our author's ID has changed.
|
||||
assertEquals(Integer.valueOf(2), author.getId());
|
||||
} catch (final EntityExistsException expected) {
|
||||
|
||||
} catch (final Exception somethingUnexpected) {
|
||||
fail(somethingUnexpected);
|
||||
}
|
||||
}
|
||||
|
||||
private static final void assertDatabaseIsEmpty(final DataSource dataSource) throws SQLException {
|
||||
try (final Connection connection = dataSource.getConnection();
|
||||
final Statement statement = connection.createStatement();
|
||||
final ResultSet resultSet = statement.executeQuery("SELECT COUNT(a.id) FROM AUTHOR a");) {
|
||||
assertNotNull(resultSet);
|
||||
assertTrue(resultSet.next());
|
||||
assertEquals(0, resultSet.getInt(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
@DataSourceDefinition(
|
||||
name = "chirp",
|
||||
className = "org.h2.jdbcx.JdbcDataSource",
|
||||
url = "jdbc:h2:mem:chirp;INIT=RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
url = "jdbc:h2:mem:chirp;INIT=SET TRACE_LEVEL_FILE=4\\;RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
serverName = "",
|
||||
properties = {
|
||||
"user=sa"
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.integrations.cdi.jpa.chirp;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.sql.DataSourceDefinition;
|
||||
import javax.inject.Inject;
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.context.BeforeDestroyed;
|
||||
import javax.enterprise.event.Observes;
|
||||
import javax.enterprise.inject.se.SeContainer;
|
||||
import javax.enterprise.inject.se.SeContainerInitializer;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.PersistenceContextType;
|
||||
import javax.persistence.SynchronizationType;
|
||||
import javax.persistence.TransactionRequiredException;
|
||||
import javax.transaction.HeuristicMixedException;
|
||||
import javax.transaction.HeuristicRollbackException;
|
||||
import javax.transaction.NotSupportedException;
|
||||
import javax.transaction.RollbackException;
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.SystemException;
|
||||
import javax.transaction.TransactionManager;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@ApplicationScoped
|
||||
@DataSourceDefinition(
|
||||
name = "chirp",
|
||||
className = "org.h2.jdbcx.JdbcDataSource",
|
||||
url = "jdbc:h2:mem:chirp;INIT=SET TRACE_LEVEL_FILE=4\\;RUNSCRIPT FROM 'classpath:chirp.ddl'",
|
||||
serverName = "",
|
||||
properties = {
|
||||
"user=sa"
|
||||
}
|
||||
)
|
||||
class TestRollbackScenarios {
|
||||
|
||||
private SeContainer cdiContainer;
|
||||
|
||||
@Inject
|
||||
private TransactionManager transactionManager;
|
||||
|
||||
@PersistenceContext(
|
||||
type = PersistenceContextType.TRANSACTION,
|
||||
synchronization = SynchronizationType.SYNCHRONIZED,
|
||||
unitName = "chirp"
|
||||
)
|
||||
private EntityManager jpaTransactionScopedSynchronizedEntityManager;
|
||||
|
||||
|
||||
/*
|
||||
* Constructors.
|
||||
*/
|
||||
|
||||
|
||||
TestRollbackScenarios() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Setup and teardown methods.
|
||||
*/
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void startCdiContainer() {
|
||||
final SeContainerInitializer initializer = SeContainerInitializer.newInstance()
|
||||
.addBeanClasses(this.getClass());
|
||||
assertNotNull(initializer);
|
||||
this.cdiContainer = initializer.initialize();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void shutDownCdiContainer() {
|
||||
if (this.cdiContainer != null) {
|
||||
this.cdiContainer.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Support methods.
|
||||
*/
|
||||
|
||||
|
||||
TestRollbackScenarios self() {
|
||||
return this.cdiContainer.select(TestRollbackScenarios.class).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* A "business method" providing access to one of this {@link
|
||||
* TestRollbackScenarios}' {@link EntityManager}
|
||||
* instances for use by {@link Test}-annotated methods.
|
||||
*
|
||||
* @return a non-{@code null} {@link EntityManager}
|
||||
*/
|
||||
EntityManager getJpaTransactionScopedSynchronizedEntityManager() {
|
||||
return this.jpaTransactionScopedSynchronizedEntityManager;
|
||||
}
|
||||
|
||||
TransactionManager getTransactionManager() {
|
||||
return this.transactionManager;
|
||||
}
|
||||
|
||||
private void onShutdown(@Observes @BeforeDestroyed(ApplicationScoped.class) final Object event,
|
||||
final TransactionManager tm) throws SystemException {
|
||||
// If an assertion fails, or some other error happens in the
|
||||
// CDI container, there may be a current transaction that has
|
||||
// neither been committed nor rolled back. Because the
|
||||
// Narayana transaction engine is fundamentally static, this
|
||||
// means that a transaction affiliation with the main thread
|
||||
// may "leak" into another JUnit test (since JUnit, by
|
||||
// default, forks once, and then runs all tests in the same
|
||||
// JVM). CDI, thankfully, will fire an event for the
|
||||
// application context shutting down, even in the case of
|
||||
// errors.
|
||||
if (tm.getStatus() != Status.STATUS_NO_TRANSACTION) {
|
||||
tm.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test methods.
|
||||
*/
|
||||
|
||||
|
||||
@Test
|
||||
void testRollbackScenarios()
|
||||
throws HeuristicMixedException,
|
||||
HeuristicRollbackException,
|
||||
InterruptedException,
|
||||
NotSupportedException,
|
||||
RollbackException,
|
||||
SystemException
|
||||
{
|
||||
|
||||
// Get a CDI contextual reference to this test instance. It
|
||||
// is important to use "self" in this test instead of "this".
|
||||
final TestRollbackScenarios self = self();
|
||||
assertNotNull(self);
|
||||
|
||||
// Get the EntityManager that is synchronized with and scoped
|
||||
// to a JTA transaction.
|
||||
final EntityManager em = self.getJpaTransactionScopedSynchronizedEntityManager();
|
||||
assertNotNull(em);
|
||||
assertTrue(em.isOpen());
|
||||
|
||||
// We haven't started any kind of transaction yet and we
|
||||
// aren't testing anything using
|
||||
// the @javax.transaction.Transactional annotation so there is
|
||||
// no transaction in effect so the EntityManager cannot be
|
||||
// joined to one.
|
||||
assertFalse(em.isJoinedToTransaction());
|
||||
|
||||
// Get the TransactionManager that normally is behind the
|
||||
// scenes and use it to start a Transaction.
|
||||
final TransactionManager tm = self.getTransactionManager();
|
||||
assertNotNull(tm);
|
||||
tm.begin();
|
||||
assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
|
||||
|
||||
// Now magically our EntityManager should be joined to it.
|
||||
assertTrue(em.isJoinedToTransaction());
|
||||
|
||||
// Create a JPA entity and insert it.
|
||||
Author author = new Author("Abraham Lincoln");
|
||||
em.persist(author);
|
||||
|
||||
// No trip to the database has happened yet, so the author's
|
||||
// identifier isn't set yet.
|
||||
assertNull(author.getId());
|
||||
|
||||
// Commit the transaction. Because we're relying on the
|
||||
// default flush mode, this will cause a flush to the
|
||||
// database, which, in turn, will result in author identifier
|
||||
// generation.
|
||||
tm.commit();
|
||||
assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
|
||||
assertEquals(Integer.valueOf(1), author.getId());
|
||||
|
||||
// We're no longer in a transaction.
|
||||
assertFalse(em.isJoinedToTransaction());
|
||||
|
||||
// The persistence context should be cleared.
|
||||
assertFalse(em.contains(author));
|
||||
|
||||
// Ensure transaction statuses are what we think they are.
|
||||
tm.begin();
|
||||
tm.setRollbackOnly();
|
||||
try {
|
||||
assertEquals(Status.STATUS_MARKED_ROLLBACK, tm.getStatus());
|
||||
} finally {
|
||||
tm.rollback();
|
||||
}
|
||||
assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
|
||||
|
||||
// We can do non-transactional things.
|
||||
assertTrue(em.isOpen());
|
||||
author = em.find(Author.class, Integer.valueOf(1));
|
||||
assertNotNull(author);
|
||||
|
||||
// Note that because we've invoked this somehow outside of a
|
||||
// transaction everything it touches is detached, per section
|
||||
// 7.6.2 of the JPA 2.2 specification.
|
||||
assertFalse(em.contains(author));
|
||||
|
||||
// Remove everything.
|
||||
tm.begin();
|
||||
author = em.merge(author);
|
||||
assertNotNull(author);
|
||||
assertTrue(em.contains(author));
|
||||
em.remove(author);
|
||||
tm.commit();
|
||||
assertFalse(em.contains(author));
|
||||
|
||||
// Perform a rollback "in the middle" of a sequence of
|
||||
// operations and observe that the EntityManager is in the
|
||||
// proper state throughout.
|
||||
author = new Author("John Kennedy");
|
||||
tm.begin();
|
||||
em.persist(author);
|
||||
assertTrue(em.contains(author));
|
||||
tm.rollback();
|
||||
assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
|
||||
assertFalse(em.contains(author));
|
||||
try {
|
||||
em.remove(author);
|
||||
fail("remove() was allowed to complete without a transaction");
|
||||
} catch (final IllegalArgumentException | TransactionRequiredException expected) {
|
||||
// The javadocs say only that either of these exceptions may
|
||||
// be thrown in this case but do not indicate which one is
|
||||
// preferred. EclipseLink 2.7.4 throws a
|
||||
// TransactionRequiredException here. It probably should
|
||||
// throw an IllegalArgumentException; see
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=553117
|
||||
// which is related.
|
||||
}
|
||||
|
||||
// author is detached; prove it.
|
||||
tm.begin();
|
||||
assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
|
||||
assertTrue(em.isJoinedToTransaction());
|
||||
assertFalse(em.contains(author));
|
||||
em.detach(author); // redundant; just making a point
|
||||
assertFalse(em.contains(author));
|
||||
try {
|
||||
em.remove(author);
|
||||
// We shouldn't get here because author is detached but with
|
||||
// EclipseLink 2.7.4 we do. See
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=553117.
|
||||
} catch (final IllegalArgumentException expected) {
|
||||
|
||||
}
|
||||
tm.rollback();
|
||||
|
||||
// Remove the author properly.
|
||||
tm.begin();
|
||||
assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
|
||||
assertTrue(em.isJoinedToTransaction());
|
||||
assertFalse(em.contains(author));
|
||||
author = em.merge(author);
|
||||
em.remove(author);
|
||||
tm.commit();
|
||||
assertFalse(em.contains(author));
|
||||
|
||||
// Cause a timeout-tripped rollback.
|
||||
tm.setTransactionTimeout(1); // 1 second
|
||||
author = new Author("Woodrow Wilson");
|
||||
tm.begin();
|
||||
assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
|
||||
Thread.sleep(1500L); // 1.5 seconds (arbitrarily greater than 1 second)
|
||||
assertEquals(Status.STATUS_ROLLEDBACK, tm.getStatus());
|
||||
try {
|
||||
em.persist(author);
|
||||
fail("Transaction rolled back but persist still happened");
|
||||
} catch (final TransactionRequiredException expected) {
|
||||
|
||||
}
|
||||
tm.rollback();
|
||||
assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
|
||||
|
||||
tm.setTransactionTimeout(60); // 60 seconds; the usual default
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,4 +14,6 @@
|
||||
.level=INFO
|
||||
handlers=io.helidon.common.HelidonConsoleHandler
|
||||
|
||||
io.helidon.integrations.cdi.jpa.level=FINER
|
||||
io.helidon.integrations.cdi.jpa.level=INFO
|
||||
org.eclipse.persistence.level=INFO
|
||||
h2database.level=INFO
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
CREATE TABLE AUTHOR (
|
||||
CREATE TABLE IF NOT EXISTS AUTHOR (
|
||||
ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
NAME VARCHAR(64) NOT NULL
|
||||
NAME VARCHAR(64) NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE MICROBLOG (
|
||||
CREATE TABLE IF NOT EXISTS MICROBLOG (
|
||||
ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
AUTHOR_ID INT NOT NULL,
|
||||
NAME VARCHAR(64) NOT NULL,
|
||||
FOREIGN KEY (AUTHOR_ID) REFERENCES AUTHOR(ID)
|
||||
);
|
||||
|
||||
CREATE TABLE CHIRP (
|
||||
CREATE TABLE IF NOT EXISTS CHIRP (
|
||||
ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
MICROBLOG_ID INT NOT NULL,
|
||||
CONTENT VARCHAR(140) NOT NULL,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package io.helidon.integrations.jta.cdi;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.context.BeforeDestroyed;
|
||||
import javax.enterprise.context.Destroyed;
|
||||
import javax.enterprise.context.Initialized;
|
||||
import javax.enterprise.event.Event;
|
||||
@@ -58,6 +59,15 @@ class NarayanaTransactionManager extends DelegatingTransactionManager {
|
||||
*/
|
||||
private final Event<Transaction> transactionScopeInitializedBroadcaster;
|
||||
|
||||
/**
|
||||
* An {@link Event} capable of {@linkplain Event#fire(Object)
|
||||
* firing} an {@link Object} just before {@linkplain TransactionScoped
|
||||
* transaction scope} is about to end.
|
||||
*
|
||||
* <p>This field may be {@code null}.</p>
|
||||
*/
|
||||
private final Event<Object> transactionScopeBeforeDestroyedBroadcaster;
|
||||
|
||||
/**
|
||||
* An {@link Event} capable of {@linkplain Event#fire(Object)
|
||||
* firing} an {@link Object} when {@linkplain TransactionScoped
|
||||
@@ -91,7 +101,7 @@ class NarayanaTransactionManager extends DelegatingTransactionManager {
|
||||
*/
|
||||
@Deprecated
|
||||
NarayanaTransactionManager() {
|
||||
this(null, null, null);
|
||||
this(null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,10 +130,13 @@ class NarayanaTransactionManager extends DelegatingTransactionManager {
|
||||
NarayanaTransactionManager(final JTAEnvironmentBean jtaEnvironmentBean,
|
||||
@Initialized(TransactionScoped.class)
|
||||
final Event<Transaction> transactionScopeInitializedBroadcaster,
|
||||
@BeforeDestroyed(TransactionScoped.class)
|
||||
final Event<Object> transactionScopeBeforeDestroyedBroadcaster,
|
||||
@Destroyed(TransactionScoped.class)
|
||||
final Event<Object> transactionScopeDestroyedBroadcaster) {
|
||||
super(jtaEnvironmentBean == null ? null : jtaEnvironmentBean.getTransactionManager());
|
||||
this.transactionScopeInitializedBroadcaster = transactionScopeInitializedBroadcaster;
|
||||
this.transactionScopeBeforeDestroyedBroadcaster = transactionScopeBeforeDestroyedBroadcaster;
|
||||
this.transactionScopeDestroyedBroadcaster = transactionScopeDestroyedBroadcaster;
|
||||
}
|
||||
|
||||
@@ -200,7 +213,13 @@ class NarayanaTransactionManager extends DelegatingTransactionManager {
|
||||
@Override
|
||||
public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException, SystemException {
|
||||
try {
|
||||
super.commit();
|
||||
try {
|
||||
if (this.transactionScopeBeforeDestroyedBroadcaster != null) {
|
||||
this.transactionScopeBeforeDestroyedBroadcaster.fire(this.toString());
|
||||
}
|
||||
} finally {
|
||||
super.commit();
|
||||
}
|
||||
} finally {
|
||||
if (this.transactionScopeDestroyedBroadcaster != null) {
|
||||
this.transactionScopeDestroyedBroadcaster.fire(this.toString());
|
||||
@@ -234,7 +253,13 @@ class NarayanaTransactionManager extends DelegatingTransactionManager {
|
||||
@Override
|
||||
public void rollback() throws SystemException {
|
||||
try {
|
||||
super.rollback();
|
||||
try {
|
||||
if (this.transactionScopeBeforeDestroyedBroadcaster != null) {
|
||||
this.transactionScopeBeforeDestroyedBroadcaster.fire(this.toString());
|
||||
}
|
||||
} finally {
|
||||
super.rollback();
|
||||
}
|
||||
} finally {
|
||||
if (this.transactionScopeDestroyedBroadcaster != null) {
|
||||
this.transactionScopeDestroyedBroadcaster.fire(this.toString());
|
||||
|
||||
Reference in New Issue
Block a user