001 package liquibase.precondition.core;
002
003 import liquibase.util.StringUtils;
004 import liquibase.util.StreamUtil;
005 import liquibase.database.Database;
006 import liquibase.changelog.DatabaseChangeLog;
007 import liquibase.changelog.ChangeSet;
008 import liquibase.exception.PreconditionFailedException;
009 import liquibase.exception.PreconditionErrorException;
010 import liquibase.exception.UnexpectedLiquibaseException;
011 import liquibase.exception.ValidationFailedException;
012 import liquibase.executor.Executor;
013 import liquibase.executor.ExecutorService;
014 import liquibase.logging.LogFactory;
015
016 import java.util.ArrayList;
017 import java.util.List;
018
019 public class PreconditionContainer extends AndPrecondition {
020
021 public enum FailOption {
022 HALT("HALT"), CONTINUE("CONTINUE"), MARK_RAN("MARK_RAN"), WARN("WARN");
023
024 String key;
025
026 FailOption(String key) {
027 this.key = key;
028 }
029
030 @Override
031 public String toString() {
032 return key;
033 }
034 }
035
036 public enum ErrorOption {
037 HALT("HALT"), CONTINUE("CONTINUE"), MARK_RAN("MARK_RAN"), WARN("WARN");
038
039 String key;
040
041 ErrorOption(String key) {
042 this.key = key;
043 }
044
045 @Override
046 public String toString() {
047 return key;
048 }
049 }
050
051 public enum OnSqlOutputOption {
052 IGNORE("IGNORE"), TEST("TEST"), FAIL("FAIL");
053
054 String key;
055
056 OnSqlOutputOption(String key) {
057 this.key = key;
058 }
059
060 @Override
061 public String toString() {
062 return key;
063 }
064 }
065
066 private FailOption onFail = FailOption.HALT;
067 private ErrorOption onError = ErrorOption.HALT;
068 private OnSqlOutputOption onSqlOutput = OnSqlOutputOption.IGNORE;
069 private String onFailMessage;
070 private String onErrorMessage;
071
072 public FailOption getOnFail() {
073 return onFail;
074 }
075
076 public void setOnFail(String onFail) {
077 if (onFail == null) {
078 this.onFail = FailOption.HALT;
079 } else {
080 for (FailOption option : FailOption.values()) {
081 if (option.key.equalsIgnoreCase(onFail)) {
082 this.onFail = option;
083 return;
084 }
085 }
086 List<String> possibleOptions = new ArrayList<String>();
087 for (FailOption option : FailOption.values()) {
088 possibleOptions.add(option.key);
089 }
090 throw new RuntimeException("Unknown onFail attribute value '" + onFail + "'. Possible values: "
091 + StringUtils.join(possibleOptions, ", "));
092 }
093 }
094
095 public ErrorOption getOnError() {
096 return onError;
097 }
098
099 public void setOnError(String onError) {
100 if (onError == null) {
101 this.onError = ErrorOption.HALT;
102 } else {
103 for (ErrorOption option : ErrorOption.values()) {
104 if (option.key.equalsIgnoreCase(onError)) {
105 this.onError = option;
106 return;
107 }
108 }
109 List<String> possibleOptions = new ArrayList<String>();
110 for (ErrorOption option : ErrorOption.values()) {
111 possibleOptions.add(option.key);
112 }
113 throw new RuntimeException("Unknown onError attribute value '" + onError + "'. Possible values: "
114 + StringUtils.join(possibleOptions, ", "));
115 }
116 }
117
118 public OnSqlOutputOption getOnSqlOutput() {
119 return onSqlOutput;
120 }
121
122 public void setOnSqlOutput(String onSqlOutput) {
123 if (onSqlOutput == null) {
124 setOnSqlOutput((OnSqlOutputOption) null);
125 return;
126 }
127
128 for (OnSqlOutputOption option : OnSqlOutputOption.values()) {
129 if (option.key.equalsIgnoreCase(onSqlOutput)) {
130 setOnSqlOutput(option);
131 return;
132 }
133 }
134 List<String> possibleOptions = new ArrayList<String>();
135 for (OnSqlOutputOption option : OnSqlOutputOption.values()) {
136 possibleOptions.add(option.key);
137 }
138 throw new RuntimeException("Unknown onSqlOutput attribute value '" + onSqlOutput + "'. Possible values: "
139 + StringUtils.join(possibleOptions, ", "));
140 }
141
142 public void setOnSqlOutput(OnSqlOutputOption onSqlOutput) {
143 if (onSqlOutput == null) {
144 this.onSqlOutput = OnSqlOutputOption.IGNORE;
145 } else {
146 this.onSqlOutput = onSqlOutput;
147 }
148 }
149
150 public String getOnFailMessage() {
151 return onFailMessage;
152 }
153
154 public void setOnFailMessage(String onFailMessage) {
155 this.onFailMessage = onFailMessage;
156 }
157
158 public String getOnErrorMessage() {
159 return onErrorMessage;
160 }
161
162 public void setOnErrorMessage(String onErrorMessage) {
163 this.onErrorMessage = onErrorMessage;
164 }
165
166 @Override
167 public void check(Database database, DatabaseChangeLog changeLog, ChangeSet changeSet)
168 throws PreconditionFailedException, PreconditionErrorException {
169 String ranOn = String.valueOf(changeLog);
170 if (changeSet != null) {
171 ranOn = String.valueOf(changeSet);
172 }
173
174 Executor executor = ExecutorService.getInstance().getExecutor(database);
175 try {
176 // Three cases for preConditions onUpdateSQL:
177 // 1. TEST: preConditions should be run, as in regular update mode
178 // 2. FAIL: the preConditions should fail if there are any
179 // 3. IGNORE: act as if preConditions don't exist
180 boolean testPrecondition = false;
181 if (executor.updatesDatabase()) {
182 testPrecondition = true;
183 } else {
184 if (this.getOnSqlOutput().equals(PreconditionContainer.OnSqlOutputOption.TEST)) {
185 testPrecondition = true;
186 } else if (this.getOnSqlOutput().equals(PreconditionContainer.OnSqlOutputOption.FAIL)) {
187 throw new PreconditionFailedException(
188 "Unexpected precondition in updateSQL mode with onUpdateSQL value: "
189 + this.getOnSqlOutput(), changeLog, this);
190 } else if (this.getOnSqlOutput().equals(PreconditionContainer.OnSqlOutputOption.IGNORE)) {
191 testPrecondition = false;
192 }
193 }
194
195 if (testPrecondition) {
196 super.check(database, changeLog, changeSet);
197 }
198 } catch (PreconditionFailedException e) {
199 StringBuffer message = new StringBuffer();
200 message.append(" ").append(e.getFailedPreconditions().size()).append(" preconditions failed")
201 .append(StreamUtil.getLineSeparator());
202 for (FailedPrecondition invalid : e.getFailedPreconditions()) {
203 message.append(" ").append(invalid.toString());
204 message.append(StreamUtil.getLineSeparator());
205 }
206
207 if (getOnFailMessage() != null) {
208 message = new StringBuffer(getOnFailMessage());
209 }
210 if (this.getOnFail().equals(PreconditionContainer.FailOption.WARN)) {
211 LogFactory.getLogger().info(
212 "Executing: " + ranOn + " despite precondition failure due to onFail='WARN':\n " + message);
213 } else {
214 if (getOnFailMessage() == null) {
215 throw e;
216 } else {
217 throw new PreconditionFailedException(getOnFailMessage(), changeLog, this);
218 }
219 }
220 } catch (PreconditionErrorException e) {
221 StringBuffer message = new StringBuffer();
222 message.append(" ").append(e.getErrorPreconditions().size()).append(" preconditions failed")
223 .append(StreamUtil.getLineSeparator());
224 for (ErrorPrecondition invalid : e.getErrorPreconditions()) {
225 message.append(" ").append(invalid.toString());
226 message.append(StreamUtil.getLineSeparator());
227 }
228
229 if (this.getOnError().equals(PreconditionContainer.ErrorOption.CONTINUE)) {
230 LogFactory.getLogger().info(
231 "Continuing past: " + toString() + " despite precondition error:\n " + message);
232 } else if (this.getOnError().equals(PreconditionContainer.ErrorOption.WARN)) {
233 LogFactory.getLogger().warning(
234 "Continuing past: " + toString() + " despite precondition error:\n " + message);
235 } else {
236 if (getOnErrorMessage() == null) {
237 throw e;
238 } else {
239 throw new PreconditionErrorException(getOnErrorMessage(), e.getErrorPreconditions());
240 }
241 }
242 }
243 }
244 }