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
025package com.ninja_squad.dbsetup;
026
027import java.sql.Connection;
028import java.sql.SQLException;
029
030import javax.annotation.Nonnull;
031
032import com.ninja_squad.dbsetup.bind.BinderConfiguration;
033import com.ninja_squad.dbsetup.bind.DefaultBinderConfiguration;
034import com.ninja_squad.dbsetup.destination.Destination;
035import com.ninja_squad.dbsetup.operation.Operation;
036import 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 */
064public 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}