Search code examples
javajakarta-eeejbcdijta

What is the CDI equivalent of EJB's SessionSynchronization#afterCompletion method?


I've read CDI 2.0 specification (JSR 365) and found out the existence of the @Observes(during=AFTER_SUCCESS) annotation, but it actually requires a custom event to be defined in order to work.

This is what i've got:

//simple """transactional""" file system manager using command pattern  

@Transactional(value = Transactional.TxType.REQUIRED)
@TransactionScoped
@Stateful
public class TransactionalFileSystemManager implements SessionSynchronization {
    private final Deque<Command> commands = new ArrayDeque<>();

    public void createFile(InputStream content, Path path, String name) throws IOException {
        CreateFile command = CreateFile.execute(content, path, name);
        commands.addLast(command);
    }

    public void deleteFile(Path path) throws IOException {
        DeleteFile command = DeleteFile.execute(path);
        commands.addLast(command);
    }

    private void commit() throws IOException{
        for(Command c : commands){
            c.confirm();
        }
    }

    private void rollback() throws IOException{
        Iterator<Command> it = commands.descendingIterator();
        while (it.hasNext()) {
            Command c = it.next();
            c.undo();
        }
    }

    @Override
    public void afterBegin() throws EJBException{

    }

    @Override
    public void beforeCompletion() throws EJBException{

    }

    @Override
    public void afterCompletion(boolean commitSucceeded) throws EJBException{
        if(commitSucceeded){
            try {
                commit();
            } catch (IOException e) {
                throw new EJBException(e);
            }
        }
        else {
            try {
                rollback();
            } catch (IOException e) {
                throw new EJBException(e);
            }
        }
    }
}

However, I want to adopt a CDI-only solution so I need to remove anything EJB related (including the SessionSynchronization interface). How can i achieve the same result using CDI?


Solution

  • First the facts: the authoritative source for this topic is the Java Transaction API (JTA) specification. Search for it online, I got this.

    Then the bad news: In order to truly participate in a JTA transaction, you either have to implement a connector according to the Java Connector Architecture (JCA) specification or a XAResource according to JTA. Never done any of them, I am afraid both are going to be hard. Nevertheless, if you search, you may find an existing implementation of a File System Connector.

    Your code above will never accomplish true 2-phase commit because, if your code fails, the transaction is already committed, so the application state is inconsistent. Or, there is a small time window when the real transaction is committed but the file system change have not beed executed, again the state is inconsistent.

    Some workarounds I can think of, none of which solves the consistency problem:

    • Persist the File System commands in a database. This ensures that they are enqueued transactionally. A scheduled job wakes up and actually tries to execute the queued FS commands.
    • Register a Synchronization with the current Transaction, fire an appropriate event from there. Your TransactionalFileSystemManager observes this event, no during attribute needed I guess.