Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
58 changes: 55 additions & 3 deletions doc/src/asciidoc/module_dbflyway.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,54 @@ flyway:
<1> Sets `flyway.table` property, in this example, we override the new default value `flyway_schema_history` for
backward compatibility.

The `flyway` subsystem can also be opened against a database selected by a
jPOS-EE DB config modifier. This is useful for multi-tenant applications that
register per-tenant DB configurations at runtime:

[source,shell]
-------------
flyway --db entity-acme:tenant
-------------

For backward compatibility, a single positional argument is treated as the DB
config modifier, so the following is equivalent:

[source,shell]
-------------
flyway entity-acme:tenant
-------------

Entering the subsystem only records the target; it does not bootstrap Hibernate
metadata or create a `SessionFactory`. The Flyway commands resolve the JDBC
properties for the target DB and run Flyway directly.

The Flyway history table can be selected independently of the DB target:

[source,shell]
-------------
flyway --db entity-acme:tenant --table flyway_schema_history_jpts
-------------

or by using a prefix shorthand:

[source,shell]
-------------
flyway --db entity-acme:tenant --prefix jpts
-------------

The prefix shorthand maps `jpts` to `jpts_schema_history`. Use `--table` when an
application needs an exact history table name, such as
`flyway_schema_history_jpts`.

Inside the subsystem, commands are unchanged:

[source,shell]
-------------
info
migrate --out-of-order
validate
-------------

[NOTE]
======
By default, the `clean` command is disabled, unless `flyway.cleanDisabled: false` is placed in the environment.
Expand All @@ -46,14 +94,19 @@ The QBean descriptor looks like this:
[source,xml]
------------
<flyway class="org.jpos.flyway.FlywayService" logger="Q2">
<property name="out-of-order" value="true" /> <1>
<property name="config-modifier" value="entity-acme:tenant" /> <1>
<property name="table" value="flyway_schema_history_jpts" /> <2>
<property name="out-of-order" value="true" /> <3>
<commands>
info
migrate
</commands>
</flyway>
------------
<1> Optional property used by Flyway migrate.
<1> Optional DB config modifier. If omitted, Flyway uses the default DB configuration.
<2> Optional exact Flyway history table name. `table-prefix` is also available
and maps `foo` to `foo_schema_history`.
<3> Optional property used by Flyway migrate.


