001    package fj.control.db;
002    
003    import fj.Unit;
004    
005    import java.sql.Connection;
006    import java.sql.DriverManager;
007    import java.sql.SQLException;
008    
009    /**
010     * Performs database I/O, in order to read or write the database state.
011     */
012    public final class DbState {
013      private final Connector pc;
014      private final DB<Unit> terminal;
015    
016      private DbState(final Connector pc, final DB<Unit> terminal) {
017        this.pc = pc;
018        this.terminal = terminal;
019      }
020    
021      /**
022       * A simple connector (the default) that gets connections to the given database URL from the driver manager.
023       *
024       * @param url The database URL to connect to.
025       * @return A connector that generates connections to the given database.
026       */
027      public static Connector driverManager(final String url) {
028        return new Connector() {
029          public Connection connect() throws SQLException {
030            return DriverManager.getConnection(url);
031          }
032        };
033      }
034    
035      /**
036       * Creates a database state reader given a connection URL.
037       *
038       * @param url The connection URL to the database.
039       * @return A database state reader that reads the given database.
040       */
041      public static DbState reader(final String url) {
042        return new DbState(driverManager(url), rollback);
043      }
044    
045      /**
046       * Creates a database state writer given a connection URL.
047       *
048       * @param url The connection URL to the database.
049       * @return A database state writer that writes the given database.
050       */
051      public static DbState writer(final String url) {
052        return new DbState(driverManager(url), commit);
053      }
054    
055      /**
056       * Returns a new reader that reads the database via the given Connector.
057       *
058       * @param pc A connector with which to generate database connections.
059       * @return A new reader that reads the database via the given Connector.
060       */
061      public static DbState reader(final Connector pc) {
062        return new DbState(pc, rollback);
063      }
064    
065      /**
066       * Returns a new writer that writes the database via the given Connector.
067       *
068       * @param pc A connector with which to generate database connections.
069       * @return A new writer that writes the database via the given Connector.
070       */
071      public static DbState writer(final Connector pc) {
072        return new DbState(pc, commit);
073      }
074    
075      private static final DB<Unit> rollback = new DB<Unit>() {
076        public Unit run(final Connection c) throws SQLException {
077          c.rollback();
078          return Unit.unit();
079        }
080      };
081    
082      private static final DB<Unit> commit = new DB<Unit>() {
083        public Unit run(final Connection c) throws SQLException {
084          c.commit();
085          return Unit.unit();
086        }
087      };
088    
089      /**
090       * Runs the given database action as a single transaction.
091       *
092       * @param dba A database action to run.
093       * @return The result of running the action against the database.
094       * @throws SQLException in case of a database error.
095       */
096      public <A> A run(final DB<A> dba) throws SQLException {
097        final Connection c = pc.connect();
098        c.setAutoCommit(false);
099        try {
100          final A a = dba.run(c);
101          terminal.run(c);
102          return a;
103        } catch (SQLException e) {
104          c.rollback();
105          throw e;
106        }
107        finally {
108          c.close();
109        }
110      }
111    }