/** * 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()); } }