Arkadiusz
Arkadiusz

Reputation: 51

AutoCloseable-ish method but runs only on catch


I would like to two have two different methods running in catch and final blocks. I have found AutoCloseable interface, but I need something to fire in case of exception only.
Like:

SomeService service = CreateService().andOpenTransaction()

try {
    service.doSomeMessyThingsInsideDB();
} catch (Exception e) {
    service.rollbackTransaction();
    throw e;
} finally {
    service.closeConnection();
}

Is there any way to make it simpler? As I said I am familiar with AutoCloseable, but it helps me only with finally block. I still cannot use it inside the catch.

Upvotes: 2

Views: 235

Answers (3)

Joop Eggen
Joop Eggen

Reputation: 109557

First step: Handling the exception

You evidently want the exception handled before some close. Then you need inside a try-with-resources to handle the exception.

/** throws RuntimeException */
void process(Callable<Void> work, Consumer<Exception> onFail) {
    try {
        work.call();
    } catch (Exception e) {
        onFail(e);
    }
}

try (SomeService service = CreateService().andOpenTransaction()) {
    process(() -> service.doSomeMessyThingsInsideDB(),
            e -> {
                service.rollbackTransaction();
                throw new IllegalStateException(e);
            });
}

This is not very satisfactory, but again also integrating the AutoCloseable, might give too few use-cases.

Second step: with AutoCloseable

<SV extends AutoCloseable> void processAutoClosing(Supplier<SV> serviceFactory,
                                               Callable<Void> work, Consumer<Exception> onFail) {
try (SV service = serviceFactory.get()) {
    process(work, onFail);
}

}

processAutoClosing(...);

Upvotes: 1

Lino
Lino

Reputation: 19926

Well you could define your own interface, and then some static runner method:

public interface ErrorHandlingCloseable extends AutoCloseable {
     void run() throws Exception;
     void onError(Exception e);

     static void execute(ErrorHandlingClosable ehc) throws Exception {
         try(ErrorHandlingClosable temp = ehc) {
             ehc.run();
         } catch(Exception e) {
             ehc.onError(e);
             throw e;
         }
     }
}

Which you then could then call like this:

SomeService service = CreateService().andOpenTransaction();
ErrorHandlingCloseable.execute(new ErrorHandlingCloseable() {
    public void run() throws Exception { service.doSomeMessyThingsInsideDB(); }
    public void onError(Exception e) { service.rollbackTransaction(); }
    public void close() throws Exception { service.closeConnection(); }
});

But you see, it's still messy.

You could even implement this interface in your SomeService but then you're restricted that the run() method will always call doSomeMessyThingsInsideDB().


Another way but still similar would be to use Java8 and create a helper functional interface:

public interface ThrowingRunnable {
   void run() throws Exception;
}

And then a static method somewhere:

public static void execute(ThrowingRunnable action,
                           ThrowingRunnable onCatch,
                           ThrowingRunnable onFinally) throws Exception {
   try(AutoCloseable ao = onFinally) {
       action.run();
   } catch(Exception e) {
       onCatch.run();
       throw e;
   }
}

The interesting part is probably this: try(AutoCloseable ao = onFinally), which "registers" your onFinally method to be called when finally is reached.

This could then be called like this:

execute(
    service::doSomeMessyThingsInsideDB, 
    service::rollbackTransaction, 
    service::closeConnection
);

Upvotes: 1

Piotr Niewinski
Piotr Niewinski

Reputation: 1347

You said you are familiar with AutoCloseable, but you don't use it. Have you considered using try-with-resources statement?

Your code can be simplified to:

try (SomeService service = CreateService().andOpenTransaction()) {
    service.doSomeMessyThingsInsideDB();
} catch(exception e){
    service.rollbackTransaction();
    throw e;
}

Oracle has great doc for that, including examples.

Note: A try-with-resources statement can have catch and finally blocks just like an ordinary try statement. In a try-with-resources statement, any catch or finally block is run after the resources declared have been closed.

Answering your question, this is as simple as it can get. If your class doesn't implement Closeable then you can either implement it or use finally.

Upvotes: 1

Related Questions