Writing a database logger from Play - how to handle errors? - java

I'm writing a database log4j appender in Play.
On its append() method, it creates a new instance of a model entity Log4jLine, and saves() it via JPA.
This works fine on debug/info logs. However, exceptions are not logged.
There is a call to this appender from framework code, but I suspect it is not working because the JPA session is not valid at this point.
How would I adapt the code to support this use case?
public class DBAppender extends AppenderSkeleton {
#Override
protected void append(LoggingEvent loggingEvent) {
Log4jLine logLine = new Log4jLine(...);
logLine.save();
}
}

Per our conversation above I was curious about how to do this and was able to get this to work. Essentially, you need to create a new EntityManager and fully manage it yourself. Calling any of the Play built-ins (.save(), .merge(), etc) will cause them to use the EntityManager that Play sets up for you. However, if you revert to standard JPA calls, it will work.
// Mapped in routes file as GET /exception
public static void exceptionTest() {
RuntimeException e = new RuntimeException("This is a test");
logException(e);
renderText("You are here");
}
private static void logException(RuntimeException e) {
EntityManager em = JPA.newEntityManager();
Notification n = new Notification();
n.setMessage(e.getMessage());
em.getTransaction().begin();
em.persist(n);
em.getTransaction().commit();
throw e;
}
This is just a quick and dirty experiment to prove the point. You'll obviously need to handle exceptions and failure cases in logException() that I'm not handling. And just for clarity, Notification is a basic object in my project I was able to use for this quickly.
Hope that helps!

Related

Best practices for exception in my DAO layer

I'm working on my dao layer implementation and I'm confused about the exception handling part. I have many solution to throw exception so what is the best solution to handle exception in dao layer :
This is my code :
My DAO Interface :
#Repository
#Transactional
public interface GnqstRepository<T,V> {
public T save(T t);
public void delete(V v)throws GnqstRepositoryNotFoundException;
public T update(T t);
public T find(V v)throws GnqstRepositoryNotFoundException;
public List<T> findAll();
}
And this is my this an implementation for an entity :
public class EnqueteurRepository implements GnqstRepository<Enqueteur, Long> {
#PersistenceContext(name="gnst")
private EntityManager em ;
private static final Logger logger = Logger.getLogger(EnqueteurRepository.class);
#Override
public Enqueteur find(Long v)throws GnqstRepositoryNotFoundException {
Enqueteur enqueteur = em.find(Enqueteur.class,v);
if(enqueteur!= null){
logger.info("EnqueteurRepository : l'enqueteur avec l'ID : "+v+" a été trouvé avec succès.");
return enqueteur;
}
else{
throw new GnqstRepositoryNotFoundException("L'identifiant entré ne ressemble pas à un ID enqueteur valide.");
}
}
}
Here I create a checked exception my question is there best way than this to throw an exception to the next layer in case for example the find method do not find an entity ?
My objectif is that the user in IHM when click in button find for search in case of no result found then a message should show in IHM in my case I throw exception with a message throught dao layer,business layer to presentation layer my question is who is the best throw an exception or return null to the presentation layer then check an return message to the user ?Thank in advance
There are no hard and fast rules regarding exception handling, but these are my recommendations:
You have to ask yourself what the users of your repository would like to do when they do a lookup with a missing id.
If they want to take immediate action - for instance use a fallback repo - I would simply return a value representing "miss", such as null or Optional.empty().
If you want the error to propagate several layers up your application and maybe render a 404 or abort the running program, I would use an exception.
If you don't want to handle exceptions from different repositories differently, I would just stick with the standard EntityNotFoundException. If you do want to handle them differently I would introduce my own RuntimeExceptions.
Checked exceptions are quite rarely used today. They exist in the borderland between returning values representing failures and throwing exceptions. I believe that the consensus in the community is that they sound like a good idea but are bad in practice. So I would avoid them.
What's wrong with javax.persistence.EntityNotFoundException? This is a matter of debate, but if I were you I would keep close to JPA and avoid adding custom exceptions. I would also avoid changing the behaviour. In JPA the find method returns null if an entity is missing. Users of your find method may find it confusing to get an exception instead. I would return null or rename the method to findRequired.
I din't it's better to throw the Exception in the next layer. And you could simply return an Optional on this method:
public class EnqueteurRepository implements GnqstRepository<Enqueteur, Long> {
#PersistenceContext(name="gnst")
private EntityManager em ;
#Override
public Optional<Enqueteur> find(Long v) {
return Optional.ofNullable(em.find(Enqueteur.class,v));
}
}
And then, could throw a custom exception in the next layer.
void methodCallingDao() {
Optional<Enqueteur> enqueteur = enqueteurRepository.find(new Long(1));
if(!enqueteur.isPresent()) {
throw new RuntimeException("Error retrieving enqueteur");
}
}