Valid commands are:
Expand All @@ -77,4 +130,3 @@ These Flyway commands use stdout for their output. We recommend to add
<property name="redirect" value="stdout, stderr" />
to your 00_logger.xml configuration in order to get unified logs.
======

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected void initService() {
String currentCommand = "init";
try {
FlywaySupport support = new FlywaySupport();
Flyway flyway = support.getFlyway(cfg.get("config-modifier", null),
Flyway flyway = support.getFlyway(FlywayTarget.fromConfiguration(cfg),
cfg.getBoolean("out-of-order") ? new String[] { "--out-of-order" } : new String[] {}
);
dbId = support.getDbId();
Expand Down
35 changes: 29 additions & 6 deletions modules/db-flyway/src/main/java/org/jpos/flyway/FlywaySupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ public class FlywaySupport implements LogCreator, Log {

private String dbId = "";

protected Flyway getFlyway(String configModifier, String args[]) {
protected Flyway getFlyway(FlywayTarget target, String args[]) {
LogFactory.setFallbackLogCreator(this);
Properties p = new DB(configModifier).getProperties();
Properties p = getDbProperties(target);

FluentConfiguration config = Flyway.configure()
.locations("classpath:db/migration")
Expand All @@ -50,16 +50,37 @@ protected Flyway getFlyway(String configModifier, String args[]) {
p.getProperty("hibernate.connection.username"),
p.getProperty("hibernate.connection.password"))
.outOfOrder(has(args, "--out-of-order"));
String historyTable = target.historyTable();
if (historyTable != null)
config.table(historyTable);
Flyway flyway = config.load();
this.dbId = resolveDbId(configModifier, flyway);
this.dbId = resolveDbId(target, flyway);
return flyway;
}

protected Flyway getFlyway(String configModifier, String args[]) {
return getFlyway(new FlywayTarget(configModifier, null, null), args);
}

public String getDbId() {
return dbId;
}

private String resolveDbId(String configModifier, Flyway flyway) {
private Properties getDbProperties(FlywayTarget target) {
try {
Properties p = DB.getProperties(target.configModifier());
if (p.getProperty("hibernate.connection.url") == null)
throw new IllegalArgumentException(
"Missing hibernate.connection.url for Flyway target " + target.label()
);
return p;
} catch (Exception e) {
throw new IllegalArgumentException("Unable to resolve Flyway DB properties for target "
+ target.label(), e);
}
}

private String resolveDbId(FlywayTarget target, Flyway flyway) {
String catalog = null;
try (Connection c = flyway.getConfiguration().getDataSource().getConnection()) {
catalog = c.getCatalog();
Expand All @@ -72,9 +93,11 @@ private String resolveDbId(String configModifier, Flyway flyway) {
catalog = extractDbFromUrl(flyway.getConfiguration().getUrl());
if (catalog == null || catalog.isEmpty())
catalog = "?";
return (configModifier != null && !configModifier.isEmpty())
? configModifier + "@" + catalog
String db = (target.configModifier() != null && !target.configModifier().isEmpty())
? target.configModifier() + "@" + catalog
: catalog;
String historyTable = target.historyTable();
return historyTable != null ? db + "/" + historyTable : db;
}

private String extractDbFromUrl(String url) {
Expand Down
93 changes: 93 additions & 0 deletions modules/db-flyway/src/main/java/org/jpos/flyway/FlywayTarget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2026 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.jpos.flyway;

import org.jpos.core.Configuration;

public record FlywayTarget(String configModifier, String table, String tablePrefix) {
public static final FlywayTarget DEFAULT = new FlywayTarget(null, null, null);

public static FlywayTarget fromCli(String[] args) {
String configModifier = null;
String table = null;
String tablePrefix = null;
for (int i = 1; i < args.length; i++) {
String arg = args[i];
if ("--db".equals(arg) || "--config-modifier".equals(arg)) {
configModifier = value(args, ++i, arg);
} else if (arg.startsWith("--db=")) {
configModifier = arg.substring("--db=".length());
} else if (arg.startsWith("--config-modifier=")) {
configModifier = arg.substring("--config-modifier=".length());
} else if ("--table".equals(arg)) {
table = value(args, ++i, arg);
} else if (arg.startsWith("--table=")) {
table = arg.substring("--table=".length());
} else if ("--prefix".equals(arg) || "--table-prefix".equals(arg)) {
tablePrefix = value(args, ++i, arg);
} else if (arg.startsWith("--prefix=")) {
tablePrefix = arg.substring("--prefix=".length());
} else if (arg.startsWith("--table-prefix=")) {
tablePrefix = arg.substring("--table-prefix=".length());
} else if (!arg.startsWith("--") && configModifier == null) {
configModifier = arg;
} else {
throw new IllegalArgumentException("Invalid flyway option '" + arg + "'");
}
}
return new FlywayTarget(blankToNull(configModifier), blankToNull(table), blankToNull(tablePrefix));
}

public static FlywayTarget fromConfiguration(Configuration cfg) {
return new FlywayTarget(
blankToNull(cfg.get("config-modifier", null)),
blankToNull(cfg.get("table", null)),
blankToNull(cfg.get("table-prefix", null))
);
}

public String historyTable() {
if (table != null)
return table;
return tablePrefix != null ? tablePrefix + "_schema_history" : null;
}

public String label() {
StringBuilder sb = new StringBuilder();
if (configModifier != null)
sb.append(configModifier);
String historyTable = historyTable();
if (historyTable != null) {
if (sb.length() > 0)
sb.append('/');
sb.append(historyTable);
}
return sb.length() == 0 ? "" : sb.toString();
}

private static String value(String[] args, int index, String option) {
if (index >= args.length)
throw new IllegalArgumentException("Missing value for " + option);
return args[index];
}

private static String blankToNull(String s) {
return s == null || s.isBlank() ? null : s;
}
}
29 changes: 9 additions & 20 deletions modules/db-flyway/src/main/java/org/jpos/q2/cli/FLYWAY.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,28 @@

package org.jpos.q2.cli;

import org.flywaydb.core.Flyway;
import org.jpos.ee.DB;
import org.jpos.flyway.FlywayTarget;
import org.jpos.q2.CLIContext;
import org.jpos.q2.CLISubSystem;

import java.util.Properties;

public class FLYWAY implements CLISubSystem {
public static final String PREFIX = "flyway.dbmodifier";
public static final String TARGET = "flyway.target";

@Override
public String getPrompt(CLIContext ctx, String[] args) {
String prefix = null;
if (args.length > 1) {
prefix = args[1];
ctx.getUserData().put(PREFIX, prefix);
} else {
ctx.getUserData().remove(PREFIX);
}
new DB(prefix); // force DB initialization
return "flyway" + (prefix != null ? "[" + args[1] + "]" : "") + "> ";
FlywayTarget target = FlywayTarget.fromCli(args);
ctx.getUserData().put(TARGET, target);
String label = target.label();
return "flyway" + (!label.isEmpty() ? "[" + label + "]" : "") + "> ";
}

@Override
public String[] getCompletionPrefixes(CLIContext ctx, String[] args) {
return new String[] { "org.jpos.q2.cli.flyway." };
}

private Flyway getFlyWay() {
Properties p = new DB().getProperties();
return Flyway.configure().dataSource(
p.getProperty("hibernate.connection.url"),
p.getProperty("hibernate.connection.username"),
p.getProperty("hibernate.connection.password")).load();
public static FlywayTarget getTarget(CLIContext ctx) {
FlywayTarget target = (FlywayTarget) ctx.getUserData().get(TARGET);
return target != null ? target : FlywayTarget.DEFAULT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class BASELINE extends FlywaySupport implements CLICommand{
@Override
public void exec(CLIContext cli, String[] args) {
try {
getFlyway((String) cli.getUserData().get(FLYWAY.PREFIX), args).baseline();
getFlyway(FLYWAY.getTarget(cli), args).baseline();
} catch (Exception e) {
cli.println(e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void exec(CLIContext cli, String[] args) {

if (superSure) {
try {
getFlyway((String) cli.getUserData().get(FLYWAY.PREFIX), args).clean();
getFlyway(FLYWAY.getTarget(cli), args).clean();
} catch (FlywayException e) {
cli.println (e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class INFO extends FlywaySupport implements CLICommand{
@Override
public void exec(CLIContext cli, String[] args) {
try {
Flyway flyway = getFlyway((String) cli.getUserData().get(FLYWAY.PREFIX), args);
Flyway flyway = getFlyway(FLYWAY.getTarget(cli), args);
MigrationInfoService info = flyway.info();
MigrationInfo current = info.current();
MigrationVersion currentSchemaVersion = current == null ? MigrationVersion.EMPTY : current.getVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class MIGRATE extends FlywaySupport implements CLICommand{
@Override
public void exec(CLIContext cli, String[] args) {
try {
Flyway flyway = getFlyway((String) cli.getUserData().get(FLYWAY.PREFIX), args);
Flyway flyway = getFlyway(FLYWAY.getTarget(cli), args);
var result = flyway.migrate();
cli.println ("Applied " + result.migrationsExecuted + " migration(s)");
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class REPAIR extends FlywaySupport implements CLICommand{
@Override
public void exec(CLIContext cli, String[] args) {
try {
getFlyway((String) cli.getUserData().get(FLYWAY.PREFIX), args).repair();
getFlyway(FLYWAY.getTarget(cli), args).repair();
} catch (Exception e) {
cli.println(e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class VALIDATE extends FlywaySupport implements CLICommand{
@Override
public void exec(CLIContext cli, String[] args) {
try {
getFlyway((String) cli.getUserData().get(FLYWAY.PREFIX), args).validate();
getFlyway(FLYWAY.getTarget(cli), args).validate();
} catch (Exception e) {
cli.println(e.getMessage());
}
Expand Down
Loading
Loading