Compare commits

...

4 Commits

Author SHA1 Message Date
Primož Delopst ea393b5fc7
Merge a6dea96fb5 into 3f6d1eb29b 2024-09-26 02:08:11 +00:00
Thomas Papke 3f6d1eb29b
#5768 Upgrade to latest simple-java-mail (#6261) 2024-09-26 02:07:27 +00:00
Tadgh 377e44b6ca
attribution and pom change (#6309) 2024-09-25 20:38:22 +00:00
Primož Delopst a6dea96fb5 Fix primary key migration in case multiple schemas on the same database 2024-09-16 19:26:16 +02:00
12 changed files with 146 additions and 133 deletions

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6290
title: "Previously, a specific migration task was using the `TRIM()` function, which does not exist in MSSQL 2012. This was causing migrations targeting MSSQL 2012 to fail.
This has been corrected and replaced with usage of a combination of LTRIM() and RTRIM(). Thanks to Primož Delopst at Better for the contribution!"

View File

@ -70,6 +70,11 @@
<artifactId>jakarta.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<optional>true</optional>
</dependency>
<!-- test dependencies --> <!-- test dependencies -->
<dependency> <dependency>

View File

@ -28,8 +28,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.mail.internet.InternetAddress; import jakarta.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;

View File

@ -17,8 +17,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.mail.internet.InternetAddress; import jakarta.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import java.util.Arrays; import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

View File

@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.mail.internet.InternetAddress; import jakarta.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;

View File

@ -79,25 +79,11 @@
<dependency> <dependency>
<groupId>org.simplejavamail</groupId> <groupId>org.simplejavamail</groupId>
<artifactId>simple-java-mail</artifactId> <artifactId>simple-java-mail</artifactId>
<!-- Excluded in favor of jakarta.activation:jakarta.activation-api -->
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.icegreen</groupId> <groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId> <artifactId>greenmail</artifactId>
<scope>compile</scope> <scope>compile</scope>
<!-- Excluded in favor of jakarta.activation:jakarta.activation-api -->
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -21,9 +21,9 @@ package ca.uhn.fhir.rest.server.mail;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Email;
import org.simplejavamail.api.mailer.AsyncResponse;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
public interface IMailSvc { public interface IMailSvc {
void sendMail(@Nonnull List<Email> theEmails); void sendMail(@Nonnull List<Email> theEmails);
@ -31,7 +31,5 @@ public interface IMailSvc {
void sendMail(@Nonnull Email theEmail); void sendMail(@Nonnull Email theEmail);
void sendMail( void sendMail(
@Nonnull Email theEmail, @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler);
@Nonnull Runnable theOnSuccess,
@Nonnull AsyncResponse.ExceptionConsumer theErrorHandler);
} }

View File

@ -20,12 +20,9 @@
package ca.uhn.fhir.rest.server.mail; package ca.uhn.fhir.rest.server.mail;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.Validate;
import org.simplejavamail.MailException; import org.simplejavamail.MailException;
import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Email;
import org.simplejavamail.api.email.Recipient; import org.simplejavamail.api.email.Recipient;
import org.simplejavamail.api.mailer.AsyncResponse;
import org.simplejavamail.api.mailer.AsyncResponse.ExceptionConsumer;
import org.simplejavamail.api.mailer.Mailer; import org.simplejavamail.api.mailer.Mailer;
import org.simplejavamail.api.mailer.config.TransportStrategy; import org.simplejavamail.api.mailer.config.TransportStrategy;
import org.simplejavamail.mailer.MailerBuilder; import org.simplejavamail.mailer.MailerBuilder;
@ -33,6 +30,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class MailSvc implements IMailSvc { public class MailSvc implements IMailSvc {
@ -42,14 +41,14 @@ public class MailSvc implements IMailSvc {
private final Mailer myMailer; private final Mailer myMailer;
public MailSvc(@Nonnull MailConfig theMailConfig) { public MailSvc(@Nonnull MailConfig theMailConfig) {
Validate.notNull(theMailConfig); Objects.requireNonNull(theMailConfig);
myMailConfig = theMailConfig; myMailConfig = theMailConfig;
myMailer = makeMailer(myMailConfig); myMailer = makeMailer(myMailConfig);
} }
@Override @Override
public void sendMail(@Nonnull List<Email> theEmails) { public void sendMail(@Nonnull List<Email> theEmails) {
Validate.notNull(theEmails); Objects.requireNonNull(theEmails);
theEmails.forEach(theEmail -> send(theEmail, new OnSuccess(theEmail), new ErrorHandler(theEmail))); theEmails.forEach(theEmail -> send(theEmail, new OnSuccess(theEmail), new ErrorHandler(theEmail)));
} }
@ -60,21 +59,23 @@ public class MailSvc implements IMailSvc {
@Override @Override
public void sendMail( public void sendMail(
@Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull ExceptionConsumer theErrorHandler) { @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler) {
send(theEmail, theOnSuccess, theErrorHandler); send(theEmail, theOnSuccess, theErrorHandler);
} }
private void send( private void send(
@Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull ExceptionConsumer theErrorHandler) { @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler) {
Validate.notNull(theEmail); Objects.requireNonNull(theEmail);
Validate.notNull(theOnSuccess); Objects.requireNonNull(theOnSuccess);
Validate.notNull(theErrorHandler); Objects.requireNonNull(theErrorHandler);
try { try {
final AsyncResponse asyncResponse = myMailer.sendMail(theEmail, true); myMailer.sendMail(theEmail, true).whenComplete((result, ex) -> {
if (asyncResponse != null) { if (ex != null) {
asyncResponse.onSuccess(theOnSuccess); theErrorHandler.accept(ex);
asyncResponse.onException(theErrorHandler); } else {
} theOnSuccess.run();
}
});
} catch (MailException e) { } catch (MailException e) {
theErrorHandler.accept(e); theErrorHandler.accept(e);
} }
@ -117,7 +118,7 @@ public class MailSvc implements IMailSvc {
} }
} }
private class ErrorHandler implements ExceptionConsumer { private class ErrorHandler implements Consumer<Throwable> {
private final Email myEmail; private final Email myEmail;
private ErrorHandler(@Nonnull Email theEmail) { private ErrorHandler(@Nonnull Email theEmail) {
@ -125,7 +126,7 @@ public class MailSvc implements IMailSvc {
} }
@Override @Override
public void accept(Exception t) { public void accept(Throwable t) {
ourLog.error("Email not sent" + makeMessage(myEmail), t); ourLog.error("Email not sent" + makeMessage(myEmail), t);
} }
} }

View File

@ -4,6 +4,7 @@ import com.icegreen.greenmail.junit5.GreenMailExtension;
import com.icegreen.greenmail.util.GreenMailUtil; import com.icegreen.greenmail.util.GreenMailUtil;
import com.icegreen.greenmail.util.ServerSetupTest; import com.icegreen.greenmail.util.ServerSetupTest;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
@ -11,7 +12,6 @@ import org.simplejavamail.MailException;
import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Email;
import org.simplejavamail.email.EmailBuilder; import org.simplejavamail.email.EmailBuilder;
import javax.mail.internet.MimeMessage;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -86,13 +86,14 @@ public class MailSvcIT {
@Test @Test
public void testSendMailWithInvalidToAddressExpectErrorHandler() { public void testSendMailWithInvalidToAddressExpectErrorHandler() {
// setup // setup
final Email email = withEmail("xyz"); String invalidEmailAdress = "xyz";
final Email email = withEmail(invalidEmailAdress);
// execute // execute
fixture.sendMail(email, fixture.sendMail(email,
() -> fail("Should not execute on Success"), () -> fail("Should not execute on Success"),
(e) -> { (e) -> {
assertTrue(e instanceof MailException); assertTrue(e instanceof MailException);
assertEquals("Invalid TO address: " + email, e.getMessage()); assertEquals("Invalid TO address: " + invalidEmailAdress, e.getMessage());
}); });
// validate // validate
assertTrue(ourGreenMail.waitForIncomingEmail(1000, 0)); assertTrue(ourGreenMail.waitForIncomingEmail(1000, 0));

View File

@ -24,9 +24,11 @@ import jakarta.annotation.Nonnull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* Migration task that handles cross-database logic for adding a new primary key. * Migration task that handles cross-database logic for adding a new primary key.
@ -37,7 +39,7 @@ public class AddPrimaryKeyTask extends BaseTableTask {
private final List<String> myPrimaryKeyColumnsInOrder; private final List<String> myPrimaryKeyColumnsInOrder;
public AddPrimaryKeyTask( public AddPrimaryKeyTask(
String theProductVersion, String theSchemaVersion, String theTableName, String... theColumnsInOrder) { String theProductVersion, String theSchemaVersion, String theTableName, String... theColumnsInOrder) {
super(theProductVersion, theSchemaVersion); super(theProductVersion, theSchemaVersion);
setTableName(theTableName); setTableName(theTableName);
@ -46,32 +48,39 @@ public class AddPrimaryKeyTask extends BaseTableTask {
@Nonnull @Nonnull
private String generateSql() { private String generateSql() {
switch (getDriverType()) { try (Connection connection = getConnectionProperties().getDataSource().getConnection()) {
case MYSQL_5_7: switch (getDriverType()) {
case MARIADB_10_1: case MYSQL_5_7:
case POSTGRES_9_4: case MARIADB_10_1:
case DERBY_EMBEDDED: case POSTGRES_9_4:
case H2_EMBEDDED: case DERBY_EMBEDDED:
case ORACLE_12C: case H2_EMBEDDED:
case MSSQL_2012: case ORACLE_12C:
case COCKROACHDB_21_1: case MSSQL_2012:
return String.format( case COCKROACHDB_21_1:
return String.format(
"ALTER TABLE %s ADD PRIMARY KEY (%s)", "ALTER TABLE %s ADD PRIMARY KEY (%s)",
getTableName(), String.join(", ", myPrimaryKeyColumnsInOrder)); Optional.of(connection.getSchema())
default: .map(schema -> String.format("%s.%s", schema, getTableName()))
throw new IllegalStateException(String.format( .orElse(getTableName()),
String.join(", ", myPrimaryKeyColumnsInOrder));
default:
throw new IllegalStateException(String.format(
"%s Unknown driver type. Cannot add primary key for task %s", "%s Unknown driver type. Cannot add primary key for task %s",
Msg.code(2531), getMigrationVersion())); Msg.code(2531), getMigrationVersion()));
}
} catch (SQLException e) {
throw new IllegalStateException(e);
} }
} }
@Override @Override
protected void doExecute() throws SQLException { protected void doExecute() throws SQLException {
logInfo( logInfo(
ourLog, ourLog,
"Going to add a primary key on table {} for columns {}", "Going to add a primary key on table {} for columns {}",
getTableName(), getTableName(),
myPrimaryKeyColumnsInOrder); myPrimaryKeyColumnsInOrder);
executeSql(getTableName(), generateSql()); executeSql(getTableName(), generateSql());
} }

View File

@ -20,14 +20,17 @@
package ca.uhn.fhir.jpa.migrate.taskdef; package ca.uhn.fhir.jpa.migrate.taskdef;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import com.google.common.collect.ImmutableList;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.Language;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Optional;
/** /**
* Migration task that handles cross-database logic for dropping a primary key. * Migration task that handles cross-database logic for dropping a primary key.
@ -52,27 +55,13 @@ public class DropPrimaryKeyTask extends BaseTableTask {
@Nullable @Nullable
@Language("SQL") @Language("SQL")
final String primaryKeyNameSql = generatePrimaryKeyNameSql(); final String primaryKeyName = getPrimaryKeyName();
@Nullable
final String primaryKeyName = primaryKeyNameSql != null
? newJdbcTemplate()
.queryForObject(primaryKeyNameSql, String.class, getTableNameWithDatabaseExpectedCase())
: null;
ourLog.debug("primaryKeyName: {} for driver: {}", primaryKeyName, getDriverType()); ourLog.debug("primaryKeyName: {} for driver: {}", primaryKeyName, getDriverType());
return generateDropPrimaryKeySql(primaryKeyName); return generateDropPrimaryKeySql(primaryKeyName);
} }
private String getTableNameWithDatabaseExpectedCase() {
if (DriverTypeEnum.ORACLE_12C == getDriverType()) {
return getTableName().toUpperCase();
}
return getTableName().toLowerCase();
}
@Override @Override
protected void doExecute() throws SQLException { protected void doExecute() throws SQLException {
logInfo(ourLog, "Going to DROP the PRIMARY KEY on table {}", getTableName()); logInfo(ourLog, "Going to DROP the PRIMARY KEY on table {}", getTableName());
@ -81,56 +70,63 @@ public class DropPrimaryKeyTask extends BaseTableTask {
} }
private String generateDropPrimaryKeySql(@Nullable String thePrimaryKeyName) { private String generateDropPrimaryKeySql(@Nullable String thePrimaryKeyName) {
switch (getDriverType()) { try (Connection connection = getConnectionProperties().getDataSource().getConnection()) {
case MARIADB_10_1: switch (getDriverType()) {
case DERBY_EMBEDDED: case MARIADB_10_1:
case H2_EMBEDDED: case DERBY_EMBEDDED:
@Language("SQL") case H2_EMBEDDED:
final String sqlH2 = "ALTER TABLE %s DROP PRIMARY KEY"; @Language("SQL")
return String.format(sqlH2, getTableName()); final String sqlH2 = "ALTER TABLE %s DROP PRIMARY KEY";
case POSTGRES_9_4: return String.format(
case ORACLE_12C: sqlH2,
case MSSQL_2012: Optional.of(connection.getSchema())
case MYSQL_5_7: .map(schema -> String.format("%s.%s", schema, getTableName()))
assert thePrimaryKeyName != null; .orElse(getTableName()));
@Language("SQL") case POSTGRES_9_4:
final String sql = "ALTER TABLE %s DROP CONSTRAINT %s"; case ORACLE_12C:
return String.format(sql, getTableName(), thePrimaryKeyName); case MSSQL_2012:
default: case MYSQL_5_7:
throw new IllegalStateException(String.format( assert thePrimaryKeyName != null;
@Language("SQL")
final String sql = "ALTER TABLE %s DROP CONSTRAINT %s";
return String.format(
sql,
Optional.of(connection.getSchema())
.map(schema -> String.format("%s.%s", schema, getTableName()))
.orElse(getTableName()),
thePrimaryKeyName);
default:
throw new IllegalStateException(String.format(
"%s Unknown driver type: %s. Cannot drop primary key: %s for task %s", "%s Unknown driver type: %s. Cannot drop primary key: %s for task %s",
Msg.code(2529), getDriverType(), getMigrationVersion(), getTableName())); Msg.code(2529), getDriverType(), getMigrationVersion(), getTableName()));
}
} catch (SQLException e) {
throw new IllegalStateException(e);
} }
} }
@Language("SQL") @SuppressWarnings({"NestedTryStatement", "MethodWithMultipleLoops"})
@Nullable @Nullable
private String generatePrimaryKeyNameSql() { private String getPrimaryKeyName() {
switch (getDriverType()) { String primaryKey = null;
case MYSQL_5_7: try (Connection connection = getConnectionProperties().getDataSource().getConnection()) {
case MARIADB_10_1: for (String tableName : ImmutableList.of(
case DERBY_EMBEDDED: getTableName().toLowerCase(), getTableName().toUpperCase())) {
case COCKROACHDB_21_1: try (ResultSet resultSet = connection
case H2_EMBEDDED: .getMetaData()
return null; // Irrelevant: We don't need to run the SQL for these databases. .getPrimaryKeys(connection.getCatalog(), connection.getSchema(), tableName)) {
case POSTGRES_9_4: while (resultSet.next()) {
return "SELECT constraint_name " + "FROM information_schema.table_constraints " primaryKey = resultSet.getString(6);
+ "WHERE table_schema = 'public' " }
+ "AND constraint_type = 'PRIMARY KEY' " } catch (SQLException e) {
+ "AND table_name = ?"; throw new IllegalStateException(e);
case ORACLE_12C: }
return "SELECT constraint_name " + "FROM user_constraints " }
+ "WHERE constraint_type = 'P' " } catch (SQLException e) {
+ "AND table_name = ?"; throw new IllegalStateException(e);
case MSSQL_2012:
return "SELECT tc.constraint_name " + "FROM information_schema.table_constraints tc "
+ "JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name "
+ "WHERE tc.constraint_type = 'PRIMARY KEY' "
+ "AND tc.table_name = ?";
default:
throw new IllegalStateException(String.format(
"%s Unknown driver type: %s Cannot find primary key to drop for task %s",
Msg.code(2530), getDriverType(), getMigrationVersion()));
} }
return primaryKey;
} }
} }

34
pom.xml
View File

@ -869,6 +869,7 @@
<developer> <developer>
<id>delopst</id> <id>delopst</id>
<name>Primož Delopst</name> <name>Primož Delopst</name>
<organization>Better</organization>
</developer> </developer>
<developer> <developer>
<id>Zach Smith</id> <id>Zach Smith</id>
@ -1160,27 +1161,38 @@
<dependency> <dependency>
<groupId>org.simplejavamail</groupId> <groupId>org.simplejavamail</groupId>
<artifactId>simple-java-mail</artifactId> <artifactId>simple-java-mail</artifactId>
<version>6.6.1</version> <version>8.11.2</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>com.sun.activation</groupId> <groupId>com.github.bbottema</groupId>
<artifactId>jakarta.activation-api</artifactId> <artifactId>jetbrains-runtime-annotations</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>com.sun.activation</groupId> <groupId>jakarta.mail</groupId>
<artifactId>jakarta.activation</artifactId> <artifactId>jakarta.mail-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>2.1.0-rc-1</version>
<exclusions>
<exclusion>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.6.4</version>
</dependency>
<dependency> <dependency>
<groupId>com.icegreen</groupId> <groupId>com.icegreen</groupId>
<artifactId>greenmail-junit5</artifactId> <artifactId>greenmail-junit5</artifactId>
<version>1.6.4</version> <version>2.1.0-rc-1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- mail end --> <!-- mail end -->