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 }