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 * @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 }