-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New hooks (issue 106) #113
Changes from all commits
0a08c82
36452d4
96e10cb
fd30ee5
dfbfcd7
493f676
95726ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,13 @@ public Change(BigDecimal id, String appliedTimestamp, String description) { | |
this.description = description; | ||
} | ||
|
||
public Change(BigDecimal id, String appliedTimestamp, String description, String filename) { | ||
this.id = id; | ||
this.appliedTimestamp = appliedTimestamp; | ||
this.description = description; | ||
this.filename = filename; | ||
} | ||
|
||
public BigDecimal getId() { | ||
return id; | ||
} | ||
|
@@ -71,7 +78,8 @@ public void setFilename(String filename) { | |
|
||
@Override | ||
public String toString() { | ||
return id + " " + (appliedTimestamp == null ? " ...pending... " : appliedTimestamp) + " " + description; | ||
String ts = appliedTimestamp == null ? " ...pending... " : appliedTimestamp; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was just easier for me to read |
||
return String.format("%s %s %s %s", id, ts, description, filename); | ||
} | ||
|
||
@Override | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,9 @@ private enum SETTING_KEY { | |
hook_before_down, | ||
hook_before_each_down, | ||
hook_after_each_down, | ||
hook_after_down | ||
hook_after_down, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follows the current design for hooks. Nothing new here just adding code to support "new" |
||
hook_before_new, | ||
hook_after_new | ||
} | ||
|
||
private static final List<String> SETTING_KEYS; | ||
|
@@ -88,6 +90,8 @@ private enum SETTING_KEY { | |
private final String hookBeforeEachDown; | ||
private final String hookAfterEachDown; | ||
private final String hookAfterDown; | ||
private final String hookBeforeNew; | ||
private final String hookAfterNew; | ||
|
||
private final Properties variables = new Properties(); | ||
|
||
|
@@ -113,7 +117,9 @@ public Environment(File file) { | |
this.username = prop.getProperty(SETTING_KEY.username.name()); | ||
this.password = prop.getProperty(SETTING_KEY.password.name()); | ||
|
||
this.hookBeforeUp = prop.getProperty(SETTING_KEY.hook_before_up.name()); | ||
this.hookBeforeNew = prop.getProperty(SETTING_KEY.hook_before_new.name()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follows current design. Importantly, there is no such thing as a |
||
this.hookAfterNew = prop.getProperty(SETTING_KEY.hook_after_new.name()); | ||
|
||
this.hookBeforeEachUp = prop.getProperty(SETTING_KEY.hook_before_each_up.name()); | ||
this.hookAfterEachUp = prop.getProperty(SETTING_KEY.hook_after_each_up.name()); | ||
this.hookAfterUp = prop.getProperty(SETTING_KEY.hook_after_up.name()); | ||
|
@@ -122,6 +128,7 @@ public Environment(File file) { | |
this.hookAfterEachDown = prop.getProperty(SETTING_KEY.hook_after_each_down.name()); | ||
this.hookAfterDown = prop.getProperty(SETTING_KEY.hook_after_down.name()); | ||
|
||
this.hookBeforeUp = prop.getProperty(SETTING_KEY.hook_before_up.name()); | ||
// User defined variables. | ||
Set<Entry<Object, Object>> entries = prop.entrySet(); | ||
for (Entry<Object, Object> entry : entries) { | ||
|
@@ -197,6 +204,14 @@ public String getPassword() { | |
return password; | ||
} | ||
|
||
public String getBeforeNewHook() { | ||
return hookBeforeNew; | ||
} | ||
|
||
public String getAfterNewHook() { | ||
return hookAfterNew; | ||
} | ||
|
||
public String getHookBeforeUp() { | ||
return hookBeforeUp; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
import static org.apache.ibatis.migration.utils.Util.file; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.FileNotFoundException; | ||
import java.io.FileReader; | ||
import java.io.FileWriter; | ||
|
@@ -35,6 +36,7 @@ | |
import java.util.ArrayList; | ||
import java.util.Date; | ||
import java.util.List; | ||
import java.util.Map.Entry; | ||
import java.util.Properties; | ||
import java.util.ServiceLoader; | ||
import java.util.TimeZone; | ||
|
@@ -49,10 +51,13 @@ | |
import org.apache.ibatis.migration.FileMigrationLoaderFactory; | ||
import org.apache.ibatis.migration.MigrationException; | ||
import org.apache.ibatis.migration.MigrationLoader; | ||
import org.apache.ibatis.migration.hook.FileHookScriptFactory; | ||
import org.apache.ibatis.migration.hook.BasicHook; | ||
import org.apache.ibatis.migration.hook.Hook; | ||
import org.apache.ibatis.migration.hook.scripts.FileHookScriptFactory; | ||
import org.apache.ibatis.migration.hook.FileMigrationHook; | ||
import org.apache.ibatis.migration.hook.HookScriptFactory; | ||
import org.apache.ibatis.migration.hook.scripts.HookScriptFactory; | ||
import org.apache.ibatis.migration.hook.MigrationHook; | ||
import org.apache.ibatis.migration.hook.NoOpHook; | ||
import org.apache.ibatis.migration.io.ExternalResources; | ||
import org.apache.ibatis.migration.options.DatabaseOperationOption; | ||
import org.apache.ibatis.migration.options.Options; | ||
|
@@ -106,6 +111,23 @@ public void setPrintStream(PrintStream aPrintStream) { | |
printStream = aPrintStream; | ||
} | ||
|
||
/** | ||
* Use this to define template variables | ||
* @return combined properties of system/environment with 'sys.' and 'env.' appended respectively | ||
*/ | ||
protected Properties getVariables() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The templating system for |
||
Properties variables = environmentProperties(); | ||
for (Entry<Object, Object> sys : System.getProperties().entrySet()) { | ||
if (sys.getValue() != null) | ||
variables.put("sys." + sys.getKey(), sys.getValue()); | ||
} | ||
for (Entry<String, String> env : System.getenv().entrySet()) { | ||
if (env.getValue() != null) | ||
variables.put("env." + env.getKey(), env.getValue()); | ||
} | ||
return variables; | ||
} | ||
|
||
protected boolean paramsEmpty(String... params) { | ||
return params == null || params.length < 1 || params[0] == null || params[0].length() < 1; | ||
} | ||
|
@@ -152,7 +174,7 @@ private String generatePatternedId(String pattern) { | |
} | ||
} | ||
|
||
private String generateTimestampId() { | ||
protected String generateTimestampId() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was changed so that the |
||
final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); | ||
final Date now = new Date(); | ||
dateFormat.setTimeZone(TimeZone.getTimeZone(environment().getTimeZone())); | ||
|
@@ -238,6 +260,17 @@ protected File existingEnvironmentFile() { | |
return envFile; | ||
} | ||
|
||
protected Properties environmentProperties() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a convenience override. |
||
File envFile = existingEnvironmentFile(); | ||
Properties props = new Properties(); | ||
try { | ||
props.load(new FileInputStream(envFile)); | ||
} catch (IOException e) { | ||
throw new MigrationException("Failed to load environment file " + envFile.getAbsolutePath(), e); | ||
} | ||
return props; | ||
} | ||
|
||
protected Environment environment() { | ||
if (environment != null) { | ||
return environment; | ||
|
@@ -315,6 +348,15 @@ protected MigrationLoader getMigrationLoader() { | |
: new FileMigrationLoader(paths.getScriptPath(), env.getScriptCharset(), env.getVariables()); | ||
} | ||
|
||
protected Hook createNewMigrationHook() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Following previous code pattern. |
||
String before = environment().getBeforeNewHook(); | ||
String after = environment().getAfterNewHook(); | ||
if (before == null && after == null) { | ||
return NoOpHook.getInstance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This follows the Null Object Pattern so that null checks don't need to be carried everywhere. |
||
} | ||
return createBasicHook(before, after); | ||
} | ||
|
||
protected MigrationHook createUpHook() { | ||
String before = environment().getHookBeforeUp(); | ||
String beforeEach = environment().getHookBeforeEachUp(); | ||
|
@@ -343,6 +385,11 @@ protected MigrationHook createFileMigrationHook(String before, String beforeEach | |
factory.create(after)); | ||
} | ||
|
||
protected BasicHook createBasicHook(String before, String after) { | ||
HookScriptFactory factory = new FileHookScriptFactory(options.getPaths(), environment(), printStream); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Following previous patterns |
||
return new BasicHook(factory.create(before), factory.create(after)); | ||
} | ||
|
||
protected DatabaseOperationOption getDatabaseOperationOption() { | ||
DatabaseOperationOption option = new DatabaseOperationOption(); | ||
option.setChangelogTable(changelogTable()); | ||
|
@@ -356,4 +403,5 @@ protected DatabaseOperationOption getDatabaseOperationOption() { | |
option.setDelimiter(environment().getDelimiter()); | ||
return option; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,21 @@ | |
*/ | ||
package org.apache.ibatis.migration.commands; | ||
|
||
import java.io.File; | ||
import java.io.FileNotFoundException; | ||
import java.io.FileReader; | ||
import java.io.IOException; | ||
import java.io.Reader; | ||
import java.math.BigDecimal; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Properties; | ||
|
||
import org.apache.ibatis.io.Resources; | ||
import org.apache.ibatis.migration.Change; | ||
import org.apache.ibatis.migration.MigrationException; | ||
import org.apache.ibatis.migration.hook.Hook; | ||
import org.apache.ibatis.migration.options.SelectedOptions; | ||
import org.apache.ibatis.migration.utils.Util; | ||
|
||
public final class NewCommand extends BaseCommand { | ||
|
||
|
@@ -35,35 +44,87 @@ public void execute(String... params) { | |
if (paramsEmpty(params)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, I really had to overhaul this function. In a nutshell, I did the following:
|
||
throw new MigrationException("No description specified for new migration."); | ||
} | ||
String description = params[0]; | ||
Properties variables = new Properties(); | ||
variables.setProperty("description", description); | ||
existingEnvironmentFile(); | ||
String filename = getNextIDAsString() + "_" + description.replace(' ', '_') + ".sql"; | ||
|
||
if (options.getTemplate() != null) { | ||
copyExternalResourceTo(options.getTemplate(), Util.file(paths.getScriptPath(), filename), variables); | ||
} else { | ||
|
||
Hook hook = createNewMigrationHook(); | ||
|
||
Properties variables = getVariables(); | ||
variables.setProperty("description", params[0]); | ||
|
||
|
||
Map<String, Object> hookBindings = createBinding(params); | ||
{ | ||
|
||
Reader templateReader = getTemplateReader(); | ||
Change change = (Change) hookBindings.get("change"); | ||
hook.before(hookBindings); | ||
File changeFile = new File(change.getFilename()); | ||
try { | ||
String customConfiguredTemplate = getPropertyOption(CUSTOM_NEW_COMMAND_TEMPLATE_PROPERTY); | ||
if (customConfiguredTemplate != null) { | ||
copyExternalResourceTo(migrationsHome() + "/" + customConfiguredTemplate, | ||
Util.file(paths.getScriptPath(), filename), variables); | ||
} else { | ||
copyDefaultTemplate(variables, filename); | ||
} | ||
} catch (FileNotFoundException e) { | ||
printStream | ||
.append("Your migrations configuration did not find your custom template. Using the default template."); | ||
copyDefaultTemplate(variables, filename); | ||
copyTemplate(templateReader, changeFile, variables); | ||
} catch (IOException e) { | ||
throw new MigrationException("Unable to create template file " + changeFile.getAbsolutePath()); | ||
} | ||
|
||
hook.after(hookBindings); | ||
} | ||
printStream.println("Done!"); | ||
printStream.println(); | ||
|
||
} | ||
|
||
private Reader getTemplateReader() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole function is meant to normalize finding the reader for the template so that the calling code can seperate the |
||
String def = "org/apache/ibatis/migration/template_migration.sql"; | ||
Reader templateReader = null; | ||
try { | ||
templateReader = Resources.getResourceAsReader(def); | ||
} catch (IOException e) { | ||
throw new MigrationException(String.format("Default template %s can't be found?! ", def), e); | ||
} | ||
try { | ||
String template = getTemplateFile(); | ||
if (template != null) | ||
templateReader = new FileReader(template); | ||
|
||
} catch (FileNotFoundException e) { | ||
String msg = String.format( | ||
"Your migrations configuration did not find your custom template: %s. Using the default template.", | ||
e.getMessage()); | ||
printStream.append(msg); | ||
} | ||
return templateReader; | ||
} | ||
|
||
private String getTemplateFile() throws FileNotFoundException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Finds the location of the template file based on previous logic. This just functionalizes it for clarity |
||
String template = null; | ||
if (options.getTemplate() != null) { | ||
template = options.getTemplate(); | ||
} else { | ||
String customConfiguredTemplate = getPropertyOption(CUSTOM_NEW_COMMAND_TEMPLATE_PROPERTY); | ||
if (customConfiguredTemplate != null) { | ||
template = migrationsHome() + "/" + customConfiguredTemplate; | ||
} | ||
} | ||
return template; | ||
} | ||
|
||
private void copyDefaultTemplate(Properties variables, String filename) { | ||
copyResourceTo("org/apache/ibatis/migration/template_migration.sql", Util.file(paths.getScriptPath(), filename), | ||
variables); | ||
private Map<String, Object> createBinding(String[] params) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. functionalizes the creation of all binding properties for clarity sake. By functionalizing it the code essentially self documents. |
||
String nextId = getNextIDAsString(); | ||
String description = params[0]; | ||
|
||
String proposedFile = nextId + "_" + description.replace(' ', '_') + ".sql"; | ||
|
||
Map<String, Object> hookBindings = new HashMap<String, Object>(); | ||
BigDecimal id = new BigDecimal(nextId); | ||
|
||
File f = new File(String.format("%s%s%s", paths.getScriptPath(), File.separator, proposedFile)); | ||
Change change = new Change(id, null, description, f.getAbsolutePath()); | ||
Properties props = environmentProperties(); | ||
|
||
hookBindings.put("change", change); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
hookBindings.put("paths", paths); | ||
hookBindings.put("environment", props); | ||
hookBindings.put("args", Arrays.asList(params)); | ||
return hookBindings; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/** | ||
* Copyright 2010-2018 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.apache.ibatis.migration.hook; | ||
|
||
import java.util.Map; | ||
import org.apache.ibatis.migration.hook.scripts.HookScript; | ||
import org.apache.ibatis.migration.hook.scripts.NoOpHookScript; | ||
|
||
/** | ||
* @author cbongiorno on 12/29/17. | ||
*/ | ||
public class BasicHook implements Hook { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simply the base implementation of a hook. There is only |
||
protected static final NoOpHookScript NO_OP = NoOpHookScript.getInstance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Null Object Pattern. I am pretty active in design patterns :) |
||
|
||
private final HookScript beforeScript; | ||
private final HookScript afterScript; | ||
|
||
public BasicHook() { | ||
this(NO_OP, NO_OP); | ||
} | ||
|
||
public BasicHook(HookScript beforeScript, HookScript afterScript) { | ||
this.beforeScript = beforeScript == null ? NO_OP : beforeScript; | ||
this.afterScript = afterScript == null ? NO_OP : afterScript; | ||
} | ||
|
||
@Override | ||
public void before(Map<String, Object> bindingMap) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I debated having
Testing that now. |
||
beforeScript.execute(bindingMap); | ||
} | ||
|
||
@Override | ||
public void after(Map<String, Object> bindingMap) { | ||
afterScript.execute(bindingMap); | ||
} | ||
|
||
public HookScript getBeforeScript() { | ||
return beforeScript; | ||
} | ||
|
||
public HookScript getAfterScript() { | ||
return afterScript; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this so I could have a single constructor that takes all params and not require a setter to add the the filename (I think that was the field missing)