Eclipse Scout - Clean Database authentication

I'm trying to implement a database authentication with Eclipse Scout.
For that I created a class DataSourceCredentialVerifier in the client module, which implements the ICredentialVerifierinterface. Then I adapted the init method of the UiServletFilter class to use my verifier.
public class DataSourceCredentialVerifier implements ICredentialVerifier {
private static final Logger LOG = LoggerFactory.getLogger(DataSourceCredentialVerifier.class);
#Override
public int verify(String username, char[] password) throws IOException {
Object queryResult[][] = BEANS.get(IMySqlAuthService.class).load();
return AUTH_OK;
}
I haven't implemented any authentication logic yet. My task now is to establish a clean database connection.
For that I created the following interface in the shared module:
public interface IMySqlAuthService extends IService {
Object[][] load();
}
The implementation is in the server module:
public class MySqlAuthService implements IMySqlAuthService {
#Override
public Object[][] load() {
String sql = "select username, password from users ";
Object[][] queryResult = SQL.select(sql, null, null);
return queryResult;
}
}
First I want to see, if there is at least something in the query, but I get an AssertionException here:
Object queryResult[][] = BEANS.get(IMySqlAuthService.class).load();
org.eclipse.scout.rt.platform.util.Assertions$AssertionException: Assertion error: no instance found for query: interface org.eclipse.scout.app.shared.services.IMySqlAuthService
at org.eclipse.scout.rt.platform.util.Assertions.fail(Assertions.java:580)
at org.eclipse.scout.rt.platform.util.Assertions.assertNotNull(Assertions.java:87)
at org.eclipse.scout.rt.platform.BEANS.get(BEANS.java:41)
I don't get an instance of my MySqlAuthService implementation. I assume that the BeanManager should have created an instance for me. MySqlAuthService should be registered as a Bean, since my IMySqlAuthService interface extends from IService which has the #ApplicationScoped annotation.
Adding the #Bean annotation to MySqlAuthService results in the same exception.
Here some information about the BeanManager and annotations:
https://eclipsescout.github.io/6.0/technical-guide.html#sec-bean.manager
Here is another different approach s.o. tried, but it doesn't feel correct:
https://www.eclipse.org/forums/index.php/t/1079741/
How can I get my example to work with my service?
Here is the working solution with important explanations of Eclipse Scout principles.
The source is summarized information of the Eclipse-Scout-Technical-Guide.
In Scout there is a built in annotation: #TunnelToServer. Interfaces marked with this annotation are called on the server. The server itself ignores this annotation.
To achieve that a bean is registered on client side, this annotation is required. The platform cannot (!) directly create an instance for these beans, a specific producer is registered which creates a proxy that delegates the call to the server.
My first clear mistake was that I hadn't annotated the IMySqlAuthServicewith #TunnelToServer.
After this addition I got rid of the no instance AssertionError.
After that my code ran into the HTTP status-code: 403 access forbidden.
This occured because my code didn't run in the correct Thread. That is the current RunContext. I had to use this lines of code in my verify method of the DataSourceCredentialVerifier:
Subject subject = new Subject();
subject.getPrincipals().add(new SimplePrincipal("system"));
subject.setReadOnly();
RunContext runContext = RunContexts.copyCurrent().withSubject(subject);
Now one can use the runContext's call() or run() method, depending whether the code returns a result. The action is run in the current thread, meaning that the caller is blocked until completion.
Concrete example solution:
Object[][] result = runContext.call(new Callable<Object[][]>() {
#Override
public Object[][] call() throws Exception {
return BEANS.get(IMySqlAuthService.class).load();
}
});
//TODO implement authentication logic.
For more information about the RunContext see here:
https://eclipsescout.github.io/6.0/technical-guide.html#runcontext

After a commited & shutdown transaction which added new class to a graph - a new Tx doesn't see the class in schema, though it is persisted

We persist a graph in a piece of code and then have another code, that tries to retrieve it. We open our transacitons with this Spring bean. Anyone who wants to access the database always calls the getGraph() method of this bean.
public class OrientDatabaseConnectionManager {
private OrientGraphFactory factory;
public OrientDatabaseConnectionManager(String path, String name, String pass) {
factory = new OrientGraphFactory(path, name, pass).setupPool(1,10);
}
public OrientGraphFactory getFactory() {
return factory;
}
public void setFactory(OrientGraphFactory factory) {
this.factory = factory;
}
/**
* Method returns graph instance from the factory's pool.
* #return
*/
public OrientGraph getGraph(){
OrientGraph resultGraph = factory.getTx();
resultGraph.setThreadMode(OrientBaseGraph.THREAD_MODE.ALWAYS_AUTOSET);
return resultGraph;
}
}
(I was unable to quite understand the thread_mode fully, but I think it should not be related to the problem.)
The code, that persists the graph commits and shuts down, as you can see here:
OrientDatabaseConnectionManager connMan; //this is an injected bean from above.
public boolean saveGraphToOrientDB(
SparseMultigraph<SocialVertex, SocialEdge> graph, String label) {
boolean isSavedCorrectly = false;
OrientGraph graphO = connMan.getGraph();
try {
graphDBinput.saveGraph(graph, label, graphO);
// LOG System.out.println("Graph was saved with label "+label);
isSavedCorrectly = true;
} catch (AlreadyUsedGraphLabelException ex) {
Logger.getLogger(GraphDBFacade.class.getName()).log(Level.SEVERE, null, ex);
} finally {
graphO.shutdown(); //calls .commit() automatically normally, but commit already happens inside.
}
return isSavedCorrectly;
}
This commit works well - the data are always persisted, I checked everytime in the orientdb admin interface, and the first persisted graph is always viewable okay. It might be important to note, that during the saving the label used defines new class (thus modifying schema, as I understand it) and uses it for the persisted graph.
The retrieval of the graph looks something like this:
#Override
public SocialGraph getSocialGraph(String label) {
OrientGraph graph = connMan.getGraph();
SocialGraph socialGraph = null;
try {
socialGraph = new SocialGraph(getAllSocialNodes(label, graph), getAllSocialEdges(label, graph));
} catch (Exception e) {
logger.error(e);
} finally {
graph.shutdown();
}
return socialGraph;
}
public List<Node> getAllSocialNodes(String label, OrientGraph graph) {
return constructNodes(graphFilterMan.getAllNodesFromGraph(label, graph));
}
public Set<Vertex> getAllNodesFromGraph(String graphLabel, OrientGraph graph) {
Set<Vertex> labelledGraph = new HashSet<>();
try{
Iterable<Vertex> configGraph = graph.getVerticesOfClass(graphLabel);
for(Vertex v : configGraph){ //THE CODE CRASHES HERE, WITH "CLASS WITH NAME graphLabel DOES NOT EXIST
labelledGraph.add(v);
}
} catch(Exception ex){
logger.error(ex);
graph.rollback();
}
return labelledGraph;
}
So the problem is, that when we persist a new graph with a new class, say "graph01" and then we want to retrieve it, it goes okay. Later, we create a "graph02" and we want to retrieve it, but it crashes, as commented above - OrientDb tells you, that the class with "graph02" name does not exist.
It does exist in the admin interface at the time, however, when I debug, the class actually is not in the schema right after call of factory.getTx()
Right at the beginning, when we get a transaction graph instance from the factory, we get a graph with a context in which the rawGraph's underlying database's metadata have the schema proxy delegate schema shared classes WITHOUT the new class, which I can apparently see commited in the database.
Or here on picture:
There should be one more class in the schema. The one that was persisted (and commited ) a while ago - which can also be seen in the orientDb admin interface (not present in the variable)
What I presume is happening is that the pool, from which the factory gets the transaction has somewhat cached schema or something. It does not refresh the schema, when we add a new class.
Why does the schema not show the new class, when we are trying to get the new graph out? Does schema not get refreshed?
I found here in schema documentation that
NOTE: Changes to the schema are not transactional, so execute them outside a transaction.
So should we create the new class outside a transaction and then we would get an update in the schema in the context?
//Maybe I am understanding the concepts wrong - I got in contact with OrientDb just yesterday and I am to find out the problem in an already written code.
Db we use is a remote:localhost/socialGraph
OrientDB of version 1.7.4
We noticed in our code about the same issue, schema changes aren't visible in pooled connections.
We also have a sort of factory that gets a connection. What we do is keep a schema version number, and each time we have some operation that changes the schema, we bump the number and when a new connection is opened, we check the schema version, if it is changed.
When the schema is changed, we reload the schema, close the pool and recreate it. The method is proven for us to work (we are currently on version 2.0.15).
Here's the relevant code:
private static volatile int schemaVersion = -1;
private OPartitionedDatabasePool pool;
protected void createPool() {
pool = new OPartitionedDatabasePool(getUrl(), getUsername(), getPassword());
}
#Override
public synchronized ODatabaseDocumentTx openDatabase() {
ODatabaseDocumentTx db = pool.acquire();
//DatabaseInfo is a simple class put in a static contect that holds the schema version.
DatabaseInfo databaseInfo = CurrentDatabaseInfo.getDatabaseInfo();
ODocument document = db.load((ORID) databaseInfo.getId(), "schemaVersion:0", true);
Integer version = document.field("schemaVersion");
if (schemaVersion == -1) {
schemaVersion = version;
} else if (schemaVersion < version) {
db.getMetadata().getSchema().reload();
schemaVersion = version;
pool.close();
createPool();
db = pool.acquire();
}
return db;
}
In the end the problem was, that we had two liferay projects, each had its own spring application context in its WAR file and when we deployed these projects as portlets within Liferay, the two projects created two contexts, in each having one OrientDatabaseConnectionManager.
In one context the schema was being changed. And even though I reset the connection and reloaded the schema, it only happened with the connection manager / factory in one context. The retrieving of the graph was happening in the portlet of the other project though, resulting in an outdate schema (which was not reloaded, because the reloading happened in the other spring context) - thus the error.
So you have to be careful - either share one spring application context with beans for all your portlets (which is possible by having a parent application context, you can read more about it here)
OR
check for changes in the schema from within the same project which you will also use to retrieve the data later.

How can I intercept JTA transactions events and get a reference to the current EntityManager associated with the transaction

Long story short: We develop and maintain a library that can be used in other projects using JavaEE7/CDI/JPA. Applications will run under Glassfish-4.0 and use Hibernate's JPA implementation for an underlying PostgreSQL persistence. This is part of a long term migration effort to rewrite old applications that were written in Spring/Struts/Hibernate into the new world of JavaEE7/CDI/JTA.
The problem: For audit purposes, our library needs to intercept all database transactions and include custom SQL statements before the user statements are executed. At this point, the current username and IP address need to be inserted into a temporary database variable (vendor specific feature) so that a database trigger can read them to create the audit trail for any row modification. This particular post was very helpful providing alternatives, and our team went down the trigger road due to a previously established legacy.
HOWEVER: We are deeply disappointed at how JTA handles transaction events. There are numerous ways to intercept transactions, but this particular case seems to be down right impossible. In the old architecture, using Spring's transaction manager, we simply used a Hibernate Interceptor implementing Interceptor.afterTransactionBegin(...). Reading up on the official JTA-1.2 spec, we found that it does have support for Synchronization.beforeCompletion and Synchronization.afterCompletion. After several hours of debugging sessions we clearly noted that Hibernate's implementation of JTA is using these facilities. But JTA seems to be lacking events like beforeBegin and afterBegin (which IMHO seems to be a lack of common sense). And since there are no facilities to intercept those, Hibernate complies fully with JTA and it simply won't. Period.
No matter what we do, we can't find a way. We tried, for instance, to intercept #Transactional annotations and run our code just after the container's JTA impl does its job to open the transaction. But we lack the ability to dynamically acquire the EntityManager associated with that particular transaction. Remember: this is a library, not the web application itself. It cannot make any assumptions about which Persistence Units are declared and used by the application. And, as far as we can tell, we need to know which specific Persistent Unit name to inject it into our code. We are trying to provide an audit facility to other temas that is as transparent as possible.
So we humbly ask for help. If anyone out there has a solution, workaround, whatever opinion, we'll be glad to hear it.
This was quickly answered here in this post by myself, but hiding the fact that we spent over two weeks trying different strategies to overcome this. So, here goes our final implementation we decided to use.
Basic idea: Create your own implementation of javax.persistence.spi.PersistenceProvider by extending the one given by Hibernate. For all effects, this is the only point where your code will be tied to Hibernate or any other vendor specific implementation.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {
#Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
}
}
The idea is to wrap hibernate's versions of EntityManagerFactory and EntityManager with your own implementation. So you need to create classes that implement these interfaces and keep the vendor specific implementation inside.
This is the EntityManagerFactoryWrapper
public class EntityManagerFactoryWrapper implements EntityManagerFactory {
private EntityManagerFactory emf;
public EntityManagerFactoryWrapper(EntityManagerFactory originalEMF) {
emf = originalEMF;
}
public EntityManager createEntityManager() {
return new EntityManagerWrapper(emf.createEntityManager());
}
// Implement all other methods for the interface
// providing a callback to the original emf.
The EntityManagerWrapper is our interception point. You will need to implement all methods from the interface. At every method where an entity can be modified, we include a call to a custom query to set local variables at the database.
public class EntityManagerWrapper implements EntityManager {
private EntityManager em;
private Principal principal;
public EntityManagerWrapper(EntityManager originalEM) {
em = originalEM;
}
public void setAuditVariables() {
String userid = getUserId();
String ipaddr = getUserAddr();
String sql = "SET LOCAL application.userid='"+userid+"'; SET LOCAL application.ipaddr='"+ipaddr+"'";
em.createNativeQuery(sql).executeUpdate();
}
protected String getUserAddr() {
HttpServletRequest httprequest = CDIBeanUtils.getBean(HttpServletRequest.class);
String ipaddr = "";
if ( httprequest != null ) {
ipaddr = httprequest.getRemoteAddr();
}
return ipaddr;
}
protected String getUserId() {
String userid = "";
// Try to look up a contextual reference
if ( principal == null ) {
principal = CDIBeanUtils.getBean(Principal.class);
}
// Try to assert it from CAS authentication
if (principal == null || "anonymous".equalsIgnoreCase(principal.getName())) {
if (AssertionHolder.getAssertion() != null) {
principal = AssertionHolder.getAssertion().getPrincipal();
}
}
if ( principal != null ) {
userid = principal.getName();
}
return userid;
}
#Override
public void persist(Object entity) {
if ( em.isJoinedToTransaction() ) {
setAuditVariables();
}
em.persist(entity);
}
#Override
public <T> T merge(T entity) {
if ( em.isJoinedToTransaction() ) {
setAuditVariables();
}
return em.merge(entity);
}
#Override
public void remove(Object entity) {
if ( em.isJoinedToTransaction() ) {
setAuditVariables();
}
em.remove(entity);
}
// Keep implementing all methods that can change
// entities so you can setAuditVariables() before
// the changes are applied.
#Override
public void createNamedQuery(.....
Downside: Interception queries (SET LOCAL) will likely run several times inside a single transaction, specially if there are several statements made on a single service call. Given the circumstances, we decided to keep it this way due to the fact that it's a simple SET LOCAL in memory call to PostgreSQL. Since there are no tables involved, we can live with the performance hit.
Now just replace Hibernate's persistence provider inside persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="petstore" transaction-type="JTA">
<provider>my.package.HibernatePersistenceProvider</provider>
<jta-data-source>java:app/jdbc/exemplo</jta-data-source>
<properties>
<property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform" />
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
</properties>
</persistence-unit>
As a side note, this is the CDIBeanUtils we have to help with the bean manager on some special occasions. In this case, we are using it to look up a reference to HttpServletRequest and Principal.
public class CDIBeanUtils {
public static <T> T getBean(Class<T> beanClass) {
BeanManager bm = CDI.current().getBeanManager();
Iterator<Bean<?>> ite = bm.getBeans(beanClass).iterator();
if (!ite.hasNext()) {
return null;
}
final Bean<T> bean = (Bean<T>) ite.next();
final CreationalContext<T> ctx = bm.createCreationalContext(bean);
final T t = (T) bm.getReference(bean, beanClass, ctx);
return t;
}
}
To be fair, this is not exactly intercepting Transactions events. But we are able to include the custom queries we need inside the transaction.
Hopefully this can help others avoid the pain we went through.

EJB TransactionRequiredException in GlassFish 2.1

In my Stateful bean, I have the following lines:
#Stateful(mappedName = "ejb/RegistrationBean")
#StatefulTimeout(unit = TimeUnit.MINUTES, value = 30)
#TransactionManagement(value=TransactionManagementType.CONTAINER)
public class RegistrationStateful implements RegistrationStatefulRemote {
#PersistenceContext
EntityManager em;
private List<Event> reservedSessions = new ArrayList<Event>();
private boolean madePayment = false;
...
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void cancelReservation() {
if (reservedSessions.size() != 0) {
Teacher theTeacher;
for (Event session : reservedSessions) {
if ((theTeacher = session.teacher) == null) theTeacher = bestTeacher.teacher;
theTeacher = em.merge(theTeacher) //The exception is thrown here
//Make changes to theTeacher
em.flush(); //The exception is also thrown here
}
//Clear the reservedSessions list
reservedSessions.clear();
}
}
#Remove
public void endRegistration() {}
#PreDestroy
public void destroy() {
//Cancel outstanding reservations if payment has not been made
if (!madePayment) cancelReservation();
}
}
The line em.merge(someEntity) throws the TransactionRequiredException. Could someone please tell me why it happens? I thought with TransactionAttribute.REQUIRED, a transaction will AUTOMATICALLY be created if there isn't an active one. I tried to use em.joinTransaction() but it throws the same Exception. I'm a beginner at this transaction thing. I'd be very grateful if someone could explain this to me.
UPDATE: I'd like to add a bit more information
The Stateful bean actually also has the following function:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void reserveSession(List<Event> sessions) throws ReservationException {
//Reserve the sessions
Teacher theTeacher;
for (Event session : sessions) {
if ((theTeacher = session.teacher) == null) theTeacher = bestTeacher.teacher;
theTeacher = em.merge(theTeacher);
//Make changes to theTeacher
em.flush();
}
}
The flow is as following: the user tells me his free time and I reserve some seats for him. After that, I show him his reserved seats and he can choose to make payment or cancel the reservations.
The reserved() function worked perfectly as expected but the cancelReservation() did not.
UPDATE 2: I have fixed the problem last night by commenting out the lines "#TransactionAttribute(TransactionAttributeType.REQUIRED)", "em.merge(theTeacher)" and "em.flush()" in the "cancelReservation()" function. The result is perfect. Would it be safe if I cut off those lines? I was afraid I would get "detached entity" exception when I used "em.merge()" in the first place.
The only thing that springs to mind (if you'll excuse the pun) is that if you're calling cancelReservation() from another method inside the bean, then i'm not sure the transaction annotation will be observed. The annotation ultimately works by summoning an interceptor, and i believe interceptors are only applied to calls between different classes (this is something i should really check).
So, if you have a non-transactional method on the bean which calls a transactional method, then a transaction won't be started when the transactional method is called.
I could be completely wrong about this. I'll go and have a bit of a read of the spec and get back to you.
EDIT: I had a read of the spec, and it reminded me what a disaster zone the J2EE specs are. Horrific. However, the section on transactions does seem to imply that the transaction attributes only apply to calls made to an EJB's business interface. I believe calls from one method to another inside a bean are not considered to go through the business interface, even when the method being called is part of that interface. Therefore, you wouldn't expect them to attract transactions.
Something you could try would be to route them through the interface; there is no nice way of doing this, but you should be able to inject a business-interface self-reference like this:
public class RegistrationStateful implements RegistrationStatefulRemote {
#EJB
private RegistrationStatefulRemote self;
You can then change your #PreDestroy method to look like this:
#PreDestroy
public void destroy() {
self.cancelReservation();
}
And i believe that should count as a normal business interface call, with transactions and so on.
I have never actually tried this, so this could be complete rubbish. If you try it, let me know how it works out!

Categories