001    /*
002     * The MIT License
003     *
004     * Copyright (c) 2012, Ninja Squad
005     *
006     * Permission is hereby granted, free of charge, to any person obtaining a copy
007     * of this software and associated documentation files (the "Software"), to deal
008     * in the Software without restriction, including without limitation the rights
009     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010     * copies of the Software, and to permit persons to whom the Software is
011     * furnished to do so, subject to the following conditions:
012     *
013     * The above copyright notice and this permission notice shall be included in
014     * all copies or substantial portions of the Software.
015     *
016     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022     * THE SOFTWARE.
023     */
024    
025    package com.ninja_squad.dbsetup;
026    
027    import java.sql.Connection;
028    import java.sql.SQLException;
029    
030    import javax.annotation.Nonnull;
031    
032    import com.ninja_squad.dbsetup.bind.BinderConfiguration;
033    import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration;
034    import com.ninja_squad.dbsetup.destination.Destination;
035    import com.ninja_squad.dbsetup.operation.Operation;
036    import com.ninja_squad.dbsetup.util.Preconditions;
037    
038    /**
039     * Allows executing a sequence of database operations. This object is reusable, and can thus be used several times
040     * to launch the same sequence of database operations. Here's a typical usage scenario in a unit test:
041     * <pre>
042     * &#064;Before
043     * public void setUp() throws Exception {
044     *     Operation operation =
045     *         Operations.sequenceOf(
046     *             CommonOperations.DELETE_ALL,
047     *             CommonOperations.INSERT_REFERENCE_DATA,
048     *             Operations.insertInto("CLIENT")
049     *                       .columns("CLIENT_ID", "FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH", "COUNTRY_ID")
050     *                       .values(1L, "John", "Doe", "1975-07-19", 1L)
051     *                       .values(2L, "Jack", "Smith", "1969-08-22", 2L)
052     *                       .build());
053     *     DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource), operation);
054     *     dbSetup.launch();
055     * }
056     * </pre>
057     * In the above code, <code>CommonOperations.DELETE_ALL</code> and <code>CommonOperations.INSERT_REFERENCE_DATA</code>
058     * are operations shared by multiple test classes.
059     * <p>
060     * Note that, to speed up test executions, a {@link DbSetupTracker} can be used, at the price of a slightly
061     * bigger complexity.
062     * @author JB Nizet
063     */
064    public final class DbSetup {
065        private final Destination destination;
066        private final Operation operation;
067        private final BinderConfiguration binderConfiguration;
068    
069        /**
070         * Constructor which uses the {@link DefaultBinderConfiguration#INSTANCE default binder configuration}.
071         * @param destination the destination of the sequence of database operations
072         * @param operation the operation to execute (most of the time, an instance of
073         * {@link com.ninja_squad.dbsetup.operation.CompositeOperation}
074         */
075        public DbSetup(@Nonnull Destination destination, @Nonnull Operation operation) {
076            this(destination, operation, DefaultBinderConfiguration.INSTANCE);
077        }
078    
079        /**
080         * Constructor allowing to use a custom {@link BinderConfiguration}.
081         * @param destination the destination of the sequence of database operations
082         * @param operation the operation to execute (most of the time, an instance of
083         * {@link com.ninja_squad.dbsetup.operation.CompositeOperation}
084         * @param binderConfiguration the binder configuration to use.
085         */
086        public DbSetup(@Nonnull Destination destination,
087                       @Nonnull Operation operation,
088                       @Nonnull BinderConfiguration binderConfiguration) {
089            Preconditions.checkNotNull(destination, "destination may not be null");
090            Preconditions.checkNotNull(operation, "operation may not be null");
091            Preconditions.checkNotNull(binderConfiguration, "binderConfiguration may not be null");
092    
093            this.destination = destination;
094            this.operation = operation;
095            this.binderConfiguration = binderConfiguration;
096        }
097    
098        /**
099         * Executes the sequence of operations. All the operations use the same connection, and are grouped
100         * in a single transaction. The transaction is rolled back if any exception occurs.
101         */
102        public void launch() {
103            try {
104                Connection connection = destination.getConnection();
105                try {
106                    connection.setAutoCommit(false);
107                    operation.execute(connection, binderConfiguration);
108                    connection.commit();
109                }
110                catch (SQLException e) {
111                    connection.rollback();
112                    throw e;
113                }
114                catch (RuntimeException e) {
115                    connection.rollback();
116                    throw e;
117                }
118                finally {
119                    connection.close();
120                }
121            }
122            catch (SQLException e) {
123                throw new DbSetupRuntimeException(e);
124            }
125        }
126    
127        @Override
128        public String toString() {
129            return "DbSetup [destination="
130                   + destination
131                   + ", operation="
132                   + operation
133                   + ", binderConfiguration="
134                   + binderConfiguration
135                   + "]";
136        }
137    
138        @Override
139        public int hashCode() {
140            final int prime = 31;
141            int result = 1;
142            result = prime * result + binderConfiguration.hashCode();
143            result = prime * result + destination.hashCode();
144            result = prime * result + operation.hashCode();
145            return result;
146        }
147    
148        @Override
149        public boolean equals(Object obj) {
150            if (this == obj) {
151                return true;
152            }
153            if (obj == null) {
154                return false;
155            }
156            if (getClass() != obj.getClass()) {
157                return false;
158            }
159            DbSetup other = (DbSetup) obj;
160            return binderConfiguration.equals(other.binderConfiguration)
161                   && destination.equals(other.destination)
162                   && operation.equals(other.operation);
163        }
164    }