Skip to content
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

[WIP] Refactor jdbc migration and use service loader #151

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ebean-migration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

<properties>
<nexus.staging.autoReleaseAfterClose>true</nexus.staging.autoReleaseAfterClose>
<surefire.useModulePath>false</surefire.useModulePath>
rob-bygrave marked this conversation as resolved.
Show resolved Hide resolved
</properties>

<dependencies>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,56 @@
/**
* Interface to be implemented by Jdbc Java Migrations. By default the migration
* version and description will be extracted from the class name. The checksum of this migration
* (for validation) will also be null, unless the migration also implements the
* MigrationChecksumProvider, in which case it can be returned programmatically.
* will be 0 by default
* <p>
* When the JdbcMigration implements ConfigurationAware, the master
* {@link MigrationConfig} is automatically injected upon creation, which is
* useful for getting placeholder and schema information.
* Note: Instances of JdbcMigration should be stateless, as the <code>migrate</code> method may
* run multiple times in multi-tenant setups.
* <p>
* There are several ways, how the JdbcMigrations are found.
* <ul>
* <li><b>ServiceLoader</b> this is the default behaviour<br>
* in this case add all your migration class names in META-INF/services/io.ebean.migration.JdbcMigration and/or in your
* module info.</li>
* <li>Using <b>jdbcMigrations</b> property<br>
* you can specify all migrations in the jdbcMigrations property</li>
* <li>Using own <b>jdbcMigrationFactory</b<br>
* you can write your own jdbcMigrationFactory that provides JdbcMigrations</li>
* </ul>
*
* @author Roland Praml, FOCONIS AG
*/
public interface JdbcMigration extends MigrationChecksumProvider {

/**
* Execute the migration using the connection.
* <p>
* Note: This API has changed with ebean-migration 13.12, as the initialization has changed.
* See https://github.com/ebean-orm/ebean-migration/issues/90 for migration advice.
*/
void migrate(Connection connection);
void migrate(Connection connection, MigrationConfig config);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes an API break.

I could avoid this by "default" methods, but then I think no one reads the changelog and the java migrations will not work anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Yes I agree that people generally don't read release notes.


@Override
default int getChecksum() {
return 0;
}

rPraml marked this conversation as resolved.
Show resolved Hide resolved
/**
* Returns the name of the JdbcMigration. Note, the value must follow the naming conventions, for MigrationVersions.
* (example: <code>V1_2_1__comment</code>)
* <p>
* By default, the simple classname will be returned.
*/
default String getName() {
return getClass().getSimpleName();
}

/**
* Determines, if this migration can be used for that migrationConfig.
* Here, platform checks or other things can be implemented.
* <p>
* By default, <code>true</code> is returned.
*/
default boolean matches(MigrationConfig config) {
return true;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package io.ebean.migration;

import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;

/**
Expand All @@ -32,7 +37,7 @@ public class MigrationConfig {
private boolean setCurrentSchema = true;
private boolean allowErrorInRepeatable;

private JdbcMigrationFactory jdbcMigrationFactory = new DefaultMigrationFactory();
private Iterable<JdbcMigration> jdbcMigrationFactory = new DefaultMigrationFactory();

/**
* Versions that we want to insert into migration history without actually running.
Expand Down Expand Up @@ -418,17 +423,34 @@ public void setClassLoader(ClassLoader classLoader) {
/**
* Return the jdbcMigrationFactory.
*/
public JdbcMigrationFactory getJdbcMigrationFactory() {
public Iterable<JdbcMigration> getJdbcMigrationFactory() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a name mismatch in that getJdbcMigrationFactory() returns a "collection of migrations".

So I get it but it seems off. Migrations need to be sorted into version order etc. This doesn't quite look right yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, I'll rename it to getJdbcMigrationCollection

Migrations need to be sorted into version order etc. This doesn't quite look right yet.

I don't get you here. They are sorted later here https://github.com/ebean-orm/ebean-migration/pull/151/files#diff-10ad52c1950c7a3f96d7f7286cee908ff73e79324b8414c53ce3ad17204ca9e1R57 and they have to be sorted between the SQL migrations

return jdbcMigrationFactory;
}

/**
* Set the jdbcMigrationFactory.
* Set the jdbcMigrationFactory. If not set, the ServiceLoader is used.
* JdbcMigrationFactory can be either defined with the property <code>jdbcMigrationFactory</code>
* to a fully qualified class name implementing <code>Iterable&lt;JdbcMigration&gt;</code> or by
* specifying a comma separated list of JdbcMigrations in the <code>jdbcMigrations</code> property.
* <p>
* Note: If you plan to run migrations in multi-tenant env in multiple threads, the provided factory
* must be thread safe!
*/
public void setJdbcMigrationFactory(JdbcMigrationFactory jdbcMigrationFactory) {
public void setJdbcMigrationFactory(Iterable<JdbcMigration> jdbcMigrationFactory) {
this.jdbcMigrationFactory = jdbcMigrationFactory;
}

/**
* Helper method to set migrations with the <code>jdbcMigrations</code> property.
*/
public void setJdbcMigrations(String... classNames) {
List<JdbcMigration> migrations = new ArrayList<>(classNames.length);
for (String className : classNames) {
migrations.add(newInstance(className.trim()));
}
setJdbcMigrationFactory(migrations);
}

/**
* Return the minVersion.
*/
Expand Down Expand Up @@ -484,6 +506,16 @@ public void load(Properties props) {
minVersion = property("minVersion", minVersion);
minVersionFailMessage = property("minVersionFailMessage", minVersionFailMessage);

String jdbcMigrationFactory = property("jdbcMigrationFactory");
if (jdbcMigrationFactory != null) {
setJdbcMigrationFactory(newInstance(jdbcMigrationFactory));
}

String jdbcMigrations = property("jdbcMigrations");
if (jdbcMigrations != null) {
setJdbcMigrations(jdbcMigrations.split(","));
}

String patchInsertOn = property("patchInsertOn");
if (patchInsertOn != null) {
setPatchInsertOn(patchInsertOn);
Expand All @@ -498,6 +530,15 @@ public void load(Properties props) {
}
}

public <T> T newInstance(String className) {
try {
Class<?> cls = Class.forName(className, true, getClassLoader());
return (T) cls.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("Error constructing " + className, e);
}
}

private boolean property(String key, boolean value) {
String val = property(key);
return val != null ? Boolean.parseBoolean(val) : value;
Expand Down Expand Up @@ -611,25 +652,14 @@ public void setFastMode(boolean fastMode) {
}

/**
* Default factory. Uses the migration's class loader and injects the config if necessary.
*
* @author Roland Praml, FOCONIS AG
* Default implementation for service-loader. Note: ServiceLoader is not thread safe,
* so it is better to retrun a new iterator each time.
*/
public class DefaultMigrationFactory implements JdbcMigrationFactory {
private class DefaultMigrationFactory implements Iterable<JdbcMigration> {

@Override
public JdbcMigration createInstance(String className) {
try {
Class<?> clazz = Class.forName(className, true, MigrationConfig.this.getClassLoader());
JdbcMigration migration = (JdbcMigration) clazz.getDeclaredConstructor().newInstance();
if (migration instanceof ConfigurationAware) {
((ConfigurationAware) migration).setMigrationConfig(MigrationConfig.this);
}
return migration;
} catch (Exception e) {
throw new IllegalArgumentException(className + " is not a valid JdbcMigration", e);
}
public Iterator<JdbcMigration> iterator() {
return ServiceLoader.load(JdbcMigration.class, getClassLoader()).iterator();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about service loading JdbcMigration (each migration). I have in my mind that it would be better to add 1 level of indirection (a JdbcMigrationSupplier or JdbcMigrationFactory) ... then typically only that 1 supplier/factory would need to be registered for service loading rather than each JdbcMigration.

Hmmm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have the first level of indirection in the migrationConfig. You can set your jdbcMigrationFactory/Collection by a property. So you can implement your own search strategy (we did this with our spring-classpath scan)

I think, just Service-loading the factory is not a good idea, if you have multiple ebean-servers to different databases with different schemas or even different platforms. So the jdbcMigrations have to be configured the same way as the jdbcMigration-directory

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ final class LocalJdbcMigrationResource extends LocalMigrationResource {
/**
* Construct with version and resource.
*/
LocalJdbcMigrationResource(MigrationVersion version, String location, JdbcMigration migration) {
super(version, location);
LocalJdbcMigrationResource(MigrationVersion version, JdbcMigration migration) {
super(version, migration.getClass().getName());
this.migration = migration;
}

Expand Down
Loading
Loading