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 }