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    }