From 8d0140797402f3029202fbd1afbb637e7b921913 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Thu, 21 Nov 2019 23:27:50 +0100 Subject: [PATCH] [jOOQ/jOOQ#9586] Add MigrationListener SPI --- .../src/main/java/org/jooq/Configuration.java | 75 ++++++++++ .../main/java/org/jooq/MigrationContext.java | 129 +++++++++++++++++ .../main/java/org/jooq/MigrationListener.java | 78 +++++++++++ .../org/jooq/MigrationListenerProvider.java | 74 ++++++++++ .../src/main/java/org/jooq/conf/Settings.java | 78 +++++++++++ .../org/jooq/impl/DefaultConfiguration.java | 77 ++++++++++ .../jooq/impl/DefaultMigrationContext.java | 105 ++++++++++++++ .../jooq/impl/DefaultMigrationListener.java | 70 ++++++++++ .../DefaultMigrationListenerProvider.java | 97 +++++++++++++ .../java/org/jooq/impl/MigrationImpl.java | 132 +++++++++++------- .../org/jooq/impl/MigrationListeners.java | 112 +++++++++++++++ .../jooq/tools/jdbc/MockConfiguration.java | 27 ++++ .../resources/xsd/jooq-runtime-3.13.0.xsd | 8 ++ 13 files changed, 1008 insertions(+), 54 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/MigrationContext.java create mode 100644 jOOQ/src/main/java/org/jooq/MigrationListener.java create mode 100644 jOOQ/src/main/java/org/jooq/MigrationListenerProvider.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/DefaultMigrationContext.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListener.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListenerProvider.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/MigrationListeners.java diff --git a/jOOQ/src/main/java/org/jooq/Configuration.java b/jOOQ/src/main/java/org/jooq/Configuration.java index e8a9015803..7c5e98696b 100644 --- a/jOOQ/src/main/java/org/jooq/Configuration.java +++ b/jOOQ/src/main/java/org/jooq/Configuration.java @@ -55,6 +55,7 @@ import org.jooq.impl.DefaultConnectionProvider; import org.jooq.impl.DefaultDiagnosticsListenerProvider; import org.jooq.impl.DefaultExecuteListenerProvider; import org.jooq.impl.DefaultExecutorProvider; +import org.jooq.impl.DefaultMigrationListenerProvider; import org.jooq.impl.DefaultRecordListenerProvider; import org.jooq.impl.DefaultRecordMapper; import org.jooq.impl.DefaultRecordMapperProvider; @@ -405,6 +406,31 @@ public interface Configuration extends Serializable { */ ExecuteListenerProvider[] executeListenerProviders(); + /** + * Get the configured MigrationListenerProviders from this + * configuration. + *

+ * This method allows for retrieving the configured + * MigrationListenerProvider from this configuration. The + * providers will provide jOOQ with {@link MigrationListener} instances. + * These instances receive migration lifecycle notification events every + * time jOOQ executes migrations. jOOQ makes no assumptions about the + * internal state of these listeners, i.e. listener instances may + *

+ * + * @return The configured set of migration listeners. + * @see MigrationListenerProvider + * @see MigrationListener + * @see MigrationContext + */ + MigrationListenerProvider[] migrationListenerProviders(); + /** * Get the configured VisitListenerProvider instances from this * configuration. @@ -689,6 +715,33 @@ public interface Configuration extends Serializable { */ Configuration set(ExecuteListenerProvider... newExecuteListenerProviders); + /** + * Change this configuration to hold a new migration listeners. + *

+ * This will wrap the argument {@link MigrationListener} in a + * {@link DefaultMigrationListenerProvider} for convenience. + *

+ * This method is not thread-safe and should not be used in globally + * available Configuration objects. + * + * @param newMigrationListeners The new migration listeners to be contained + * in the changed configuration. + * @return The changed configuration. + */ + Configuration set(MigrationListener... newMigrationListeners); + + /** + * Change this configuration to hold a new migration listener providers. + *

+ * This method is not thread-safe and should not be used in globally + * available Configuration objects. + * + * @param newMigrationListenerProviders The new migration listener providers to + * be contained in the changed configuration. + * @return The changed configuration. + */ + Configuration set(MigrationListenerProvider... newMigrationListenerProviders); + /** * Change this configuration to hold a new visit listeners. *

@@ -1023,6 +1076,28 @@ public interface Configuration extends Serializable { */ Configuration derive(ExecuteListenerProvider... newExecuteListenerProviders); + /** + * Create a derived configuration from this one, with new migration listeners. + *

+ * This will wrap the argument {@link MigrationListener} in a + * {@link DefaultMigrationListenerProvider} for convenience. + * + * @param newMigrationListeners The new migration listener to be contained in + * the derived configuration. + * @return The derived configuration. + */ + Configuration derive(MigrationListener... newMigrationListeners); + + /** + * Create a derived configuration from this one, with new migration listener + * providers. + * + * @param newMigrationListenerProviders The new migration listener providers to + * be contained in the derived configuration. + * @return The derived configuration. + */ + Configuration derive(MigrationListenerProvider... newMigrationListenerProviders); + /** * Create a derived configuration from this one, with new visit listeners. *

diff --git a/jOOQ/src/main/java/org/jooq/MigrationContext.java b/jOOQ/src/main/java/org/jooq/MigrationContext.java new file mode 100644 index 0000000000..dba1966dc5 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MigrationContext.java @@ -0,0 +1,129 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +/** + * The context in which a {@link Migration} is executed. + * + * @see MigrationListener + * @author Lukas Eder + */ +public interface MigrationContext extends Scope { + + /** + * The {@link Version} from which a {@link Migration} has started. + *

+ * {@link #migrationFrom()} and {@link #migrationTo()} versions need not be + * consecutive versions for any given migration. If a migration jumps a few + * versions, these two methods will only return the endpoints. + *

+ * This is available on all {@link MigrationListener} events. + */ + Version migrationFrom(); + + /** + * The {@link Version} to which a {@link Migration} is headed. + *

+ * {@link #migrationFrom()} and {@link #migrationTo()} versions need not be + * consecutive versions for any given migration. If a migration jumps a few + * versions, these two methods will only return the endpoints. + *

+ * This is available on all {@link MigrationListener} events. + */ + Version migrationTo(); + + /** + * The complete set of {@link Queries} that are executed between + * {@link #migrationFrom()} and {@link #migrationTo()}. + *

+ * This is available on all {@link MigrationListener} events. + */ + Queries migrationQueries(); + + /** + * The {@link Version} from which an individual set of {@link Queries} has + * started. + *

+ * {@link #queriesFrom()} and {@link #queriesTo()} versions are consecutive + * versions in a migration. If a migration jumps a few versions, these two + * methods might return those intermediate versions on these events: + *

+ *

+ */ + Version queriesFrom(); + + /** + * The {@link Version} to which an individual set of {@link Queries} is + * headed. + *

+ * {@link #queriesFrom()} and {@link #queriesTo()} versions are consecutive + * versions in a migration. If a migration jumps a few versions, these two + * methods might return those intermediate versions on these events: + *

+ *

+ */ + Version queriesTo(); + + /** + * The complete set of {@link Queries} that are executed between + * {@link #queriesFrom()} and {@link #queriesTo()}. + *

+ * This is available on the same {@link MigrationListener} events as + * {@link #queriesFrom()} and {@link #queriesTo()}. + */ + Queries queries(); + + /** + * The current {@link Query} that is being executed. + *

+ * This is available on + * {@link MigrationListener#queryStart(MigrationContext)} and + * {@link MigrationListener#queryEnd(MigrationContext)}. + */ + Query query(); +} diff --git a/jOOQ/src/main/java/org/jooq/MigrationListener.java b/jOOQ/src/main/java/org/jooq/MigrationListener.java new file mode 100644 index 0000000000..6c2593d04a --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MigrationListener.java @@ -0,0 +1,78 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +import java.util.EventListener; + +/** + * A listener for {@link Migration} lifecycles. + * + * @author Lukas Eder + */ +public interface MigrationListener extends EventListener { + + /** + * Invoked at the start of a {@link Migration}. + */ + void migrationStart(MigrationContext ctx); + + /** + * Invoked at the end of a {@link Migration}. + */ + void migrationEnd(MigrationContext ctx); + + /** + * Invoked at the start of a set of {@link Queries} that describe a single version increment. + */ + void queriesStart(MigrationContext ctx); + + /** + * Invoked at the end of a set of {@link Queries} that describe a single version increment. + */ + void queriesEnd(MigrationContext ctx); + + /** + * Invoked at the start of an individual {@link Query}. + */ + void queryStart(MigrationContext ctx); + + /** + * Invoked at the start of an individual {@link Query}. + */ + void queryEnd(MigrationContext ctx); +} diff --git a/jOOQ/src/main/java/org/jooq/MigrationListenerProvider.java b/jOOQ/src/main/java/org/jooq/MigrationListenerProvider.java new file mode 100644 index 0000000000..988f196fb9 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MigrationListenerProvider.java @@ -0,0 +1,74 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +/** + * A provider for {@link MigrationListener} instances. + *

+ * In order to facilitate the lifecycle management of + * MigrationListener instances that are provided to a jOOQ + * {@link Configuration}, clients can implement this API. To jOOQ, it is thus + * irrelevant, if migration listeners are stateful or stateless, local to an + * execution, or global to an application. + * + * @author Lukas Eder + * @see MigrationListener + * @see Configuration + */ + +@FunctionalInterface + +public interface MigrationListenerProvider { + + /** + * Provide an MigrationListener instance. + *

+ * Implementations are free to choose whether this method returns new + * instances at every call or whether the same instance is returned + * repetitively. + *

+ * An MigrationListener shall be provided exactly once per {@link Migration} + * execution lifecycle, i.e. per MigrationContext. + * + * @return An MigrationListener instance. + * @see MigrationListener + * @see MigrationContext + * @see DefaultMigrationListenerProvider + */ + MigrationListener provide(); +} diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index 9979b051ec..20b09822cb 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -103,6 +103,12 @@ public class Settings protected InvocationOrder transactionListenerEndInvocationOrder = InvocationOrder.DEFAULT; @XmlElement(defaultValue = "DEFAULT") @XmlSchemaType(name = "string") + protected InvocationOrder migrationListenerStartInvocationOrder = InvocationOrder.DEFAULT; + @XmlElement(defaultValue = "DEFAULT") + @XmlSchemaType(name = "string") + protected InvocationOrder migrationListenerEndInvocationOrder = InvocationOrder.DEFAULT; + @XmlElement(defaultValue = "DEFAULT") + @XmlSchemaType(name = "string") protected InvocationOrder visitListenerStartInvocationOrder = InvocationOrder.DEFAULT; @XmlElement(defaultValue = "DEFAULT") @XmlSchemaType(name = "string") @@ -822,6 +828,38 @@ public class Settings this.transactionListenerEndInvocationOrder = value; } + /** + * The order of invocation for [action]start() methods registered {@link org.jooq.MigrationListener}s. + * + */ + public InvocationOrder getMigrationListenerStartInvocationOrder() { + return migrationListenerStartInvocationOrder; + } + + /** + * The order of invocation for [action]start() methods registered {@link org.jooq.MigrationListener}s. + * + */ + public void setMigrationListenerStartInvocationOrder(InvocationOrder value) { + this.migrationListenerStartInvocationOrder = value; + } + + /** + * The order of invocation for [action]end() methods registered {@link org.jooq.MigrationListener}s. + * + */ + public InvocationOrder getMigrationListenerEndInvocationOrder() { + return migrationListenerEndInvocationOrder; + } + + /** + * The order of invocation for [action]end() methods registered {@link org.jooq.MigrationListener}s. + * + */ + public void setMigrationListenerEndInvocationOrder(InvocationOrder value) { + this.migrationListenerEndInvocationOrder = value; + } + /** * The order of invocation for [action]start() methods registered {@link org.jooq.VisitListener}s. * @@ -1946,6 +1984,24 @@ public class Settings return this; } + /** + * The order of invocation for [action]start() methods registered {@link org.jooq.MigrationListener}s. + * + */ + public Settings withMigrationListenerStartInvocationOrder(InvocationOrder value) { + setMigrationListenerStartInvocationOrder(value); + return this; + } + + /** + * The order of invocation for [action]end() methods registered {@link org.jooq.MigrationListener}s. + * + */ + public Settings withMigrationListenerEndInvocationOrder(InvocationOrder value) { + setMigrationListenerEndInvocationOrder(value); + return this; + } + /** * The order of invocation for [action]start() methods registered {@link org.jooq.VisitListener}s. * @@ -2306,6 +2362,8 @@ public class Settings builder.append("inlineThreshold", inlineThreshold); builder.append("transactionListenerStartInvocationOrder", transactionListenerStartInvocationOrder); builder.append("transactionListenerEndInvocationOrder", transactionListenerEndInvocationOrder); + builder.append("migrationListenerStartInvocationOrder", migrationListenerStartInvocationOrder); + builder.append("migrationListenerEndInvocationOrder", migrationListenerEndInvocationOrder); builder.append("visitListenerStartInvocationOrder", visitListenerStartInvocationOrder); builder.append("visitListenerEndInvocationOrder", visitListenerEndInvocationOrder); builder.append("recordListenerStartInvocationOrder", recordListenerStartInvocationOrder); @@ -2605,6 +2663,24 @@ public class Settings return false; } } + if (migrationListenerStartInvocationOrder == null) { + if (other.migrationListenerStartInvocationOrder!= null) { + return false; + } + } else { + if (!migrationListenerStartInvocationOrder.equals(other.migrationListenerStartInvocationOrder)) { + return false; + } + } + if (migrationListenerEndInvocationOrder == null) { + if (other.migrationListenerEndInvocationOrder!= null) { + return false; + } + } else { + if (!migrationListenerEndInvocationOrder.equals(other.migrationListenerEndInvocationOrder)) { + return false; + } + } if (visitListenerStartInvocationOrder == null) { if (other.visitListenerStartInvocationOrder!= null) { return false; @@ -3034,6 +3110,8 @@ public class Settings result = ((prime*result)+((inlineThreshold == null)? 0 :inlineThreshold.hashCode())); result = ((prime*result)+((transactionListenerStartInvocationOrder == null)? 0 :transactionListenerStartInvocationOrder.hashCode())); result = ((prime*result)+((transactionListenerEndInvocationOrder == null)? 0 :transactionListenerEndInvocationOrder.hashCode())); + result = ((prime*result)+((migrationListenerStartInvocationOrder == null)? 0 :migrationListenerStartInvocationOrder.hashCode())); + result = ((prime*result)+((migrationListenerEndInvocationOrder == null)? 0 :migrationListenerEndInvocationOrder.hashCode())); result = ((prime*result)+((visitListenerStartInvocationOrder == null)? 0 :visitListenerStartInvocationOrder.hashCode())); result = ((prime*result)+((visitListenerEndInvocationOrder == null)? 0 :visitListenerEndInvocationOrder.hashCode())); result = ((prime*result)+((recordListenerStartInvocationOrder == null)? 0 :recordListenerStartInvocationOrder.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java index 52020b14a9..cbb4a440f9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java @@ -63,6 +63,8 @@ import org.jooq.ExecuteListener; import org.jooq.ExecuteListenerProvider; import org.jooq.ExecutorProvider; import org.jooq.MetaProvider; +import org.jooq.MigrationListener; +import org.jooq.MigrationListenerProvider; import org.jooq.Record; import org.jooq.RecordListener; import org.jooq.RecordListenerProvider; @@ -120,6 +122,7 @@ public class DefaultConfiguration implements Configuration { private transient RecordUnmapperProvider recordUnmapperProvider; private transient RecordListenerProvider[] recordListenerProviders; private transient ExecuteListenerProvider[] executeListenerProviders; + private transient MigrationListenerProvider[] migrationListenerProviders; private transient VisitListenerProvider[] visitListenerProviders; private transient TransactionListenerProvider[] transactionListenerProviders; private transient DiagnosticsListenerProvider[] diagnosticsListenerProviders; @@ -177,6 +180,7 @@ public class DefaultConfiguration implements Configuration { null, null, null, + null, null, @@ -207,6 +211,7 @@ public class DefaultConfiguration implements Configuration { configuration.recordUnmapperProvider, configuration.recordListenerProviders, configuration.executeListenerProviders, + configuration.migrationListenerProviders, configuration.visitListenerProviders, configuration.transactionListenerProviders, configuration.diagnosticsListenerProviders, @@ -241,6 +246,7 @@ public class DefaultConfiguration implements Configuration { RecordUnmapperProvider recordUnmapperProvider, RecordListenerProvider[] recordListenerProviders, ExecuteListenerProvider[] executeListenerProviders, + MigrationListenerProvider[] migrationListenerProviders, VisitListenerProvider[] visitListenerProviders, TransactionListenerProvider[] transactionListenerProviders, DiagnosticsListenerProvider[] diagnosticsListenerProviders, @@ -264,6 +270,7 @@ public class DefaultConfiguration implements Configuration { set(recordUnmapperProvider); set(recordListenerProviders); set(executeListenerProviders); + set(migrationListenerProviders); set(visitListenerProviders); set(transactionListenerProviders); set(diagnosticsListenerProviders); @@ -322,6 +329,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -350,6 +358,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -378,6 +387,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -411,6 +421,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -439,6 +450,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -472,6 +484,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -505,6 +518,7 @@ public class DefaultConfiguration implements Configuration { newRecordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -538,6 +552,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, newRecordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -571,6 +586,41 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, newExecuteListenerProviders, + migrationListenerProviders, + visitListenerProviders, + transactionListenerProviders, + diagnosticsListenerProviders, + unwrapperProvider, + converterProvider, + + clock, + + dialect, + settings, + data + ); + } + + @Override + public final Configuration derive(MigrationListener... newMigrationListeners) { + return derive(DefaultMigrationListenerProvider.providers(newMigrationListeners)); + } + + @Override + public final Configuration derive(MigrationListenerProvider... newMigrationListenerProviders) { + return new DefaultConfiguration( + connectionProvider, + interpreterConnectionProvider, + systemConnectionProvider, + metaProvider, + versionProvider, + executorProvider, + transactionProvider, + recordMapperProvider, + recordUnmapperProvider, + recordListenerProviders, + executeListenerProviders, + newMigrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -604,6 +654,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, newVisitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -637,6 +688,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, newTransactionListenerProviders, diagnosticsListenerProviders, @@ -670,6 +722,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, newDiagnosticsListenerProviders, @@ -703,6 +756,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -731,6 +785,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -760,6 +815,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -787,6 +843,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -815,6 +872,7 @@ public class DefaultConfiguration implements Configuration { recordUnmapperProvider, recordListenerProviders, executeListenerProviders, + migrationListenerProviders, visitListenerProviders, transactionListenerProviders, diagnosticsListenerProviders, @@ -949,6 +1007,20 @@ public class DefaultConfiguration implements Configuration { return this; } + @Override + public final Configuration set(MigrationListener... newMigrationListeners) { + return set(DefaultMigrationListenerProvider.providers(newMigrationListeners)); + } + + @Override + public final Configuration set(MigrationListenerProvider... newMigrationListenerProviders) { + this.migrationListenerProviders = newMigrationListenerProviders != null + ? newMigrationListenerProviders + : new MigrationListenerProvider[0]; + + return this; + } + @Override public final Configuration set(VisitListener... newVisitListeners) { return set(DefaultVisitListenerProvider.providers(newVisitListeners)); @@ -1348,6 +1420,11 @@ public class DefaultConfiguration implements Configuration { return executeListenerProviders; } + @Override + public final MigrationListenerProvider[] migrationListenerProviders() { + return migrationListenerProviders; + } + @Override public final VisitListenerProvider[] visitListenerProviders() { return visitListenerProviders; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationContext.java new file mode 100644 index 0000000000..aa45600bdc --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationContext.java @@ -0,0 +1,105 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import org.jooq.Configuration; +import org.jooq.MigrationContext; +import org.jooq.Queries; +import org.jooq.Query; +import org.jooq.Version; + +/** + * A default implementation for {@link MigrationContext}. + * + * @author Lukas Eder + */ +final class DefaultMigrationContext extends AbstractScope implements MigrationContext { + + private final Version migrationFrom; + private final Version migrationTo; + private final Queries migrationQueries; + + private Query query; + + DefaultMigrationContext(Configuration configuration, Version migrationFrom, Version migrationTo, Queries migrationQueries) { + super(configuration); + + this.migrationFrom = migrationFrom; + this.migrationTo = migrationTo; + this.migrationQueries = migrationQueries; + } + + @Override + public Version migrationFrom() { + return migrationFrom; + } + + @Override + public Version migrationTo() { + return migrationTo; + } + + @Override + public Queries migrationQueries() { + return migrationQueries; + } + + @Override + public Version queriesFrom() { + return migrationFrom; + } + + @Override + public Version queriesTo() { + return migrationTo; + } + + @Override + public Queries queries() { + return migrationQueries; + } + + @Override + public Query query() { + return query; + } + + void query(Query q) { + this.query = q; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListener.java b/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListener.java new file mode 100644 index 0000000000..89b85af527 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListener.java @@ -0,0 +1,70 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import org.jooq.MigrationContext; +import org.jooq.MigrationListener; + +/** + * A publicly available default implementation of {@link MigrationListener}. + *

+ * Use this to stay compatible with future API changes (i.e. added methods to + * MigrationListener) + * + * @author Lukas Eder + */ +public class DefaultMigrationListener implements MigrationListener { + + @Override + public void migrationStart(MigrationContext ctx) {} + + @Override + public void migrationEnd(MigrationContext ctx) {} + + @Override + public void queriesStart(MigrationContext ctx) {} + + @Override + public void queriesEnd(MigrationContext ctx) { } + + @Override + public void queryStart(MigrationContext ctx) {} + + @Override + public void queryEnd(MigrationContext ctx) {} +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListenerProvider.java b/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListenerProvider.java new file mode 100644 index 0000000000..64f5389d26 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultMigrationListenerProvider.java @@ -0,0 +1,97 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import java.io.Serializable; + +import org.jooq.MigrationListener; +import org.jooq.MigrationListenerProvider; + +/** + * A default implementation for {@link MigrationListenerProvider}. + *

+ * This implementation just wraps an instance of {@link MigrationListener}, always + * providing the same. + * + * @author Lukas Eder + */ +public class DefaultMigrationListenerProvider implements MigrationListenerProvider, Serializable { + + /** + * Generated UID. + */ + private static final long serialVersionUID = -2122007794302549679L; + + /** + * The delegate listener. + */ + private final MigrationListener listener; + + /** + * Convenience method to construct an array of + * DefaultMigrationListenerProvider from an array of + * MigrationListener instances. + */ + public static MigrationListenerProvider[] providers(MigrationListener... listeners) { + MigrationListenerProvider[] result = new MigrationListenerProvider[listeners.length]; + + for (int i = 0; i < listeners.length; i++) + result[i] = new DefaultMigrationListenerProvider(listeners[i]); + + return result; + } + + /** + * Create a new provider instance from an argument listener. + * + * @param listener The argument listener. + */ + public DefaultMigrationListenerProvider(MigrationListener listener) { + this.listener = listener; + } + + @Override + public final MigrationListener provide() { + return listener; + } + + @Override + public String toString() { + return listener.toString(); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java b/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java index 1a24fe0a5a..e018ddb67b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MigrationImpl.java @@ -49,6 +49,7 @@ import org.jooq.ContextTransactionalCallable; import org.jooq.Field; import org.jooq.Identity; import org.jooq.Migration; +import org.jooq.MigrationListener; import org.jooq.MigrationResult; import org.jooq.Name; import org.jooq.Queries; @@ -135,63 +136,86 @@ final class MigrationImpl extends AbstractScope implements Migration { return run(new ContextTransactionalCallable() { @Override public MigrationResult run() { - if (from().equals(to())) { - log.info("jOOQ Migrations", "Version " + to().id() + " is already installed as the current version."); + DefaultMigrationContext ctx = new DefaultMigrationContext(configuration(), from(), to(), queries()); + MigrationListener listener = new MigrationListeners(configuration); + + try { + listener.migrationStart(ctx); + + if (from().equals(to())) { + log.info("jOOQ Migrations", "Version " + to().id() + " is already installed as the current version."); + return MIGRATION_RESULT; + } + + // TODO: Implement preconditions + // TODO: Implement a listener with a variety of pro / oss features + // TODO: Implement additional out-of-the-box sanity checks + // TODO: Allow undo migrations only if enabled explicitly + // TODO: Add some migration settings, e.g. whether CHANGELOG.SQL should be filled + // TODO: Migrate the CHANGELOG table with the Migration API + // TODO: Create an Enum for CHANGELOG.STATUS + + log.info("jOOQ Migrations", "Version " + from().id() + " is migrated to " + to().id()); + + StopWatch watch = new StopWatch(); + + // TODO: Make logging configurable + if (log.isDebugEnabled()) + for (Query query : queries()) + log.debug("jOOQ Migrations", dsl().renderInlined(query)); + + JooqMigrationsChangelogRecord newRecord = dsl().newRecord(CHANGELOG); + + newRecord + .setJooqVersion(Constants.VERSION) + .setMigratedAt(new Timestamp(System.currentTimeMillis())) + .setMigratedFrom(from().id()) + .setMigratedTo(to().id()) + .setMigrationTime(0L) + .setSql(queries().toString()) + .setSqlCount(queries().queries().length) + .setStatus("PENDING") + .insert(); + + try { + + // TODO: Can we access the individual Queries from Version, if applicable? + // TODO: Set the ctx.queriesFrom(), ctx.queriesTo(), and ctx.queries() values + listener.queriesStart(ctx); + + // TODO: Make batching an option: queries().executeBatch(); + for (Query query : queries().queries()) { + ctx.query(query); + listener.queryStart(ctx); + query.execute(); + listener.queryEnd(ctx); + ctx.query(null); + } + + listener.queriesEnd(ctx); + + newRecord + .setMigrationTime(watch.split() / 1000000L) + .setStatus("SUCCESS") + .update(); + } + catch (DataAccessException e) { + + // TODO: Make sure this is committed, given that we're re-throwing the exception. + // TODO: How can we recover from failure? + newRecord + .setMigrationTime(watch.split() / 1000000L) + .setStatus("FAILURE") + .update(); + + throw e; + } + return MIGRATION_RESULT; } - - // TODO: Implement preconditions - // TODO: Implement a listener with a variety of pro / oss features - // TODO: Implement additional out-of-the-box sanity checks - // TODO: Allow undo migrations only if enabled explicitly - // TODO: Add some migration settings, e.g. whether CHANGELOG.SQL should be filled - // TODO: Migrate the CHANGELOG table with the Migration API - // TODO: Create an Enum for CHANGELOG.STATUS - - log.info("jOOQ Migrations", "Version " + from().id() + " is migrated to " + to().id()); - - StopWatch watch = new StopWatch(); - - // TODO: Make logging configurable - if (log.isDebugEnabled()) - for (Query query : queries()) - log.debug("jOOQ Migrations", dsl().renderInlined(query)); - - JooqMigrationsChangelogRecord newRecord = dsl().newRecord(CHANGELOG); - - newRecord - .setJooqVersion(Constants.VERSION) - .setMigratedAt(new Timestamp(System.currentTimeMillis())) - .setMigratedFrom(from().id()) - .setMigratedTo(to().id()) - .setMigrationTime(0L) - .setSql(queries().toString()) - .setSqlCount(queries().queries().length) - .setStatus("PENDING") - .insert(); - - // TODO: Make batching an option - try { - queries().executeBatch(); - - newRecord - .setMigrationTime(watch.split() / 1000000L) - .setStatus("SUCCESS") - .update(); + finally { + listener.migrationEnd(ctx); } - catch (DataAccessException e) { - - // TODO: Make sure this is committed, given that we're re-throwing the exception. - // TODO: How can we recover from failure? - newRecord - .setMigrationTime(watch.split() / 1000000L) - .setStatus("FAILURE") - .update(); - - throw e; - } - - return MIGRATION_RESULT; } }); } diff --git a/jOOQ/src/main/java/org/jooq/impl/MigrationListeners.java b/jOOQ/src/main/java/org/jooq/impl/MigrationListeners.java new file mode 100644 index 0000000000..a02863f88c --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/MigrationListeners.java @@ -0,0 +1,112 @@ +/* + * 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static org.jooq.conf.InvocationOrder.REVERSE; + +import java.util.Arrays; + +import org.jooq.Configuration; +import org.jooq.MigrationContext; +import org.jooq.MigrationListener; +import org.jooq.MigrationListenerProvider; + +/** + * @author Lukas Eder + */ +class MigrationListeners implements MigrationListener { + + private final MigrationListener[] listeners; + + MigrationListeners(Configuration configuration) { + MigrationListenerProvider[] providers = configuration.migrationListenerProviders(); + listeners = new MigrationListener[providers.length]; + + for (int i = 0; i < providers.length; i++) + listeners[i] = providers[i].provide(); + } + + @Override + public final void migrationStart(MigrationContext ctx) { + for (MigrationListener listener : ctx.settings().getMigrationListenerStartInvocationOrder() != REVERSE + ? Arrays.asList(listeners) + : Tools.reverseIterable(listeners)) + listener.migrationStart(ctx); + } + + @Override + public final void migrationEnd(MigrationContext ctx) { + for (MigrationListener listener : ctx.settings().getMigrationListenerEndInvocationOrder() != REVERSE + ? Arrays.asList(listeners) + : Tools.reverseIterable(listeners)) + listener.migrationEnd(ctx); + } + + @Override + public final void queriesStart(MigrationContext ctx) { + for (MigrationListener listener : ctx.settings().getMigrationListenerStartInvocationOrder() != REVERSE + ? Arrays.asList(listeners) + : Tools.reverseIterable(listeners)) + listener.queriesStart(ctx); + } + + @Override + public final void queriesEnd(MigrationContext ctx) { + for (MigrationListener listener : ctx.settings().getMigrationListenerEndInvocationOrder() != REVERSE + ? Arrays.asList(listeners) + : Tools.reverseIterable(listeners)) + listener.queriesEnd(ctx); + } + + @Override + public final void queryStart(MigrationContext ctx) { + for (MigrationListener listener : ctx.settings().getMigrationListenerStartInvocationOrder() != REVERSE + ? Arrays.asList(listeners) + : Tools.reverseIterable(listeners)) + listener.queryStart(ctx); + } + + @Override + public final void queryEnd(MigrationContext ctx) { + for (MigrationListener listener : ctx.settings().getMigrationListenerEndInvocationOrder() != REVERSE + ? Arrays.asList(listeners) + : Tools.reverseIterable(listeners)) + listener.queryEnd(ctx); + } + +} diff --git a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java index 86044f6630..8c8a0e9669 100644 --- a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java +++ b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java @@ -54,6 +54,8 @@ import org.jooq.ExecuteListener; import org.jooq.ExecuteListenerProvider; import org.jooq.ExecutorProvider; import org.jooq.MetaProvider; +import org.jooq.MigrationListener; +import org.jooq.MigrationListenerProvider; import org.jooq.RecordListener; import org.jooq.RecordListenerProvider; import org.jooq.RecordMapper; @@ -178,6 +180,11 @@ public class MockConfiguration implements Configuration { return delegate.executeListenerProviders(); } + @Override + public MigrationListenerProvider[] migrationListenerProviders() { + return delegate.migrationListenerProviders(); + } + @Override public VisitListenerProvider[] visitListenerProviders() { return delegate.visitListenerProviders(); @@ -310,6 +317,16 @@ public class MockConfiguration implements Configuration { return delegate.set(newExecuteListenerProviders); } + @Override + public Configuration set(MigrationListener... newMigrationListeners) { + return delegate.set(newMigrationListeners); + } + + @Override + public Configuration set(MigrationListenerProvider... newMigrationListenerProviders) { + return delegate.set(newMigrationListenerProviders); + } + @Override public Configuration set(VisitListener... newVisitListeners) { return delegate.set(newVisitListeners); @@ -457,6 +474,16 @@ public class MockConfiguration implements Configuration { return delegate.derive(newExecuteListenerProviders); } + @Override + public Configuration derive(MigrationListener... newMigrationListeners) { + return delegate.derive(newMigrationListeners); + } + + @Override + public Configuration derive(MigrationListenerProvider... newMigrationListenerProviders) { + return delegate.derive(newMigrationListenerProviders); + } + @Override public Configuration derive(VisitListener... newVisitListeners) { return delegate.derive(newVisitListeners); diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd index d5fffc5489..aaa33502c7 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd @@ -224,6 +224,14 @@ case of which, this defaults to INLINED]]> + + + + + + + +