/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.jooby.flyway;
import static java.util.Objects.requireNonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import org.flywaydb.core.Flyway;
import org.jooby.Env;
import org.jooby.Jooby.Module;
import com.google.inject.Binder;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
/**
* <h1>flyway module</h1>
* <p>
* Run database migrations on startup and exposes a {@link Flyway} instance.
* </p>
*
* <h2>usage</h2>
*
* <pre>
* flyway.url = ...
* flyway.user = ...
* flyway.password = ...
* </pre>
*
* <pre>
* {
* use(new Flywaydb());
* }
* </pre>
*
* <p>
* If for any reason you need to maintain two or more databases:
* </p>
*
* <pre>
* flyway.db1.url = "..."
* flyway.db1.locations = "db1/migration"
* flyway.db2.url = "..."
* flyway.db2.locations = "db2/migration"
* </pre>
*
* <pre>
* {
* use(new Flywaydb("flyway.db1"));
* use(new Flywaydb("flyway.db2"));
* }
* </pre>
*
* <h2>migration scripts</h2>
* <p>
* {@link Flyway} looks for migration scripts at the <code>db/migration</code> classpath location.
* We recommend to use <a href="http://semver.org">Semantic versioning</a> for naming the migration
* scripts:
* </p>
*
* <pre>
* v0.1.0_My_description.sql
* v0.1.1_My_small_change.sql
* </pre>
*
* <h2>commands</h2>
* <p>
* It is possible to run {@link Flyway} commands on startup, default command is:
* <code>migrate</code>.
* </p>
* <p>
* If you need to run multiple commands, set the <code>flyway.run</code> property:
* </p>
*
* <pre>
* flyway.run = [clean, migrate, validate, info]
* </pre>
*
* <h2>configuration</h2>
* <p>
* Configuration is done via <code>application.conf</code> under the <code>flyway.*</code> path.
* There are some defaults setting that you can see in the appendix.
* </p>
*
* @author edgar
* @since 0.8.0
*/
public class Flywaydb implements Module {
private static enum Command {
migrate {
@Override
public void run(final Flyway flyway) {
flyway.migrate();
}
},
clean {
@Override
public void run(final Flyway flyway) {
flyway.clean();
}
},
info {
@Override
public void run(final Flyway flyway) {
flyway.info();
}
},
validate {
@Override
public void run(final Flyway flyway) {
flyway.validate();
}
},
baseline {
@Override
public void run(final Flyway flyway) {
flyway.baseline();
}
},
repair {
@Override
public void run(final Flyway flyway) {
flyway.repair();
}
};
public abstract void run(Flyway flyway);
}
private String name;
/**
* Creates a new {@link Flywaydb}. The given name is use it to read flyway properties, keep in
* mind that custom configuration will inherited all the properties from <code>flyway.*</code>.
*
* @param name Name of the property with flyway properties.
*/
public Flywaydb(final String name) {
this.name = requireNonNull(name, "Flyway name is required.");
}
/**
* Creates a new {@link Flywaydb} module, using <code>flyway</code> as property.
*/
public Flywaydb() {
this("flyway");
}
@Override
public void configure(final Env env, final Config conf, final Binder binder) {
Config $flyway = conf.getConfig(name)
.withFallback(conf.getConfig("flyway"));
Flyway flyway = new Flyway();
flyway.configure(props($flyway));
// bind
env.serviceKey()
.generate(Flyway.class, name, key -> binder.bind(key).toInstance(flyway));
// run
Iterable<Command> cmds = commands($flyway);
env.onStart(() -> {
cmds.forEach(cmd -> cmd.run(flyway));
});
}
@Override
public Config config() {
return ConfigFactory.parseResources(getClass(), "flyway.conf");
}
@SuppressWarnings({"unchecked", "rawtypes" })
private static Properties props(final Config config) {
Properties props = new Properties();
config.withoutPath("run").entrySet().forEach(prop -> {
Object value = prop.getValue().unwrapped();
if (value instanceof List) {
value = ((List) value).stream().collect(Collectors.joining(","));
}
props.setProperty("flyway." + prop.getKey(), value.toString());
});
return props;
}
@SuppressWarnings("unchecked")
private static Iterable<Command> commands(final Config config) {
Object value = config.getAnyRef("run");
List<String> commands = new ArrayList<>();
if (value instanceof List) {
commands.addAll((List<? extends String>) value);
} else {
commands.add(value.toString());
}
return commands.stream()
.map(command -> Command.valueOf(command.toLowerCase()))
.collect(Collectors.toList());
}
}