1 | |
package org.liquibase.maven.plugins; |
2 | |
|
3 | |
import java.io.IOException; |
4 | |
import java.io.InputStream; |
5 | |
import java.lang.reflect.Field; |
6 | |
import java.net.MalformedURLException; |
7 | |
import java.util.*; |
8 | |
|
9 | |
import liquibase.*; |
10 | |
import liquibase.integration.commandline.CommandLineUtils; |
11 | |
import liquibase.logging.LogFactory; |
12 | |
import liquibase.resource.CompositeResourceAccessor; |
13 | |
import liquibase.resource.ResourceAccessor; |
14 | |
import liquibase.resource.FileSystemResourceAccessor; |
15 | |
import liquibase.database.Database; |
16 | |
import liquibase.exception.*; |
17 | |
import liquibase.util.ui.UIFactory; |
18 | |
import org.apache.maven.artifact.manager.WagonManager; |
19 | |
import org.apache.maven.plugin.*; |
20 | |
import org.apache.maven.project.MavenProject; |
21 | |
import org.apache.maven.wagon.authentication.AuthenticationInfo; |
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
|
31 | |
|
32 | 10 | public abstract class AbstractLiquibaseMojo extends AbstractMojo { |
33 | |
|
34 | |
|
35 | |
|
36 | |
|
37 | |
private static final String DEFAULT_FIELD_SUFFIX = "Default"; |
38 | |
|
39 | |
|
40 | |
|
41 | |
|
42 | |
|
43 | |
|
44 | |
protected String driver; |
45 | |
|
46 | |
|
47 | |
|
48 | |
|
49 | |
|
50 | |
|
51 | |
protected String url; |
52 | |
|
53 | |
|
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | |
|
59 | |
|
60 | |
protected WagonManager wagonManager; |
61 | |
|
62 | |
|
63 | |
|
64 | |
|
65 | |
|
66 | |
private String server; |
67 | |
|
68 | |
|
69 | |
|
70 | |
|
71 | |
|
72 | |
|
73 | |
protected String username; |
74 | |
|
75 | |
|
76 | |
|
77 | |
|
78 | |
|
79 | |
|
80 | |
protected String password; |
81 | |
|
82 | |
|
83 | |
|
84 | |
|
85 | |
|
86 | |
|
87 | |
|
88 | |
|
89 | |
protected boolean emptyPassword; |
90 | |
|
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
|
96 | |
protected String defaultSchemaName; |
97 | |
|
98 | |
|
99 | |
|
100 | |
|
101 | |
|
102 | |
|
103 | |
protected String databaseClass; |
104 | |
|
105 | |
|
106 | |
|
107 | |
|
108 | |
|
109 | |
|
110 | |
|
111 | |
protected boolean promptOnNonLocalDatabase; |
112 | |
|
113 | |
|
114 | |
|
115 | |
|
116 | |
|
117 | |
|
118 | |
|
119 | |
protected boolean includeArtifact; |
120 | |
|
121 | |
|
122 | |
|
123 | |
|
124 | |
|
125 | |
|
126 | |
|
127 | |
protected boolean includeTestOutputDirectory; |
128 | |
|
129 | |
|
130 | |
|
131 | |
|
132 | |
|
133 | |
|
134 | |
|
135 | |
protected boolean verbose; |
136 | |
|
137 | |
|
138 | |
|
139 | |
|
140 | |
|
141 | |
|
142 | |
|
143 | |
|
144 | |
protected String logging; |
145 | |
|
146 | |
|
147 | |
|
148 | |
|
149 | |
|
150 | |
|
151 | |
protected String propertyFile; |
152 | |
|
153 | |
|
154 | |
|
155 | |
|
156 | |
|
157 | |
|
158 | |
|
159 | |
|
160 | |
protected boolean propertyFileWillOverride; |
161 | |
|
162 | |
|
163 | |
|
164 | |
|
165 | |
|
166 | |
|
167 | |
protected boolean clearCheckSums; |
168 | |
|
169 | |
|
170 | |
|
171 | |
|
172 | |
|
173 | |
|
174 | |
protected Properties systemProperties; |
175 | |
|
176 | |
|
177 | |
|
178 | |
|
179 | |
|
180 | |
|
181 | |
|
182 | |
|
183 | |
protected MavenProject project; |
184 | |
|
185 | |
|
186 | |
|
187 | |
|
188 | |
private Liquibase liquibase; |
189 | |
|
190 | |
|
191 | |
|
192 | |
|
193 | |
|
194 | |
|
195 | |
private Properties expressionVars; |
196 | |
|
197 | |
|
198 | |
|
199 | |
|
200 | |
|
201 | |
|
202 | |
protected boolean skip; |
203 | |
|
204 | |
|
205 | |
|
206 | |
|
207 | |
|
208 | |
|
209 | |
private Map expressionVariables; |
210 | |
|
211 | |
public void execute() throws MojoExecutionException, MojoFailureException { |
212 | 0 | getLog().info(MavenUtils.LOG_SEPARATOR); |
213 | |
|
214 | 0 | if (server != null) { |
215 | 0 | AuthenticationInfo info = wagonManager.getAuthenticationInfo(server); |
216 | 0 | if (info != null) { |
217 | 0 | username = info.getUserName(); |
218 | 0 | password = info.getPassword(); |
219 | |
} |
220 | |
} |
221 | |
|
222 | 0 | String shouldRunProperty = System.getProperty(Liquibase.SHOULD_RUN_SYSTEM_PROPERTY); |
223 | 0 | if (shouldRunProperty != null && !Boolean.valueOf(shouldRunProperty)) { |
224 | 0 | getLog().warn( |
225 | |
"Liquibase did not run because '" + Liquibase.SHOULD_RUN_SYSTEM_PROPERTY |
226 | |
+ "' system property was set to false"); |
227 | 0 | return; |
228 | |
} |
229 | |
|
230 | 0 | if (skip) { |
231 | 0 | getLog().warn("Liquibase skipped due to maven configuration"); |
232 | 0 | return; |
233 | |
} |
234 | |
|
235 | 0 | processSystemProperties(); |
236 | |
|
237 | 0 | ClassLoader artifactClassLoader = getMavenArtifactClassLoader(); |
238 | 0 | configureFieldsAndValues(getFileOpener(artifactClassLoader)); |
239 | |
|
240 | |
try { |
241 | 0 | LogFactory.setLoggingLevel(logging); |
242 | 0 | } catch (IllegalArgumentException e) { |
243 | 0 | throw new MojoExecutionException("Failed to set logging level: " + e.getMessage(), e); |
244 | 0 | } |
245 | |
|
246 | |
|
247 | 0 | displayMojoSettings(); |
248 | |
|
249 | |
|
250 | 0 | checkRequiredParametersAreSpecified(); |
251 | |
|
252 | 0 | Database database = null; |
253 | |
try { |
254 | 0 | String dbPassword = emptyPassword || password == null ? "" : password; |
255 | 0 | database = CommandLineUtils.createDatabaseObject(artifactClassLoader, url, username, dbPassword, driver, |
256 | |
defaultSchemaName, databaseClass, null); |
257 | 0 | liquibase = createLiquibase(getFileOpener(artifactClassLoader), database); |
258 | |
|
259 | 0 | getLog().debug("expressionVars = " + String.valueOf(expressionVars)); |
260 | |
|
261 | 0 | if (expressionVars != null) { |
262 | 0 | for (Map.Entry<Object, Object> var : expressionVars.entrySet()) { |
263 | 0 | this.liquibase.setChangeLogParameter(var.getKey().toString(), var.getValue()); |
264 | |
} |
265 | |
} |
266 | |
|
267 | 0 | getLog().debug("expressionVariables = " + String.valueOf(expressionVariables)); |
268 | 0 | if (expressionVariables != null) { |
269 | 0 | for (Map.Entry var : (Set<Map.Entry>) expressionVariables.entrySet()) { |
270 | 0 | if (var.getValue() != null) { |
271 | 0 | this.liquibase.setChangeLogParameter(var.getKey().toString(), var.getValue()); |
272 | |
} |
273 | |
} |
274 | |
} |
275 | |
|
276 | 0 | if (clearCheckSums) { |
277 | 0 | getLog().info("Clearing the Liquibase Checksums on the database"); |
278 | 0 | liquibase.clearCheckSums(); |
279 | |
} |
280 | |
|
281 | 0 | getLog().info("Executing on Database: " + url); |
282 | |
|
283 | 0 | if (isPromptOnNonLocalDatabase()) { |
284 | 0 | if (!liquibase.isSafeToRunMigration()) { |
285 | 0 | if (UIFactory.getInstance().getFacade().promptForNonLocalDatabase(liquibase.getDatabase())) { |
286 | 0 | throw new LiquibaseException("User decided not to run against non-local database"); |
287 | |
} |
288 | |
} |
289 | |
} |
290 | |
|
291 | 0 | performLiquibaseTask(liquibase); |
292 | 0 | } catch (LiquibaseException e) { |
293 | 0 | cleanup(database); |
294 | 0 | throw new MojoExecutionException("Error setting up or running Liquibase: " + e.getMessage(), e); |
295 | 0 | } |
296 | |
|
297 | 0 | cleanup(database); |
298 | 0 | getLog().info(MavenUtils.LOG_SEPARATOR); |
299 | 0 | getLog().info(""); |
300 | 0 | } |
301 | |
|
302 | |
protected Liquibase getLiquibase() { |
303 | 0 | return liquibase; |
304 | |
} |
305 | |
|
306 | |
protected abstract void performLiquibaseTask(Liquibase liquibase) throws LiquibaseException; |
307 | |
|
308 | |
protected boolean isPromptOnNonLocalDatabase() { |
309 | 0 | return promptOnNonLocalDatabase; |
310 | |
} |
311 | |
|
312 | |
private void displayMojoSettings() { |
313 | 0 | if (verbose) { |
314 | 0 | getLog().info("Settings----------------------------"); |
315 | 0 | printSettings(" "); |
316 | 0 | getLog().info(MavenUtils.LOG_SEPARATOR); |
317 | |
} |
318 | 0 | } |
319 | |
|
320 | |
protected Liquibase createLiquibase(ResourceAccessor fo, Database db) throws MojoExecutionException { |
321 | |
try { |
322 | 0 | return new Liquibase("", fo, db); |
323 | 0 | } catch (LiquibaseException ex) { |
324 | 0 | throw new MojoExecutionException("Error creating liquibase: " + ex.getMessage(), ex); |
325 | |
} |
326 | |
} |
327 | |
|
328 | |
public void configureFieldsAndValues(ResourceAccessor fo) throws MojoExecutionException, MojoFailureException { |
329 | |
|
330 | |
|
331 | 6 | if (propertyFile != null) { |
332 | 3 | getLog().info("Parsing Liquibase Properties File"); |
333 | 3 | getLog().info(" File: " + propertyFile); |
334 | |
try { |
335 | 3 | InputStream is = fo.getResourceAsStream(propertyFile); |
336 | 3 | if (is == null) { |
337 | 0 | throw new MojoFailureException("Failed to resolve the properties file."); |
338 | |
} |
339 | 3 | parsePropertiesFile(is); |
340 | 3 | getLog().info(MavenUtils.LOG_SEPARATOR); |
341 | 0 | } catch (IOException e) { |
342 | 0 | throw new MojoExecutionException("Failed to resolve properties file", e); |
343 | 3 | } |
344 | |
} |
345 | 6 | } |
346 | |
|
347 | |
protected ClassLoader getMavenArtifactClassLoader() throws MojoExecutionException { |
348 | |
try { |
349 | 0 | return MavenUtils.getArtifactClassloader(project, includeArtifact, includeTestOutputDirectory, getClass(), |
350 | |
getLog(), verbose); |
351 | 0 | } catch (MalformedURLException e) { |
352 | 0 | throw new MojoExecutionException("Failed to create artifact classloader", e); |
353 | |
} |
354 | |
} |
355 | |
|
356 | |
protected ResourceAccessor getFileOpener(ClassLoader cl) { |
357 | 0 | ResourceAccessor mFO = new MavenResourceAccessor(cl); |
358 | 0 | ResourceAccessor fsFO = new FileSystemResourceAccessor(project.getBasedir().getAbsolutePath()); |
359 | 0 | return new CompositeResourceAccessor(mFO, fsFO); |
360 | |
} |
361 | |
|
362 | |
|
363 | |
|
364 | |
|
365 | |
|
366 | |
|
367 | |
|
368 | |
|
369 | |
protected void checkRequiredParametersAreSpecified() throws MojoFailureException { |
370 | 0 | if (driver == null) { |
371 | 0 | throw new MojoFailureException("The driver has not been specified either as a " |
372 | |
+ "parameter or in a properties file."); |
373 | 0 | } else if (url == null) { |
374 | 0 | throw new MojoFailureException("The database URL has not been specified either as " |
375 | |
+ "a parameter or in a properties file."); |
376 | |
} |
377 | |
|
378 | 0 | if (password != null && emptyPassword) { |
379 | 0 | throw new MojoFailureException("A password cannot be present and the empty " |
380 | |
+ "password property both be specified."); |
381 | |
} |
382 | 0 | } |
383 | |
|
384 | |
|
385 | |
|
386 | |
|
387 | |
|
388 | |
|
389 | |
|
390 | |
protected void printSettings(String indent) { |
391 | 0 | if (indent == null) { |
392 | 0 | indent = ""; |
393 | |
} |
394 | 0 | getLog().info(indent + "driver: " + driver); |
395 | 0 | getLog().info(indent + "url: " + url); |
396 | 0 | getLog().info(indent + "username: " + username); |
397 | 0 | getLog().info(indent + "password: " + password); |
398 | 0 | getLog().info(indent + "use empty password: " + emptyPassword); |
399 | 0 | getLog().info(indent + "properties file: " + propertyFile); |
400 | 0 | getLog().info(indent + "properties file will override? " + propertyFileWillOverride); |
401 | 0 | getLog().info(indent + "prompt on non-local database? " + promptOnNonLocalDatabase); |
402 | 0 | getLog().info(indent + "clear checksums? " + clearCheckSums); |
403 | 0 | } |
404 | |
|
405 | |
protected void cleanup(Database db) { |
406 | |
|
407 | 0 | if (getLiquibase() != null) { |
408 | |
try { |
409 | 0 | getLiquibase().forceReleaseLocks(); |
410 | 0 | } catch (LiquibaseException e) { |
411 | 0 | getLog().error(e.getMessage(), e); |
412 | 0 | } |
413 | |
} |
414 | |
|
415 | |
|
416 | 0 | if (db != null) { |
417 | |
try { |
418 | 0 | db.rollback(); |
419 | 0 | db.close(); |
420 | 0 | } catch (DatabaseException e) { |
421 | 0 | getLog().error("Failed to close open connection to database.", e); |
422 | 0 | } |
423 | |
} |
424 | 0 | } |
425 | |
|
426 | |
|
427 | |
|
428 | |
|
429 | |
|
430 | |
|
431 | |
|
432 | |
|
433 | |
|
434 | |
protected void parsePropertiesFile(InputStream propertiesInputStream) throws MojoExecutionException { |
435 | 3 | if (propertiesInputStream == null) { |
436 | 0 | throw new MojoExecutionException("Properties file InputStream is null."); |
437 | |
} |
438 | 3 | Properties props = new Properties(); |
439 | |
try { |
440 | 3 | props.load(propertiesInputStream); |
441 | 0 | } catch (IOException e) { |
442 | 0 | throw new MojoExecutionException("Could not load the properties Liquibase file", e); |
443 | 3 | } |
444 | |
|
445 | 3 | for (Iterator it = props.keySet().iterator(); it.hasNext();) { |
446 | 13 | String key = null; |
447 | |
try { |
448 | 13 | key = (String) it.next(); |
449 | 13 | Field field = MavenUtils.getDeclaredField(this.getClass(), key); |
450 | |
|
451 | 13 | if (propertyFileWillOverride) { |
452 | 9 | getLog().debug(" properties file setting value: " + field.getName()); |
453 | 9 | setFieldValue(field, props.get(key).toString()); |
454 | |
} else { |
455 | 4 | if (!isCurrentFieldValueSpecified(field)) { |
456 | 1 | getLog().debug(" properties file setting value: " + field.getName()); |
457 | 1 | setFieldValue(field, props.get(key).toString()); |
458 | |
} |
459 | |
} |
460 | 0 | } catch (Exception e) { |
461 | 0 | getLog().info(" '" + key + "' in properties file is not being used by this " + "task."); |
462 | 13 | } |
463 | 13 | } |
464 | 3 | } |
465 | |
|
466 | |
|
467 | |
|
468 | |
|
469 | |
|
470 | |
|
471 | |
|
472 | |
|
473 | |
|
474 | |
private boolean isCurrentFieldValueSpecified(Field f) throws IllegalAccessException { |
475 | 4 | Object currentValue = f.get(this); |
476 | 4 | if (currentValue == null) { |
477 | 1 | return false; |
478 | |
} |
479 | |
|
480 | 3 | Object defaultValue = getDefaultValue(f); |
481 | 3 | if (defaultValue == null) { |
482 | 3 | return currentValue != null; |
483 | |
} else { |
484 | |
|
485 | |
|
486 | 0 | return !defaultValue.equals(f.get(this)); |
487 | |
} |
488 | |
} |
489 | |
|
490 | |
private Object getDefaultValue(Field field) throws IllegalAccessException { |
491 | 3 | List<Field> allFields = new ArrayList<Field>(); |
492 | 3 | allFields.addAll(Arrays.asList(getClass().getDeclaredFields())); |
493 | 3 | allFields.addAll(Arrays.asList(AbstractLiquibaseMojo.class.getDeclaredFields())); |
494 | |
|
495 | 3 | for (Field f : allFields) { |
496 | 75 | if (f.getName().equals(field.getName() + DEFAULT_FIELD_SUFFIX)) { |
497 | 0 | f.setAccessible(true); |
498 | 0 | return f.get(this); |
499 | |
} |
500 | |
} |
501 | 3 | return null; |
502 | |
} |
503 | |
|
504 | |
private void setFieldValue(Field field, String value) throws IllegalAccessException { |
505 | 10 | if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) { |
506 | 0 | field.set(this, Boolean.valueOf(value)); |
507 | |
} else { |
508 | 10 | field.set(this, value); |
509 | |
} |
510 | 10 | } |
511 | |
|
512 | |
@SuppressWarnings("unchecked") |
513 | |
private void processSystemProperties() { |
514 | 0 | if (systemProperties == null) { |
515 | 0 | systemProperties = new Properties(); |
516 | |
} |
517 | |
|
518 | 0 | Iterator iter = systemProperties.keySet().iterator(); |
519 | 0 | while (iter.hasNext()) { |
520 | 0 | String key = (String) iter.next(); |
521 | 0 | String value = systemProperties.getProperty(key); |
522 | 0 | System.setProperty(key, value); |
523 | 0 | } |
524 | 0 | } |
525 | |
|
526 | |
} |