Nullability refinements and related polishing

This commit is contained in:
Juergen Hoeller 2020-06-23 16:55:09 +02:00
parent 56c661829b
commit d0209e5f1f
15 changed files with 206 additions and 350 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,6 +39,7 @@ import org.springframework.lang.Nullable;
* Particularly appropriate for waiting on a slowly starting Oracle database.
*
* @author Juergen Hoeller
* @author Marten Deinum
* @since 18.12.2003
*/
public class DatabaseStartupValidator implements InitializingBean {
@ -59,12 +60,7 @@ public class DatabaseStartupValidator implements InitializingBean {
@Nullable
private DataSource dataSource;
/**
* The query used to validate the connection
* @deprecated in favor of JDBC 4.0 connection validation
*/
@Nullable
@Deprecated
private String validationQuery;
private int interval = DEFAULT_INTERVAL;
@ -81,8 +77,7 @@ public class DatabaseStartupValidator implements InitializingBean {
/**
* Set the SQL query string to use for validation.
*
* @deprecated in favor of the JDBC 4.0 connection validation
* @deprecated as of 5.3, in favor of the JDBC 4.0 connection validation
*/
@Deprecated
public void setValidationQuery(String validationQuery) {
@ -148,10 +143,9 @@ public class DatabaseStartupValidator implements InitializingBean {
logger.debug("Validation query [" + this.validationQuery + "] threw exception", ex);
}
else {
logger.debug(" Validation threw exception", ex);
logger.debug("Validation check threw exception", ex);
}
}
if (logger.isInfoEnabled()) {
float rest = ((float) (deadLine - System.currentTimeMillis())) / 1000;
if (rest > this.interval) {

View File

@ -80,7 +80,6 @@ public abstract class ConnectionFactoryUtils {
* Translates exceptions into the Spring hierarchy of unchecked generic
* data access exceptions, simplifying calling code and making any
* exception that is thrown more meaningful.
*
* <p>Is aware of a corresponding Connection bound to the current
* {@link TransactionSynchronizationManager}. Will bind a Connection to the
* {@link TransactionSynchronizationManager} if transaction synchronization is active.
@ -99,7 +98,6 @@ public abstract class ConnectionFactoryUtils {
/**
* Actually obtain a R2DBC Connection from the given {@link ConnectionFactory}.
* Same as {@link #getConnection}, but preserving the original exceptions.
*
* <p>Is aware of a corresponding Connection bound to the current
* {@link TransactionSynchronizationManager}. Will bind a Connection to the
* {@link TransactionSynchronizationManager} if transaction synchronization is active
@ -193,11 +191,9 @@ public abstract class ConnectionFactoryUtils {
* @param connectionFactory the {@link ConnectionFactory} that the Connection was obtained from
* @see #doGetConnection
*/
public static Mono<Void> doReleaseConnection(Connection connection,
ConnectionFactory connectionFactory) {
public static Mono<Void> doReleaseConnection(Connection connection, ConnectionFactory connectionFactory) {
return TransactionSynchronizationManager.forCurrentTransaction()
.flatMap(synchronizationManager -> {
ConnectionHolder conHolder = (ConnectionHolder) synchronizationManager.getResource(connectionFactory);
if (conHolder != null && connectionEquals(conHolder, connection)) {
// It's the transactional Connection: Don't close it.
@ -235,7 +231,6 @@ public abstract class ConnectionFactoryUtils {
* @return the corresponding DataAccessException instance
*/
public static DataAccessException convertR2dbcException(String task, @Nullable String sql, R2dbcException ex) {
if (ex instanceof R2dbcTransientException) {
if (ex instanceof R2dbcTransientResourceException) {
return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);
@ -247,7 +242,6 @@ public abstract class ConnectionFactoryUtils {
return new QueryTimeoutException(buildMessage(task, sql, ex), ex);
}
}
if (ex instanceof R2dbcNonTransientException) {
if (ex instanceof R2dbcNonTransientResourceException) {
return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
@ -262,7 +256,6 @@ public abstract class ConnectionFactoryUtils {
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
}
}
return new UncategorizedR2dbcException(buildMessage(task, sql, ex), sql, ex);
}
@ -326,7 +319,6 @@ public abstract class ConnectionFactoryUtils {
* @see #CONNECTION_SYNCHRONIZATION_ORDER
*/
private static int getConnectionSynchronizationOrder(ConnectionFactory connectionFactory) {
int order = CONNECTION_SYNCHRONIZATION_ORDER;
ConnectionFactory current = connectionFactory;
while (current instanceof DelegatingConnectionFactory) {
@ -336,6 +328,7 @@ public abstract class ConnectionFactoryUtils {
return order;
}
/**
* Callback for resource cleanup at the end of a non-native R2DBC transaction.
*/
@ -355,7 +348,6 @@ public abstract class ConnectionFactoryUtils {
this.order = getConnectionSynchronizationOrder(connectionFactory);
}
@Override
public int getOrder() {
return this.order;
@ -387,7 +379,8 @@ public abstract class ConnectionFactoryUtils {
public Mono<Void> resume() {
if (this.holderActive) {
return TransactionSynchronizationManager.forCurrentTransaction()
.doOnNext(synchronizationManager -> synchronizationManager.bindResource(this.connectionFactory, this.connectionHolder))
.doOnNext(synchronizationManager ->
synchronizationManager.bindResource(this.connectionFactory, this.connectionHolder))
.then();
}
return Mono.empty();
@ -401,8 +394,7 @@ public abstract class ConnectionFactoryUtils {
// to avoid issues with strict transaction implementations that expect
// the close call before transaction completion.
if (!this.connectionHolder.isOpen()) {
return TransactionSynchronizationManager.forCurrentTransaction()
.flatMap(synchronizationManager -> {
return TransactionSynchronizationManager.forCurrentTransaction().flatMap(synchronizationManager -> {
synchronizationManager.unbindResource(this.connectionFactory);
this.holderActive = false;
if (this.connectionHolder.hasConnection()) {
@ -439,4 +431,5 @@ public abstract class ConnectionFactoryUtils {
return Mono.empty();
}
}
}

View File

@ -65,7 +65,6 @@ public class ConnectionHolder extends ResourceHolderSupport {
* in an ongoing transaction
*/
public ConnectionHolder(Connection connection, boolean transactionActive) {
this.currentConnection = connection;
this.transactionActive = transactionActive;
}
@ -80,7 +79,6 @@ public class ConnectionHolder extends ResourceHolderSupport {
/**
* Set whether this holder represents an active, R2DBC-managed transaction.
*
* @see R2dbcTransactionManager
*/
protected void setTransactionActive(boolean transactionActive) {
@ -111,7 +109,6 @@ public class ConnectionHolder extends ResourceHolderSupport {
* @see #released()
*/
public Connection getConnection() {
Assert.notNull(this.currentConnection, "Active Connection is required");
return this.currentConnection;
}

View File

@ -81,22 +81,21 @@ import org.springframework.util.Assert;
@SuppressWarnings("serial")
public class R2dbcTransactionManager extends AbstractReactiveTransactionManager implements InitializingBean {
@Nullable
private ConnectionFactory connectionFactory;
private boolean enforceReadOnly = false;
/**
* Create a new @link ConnectionFactoryTransactionManager} instance. A ConnectionFactory has to be set to be able to
* use it.
*
* Create a new @link ConnectionFactoryTransactionManager} instance.
* A ConnectionFactory has to be set to be able to use it.
* @see #setConnectionFactory
*/
public R2dbcTransactionManager() {}
/**
* Create a new {@link R2dbcTransactionManager} instance.
*
* @param connectionFactory the R2DBC ConnectionFactory to manage transactions for
*/
public R2dbcTransactionManager(ConnectionFactory connectionFactory) {
@ -108,19 +107,10 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* Set the R2DBC {@link ConnectionFactory} that this instance should manage transactions for.
* <p>
* This will typically be a locally defined {@link ConnectionFactory}, for example an connection pool.
* <p>
* The {@link ConnectionFactory} specified here should be the target {@link ConnectionFactory} to manage transactions
* for, not a TransactionAwareConnectionFactoryProxy. Only data access code may work with
* TransactionAwareConnectionFactoryProxy, while the transaction manager needs to work on the underlying target
* {@link ConnectionFactory}. If there's nevertheless a TransactionAwareConnectionFactoryProxy passed in, it will be
* unwrapped to extract its target {@link ConnectionFactory}.
* <p>
* <b>The {@link ConnectionFactory} passed in here needs to return independent {@link Connection}s.</b> The
* {@link Connection}s may come from a pool (the typical case), but the {@link ConnectionFactory} must not return
* scoped {@link Connection} or the like.
*
* <p>This will typically be a locally defined {@link ConnectionFactory}, for example an connection pool.
* <p><b>The {@link ConnectionFactory} passed in here needs to return independent {@link Connection}s.</b>
* The {@link Connection}s may come from a pool (the typical case), but the {@link ConnectionFactory}
* must not return scoped {@link Connection}s or the like.
* @see TransactionAwareConnectionFactoryProxy
*/
public void setConnectionFactory(@Nullable ConnectionFactory connectionFactory) {
@ -137,7 +127,6 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* Obtain the {@link ConnectionFactory} for actual use.
*
* @return the {@link ConnectionFactory} (never {@code null})
* @throws IllegalStateException in case of no ConnectionFactory set
*/
@ -149,12 +138,11 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* Specify whether to enforce the read-only nature of a transaction (as indicated by
* {@link TransactionDefinition#isReadOnly()} through an explicit statement on the transactional connection: "SET
* TRANSACTION READ ONLY" as understood by Oracle, MySQL and Postgres.
* <p>
* The exact treatment, including any SQL statement executed on the connection, can be customized through through
* {@link #prepareTransactionalConnection}.
*
* {@link TransactionDefinition#isReadOnly()} through an explicit statement on the
* transactional connection: "SET TRANSACTION READ ONLY" as understood by Oracle,
* MySQL and Postgres.
* <p>The exact treatment, including any SQL statement executed on the connection,
* can be customized through through {@link #prepareTransactionalConnection}.
* @see #prepareTransactionalConnection
*/
public void setEnforceReadOnly(boolean enforceReadOnly) {
@ -162,9 +150,8 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
/**
* Return whether to enforce the read-only nature of a transaction through an explicit statement on the transactional
* connection.
*
* Return whether to enforce the read-only nature of a transaction through an
* explicit statement on the transactional connection.
* @see #setEnforceReadOnly
*/
public boolean isEnforceReadOnly() {
@ -179,8 +166,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
@Override
protected Object doGetTransaction(TransactionSynchronizationManager synchronizationManager)
throws TransactionException {
protected Object doGetTransaction(TransactionSynchronizationManager synchronizationManager) throws TransactionException {
ConnectionFactoryTransactionObject txObject = new ConnectionFactoryTransactionObject();
ConnectionHolder conHolder = (ConnectionHolder) synchronizationManager.getResource(obtainConnectionFactory());
txObject.setConnectionHolder(conHolder, false);
@ -196,17 +182,15 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
@Override
protected Mono<Void> doBegin(TransactionSynchronizationManager synchronizationManager, Object transaction,
TransactionDefinition definition) throws TransactionException {
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction;
return Mono.defer(() -> {
Mono<Connection> connectionMono;
if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Mono<Connection> newCon = Mono.from(obtainConnectionFactory().create());
connectionMono = newCon.doOnNext(connection -> {
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for R2DBC transaction");
}
@ -219,22 +203,18 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
return connectionMono.flatMap(con -> {
return prepareTransactionalConnection(con, definition, transaction).then(Mono.from(con.beginTransaction()))
.doOnSuccess(v -> {
txObject.getConnectionHolder().setTransactionActive(true);
Duration timeout = determineTimeout(definition);
if (!timeout.isNegative() && !timeout.isZero()) {
txObject.getConnectionHolder().setTimeoutInMillis(timeout.toMillis());
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
synchronizationManager.bindResource(obtainConnectionFactory(), txObject.getConnectionHolder());
}
}).thenReturn(con).onErrorResume(e -> {
if (txObject.isNewConnectionHolder()) {
return ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory())
.doOnTerminate(() -> txObject.setConnectionHolder(null, false))
@ -243,20 +223,17 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
return Mono.error(e);
});
}).onErrorResume(e -> {
CannotCreateTransactionException ex = new CannotCreateTransactionException(
"Could not open R2DBC Connection for transaction",
e);
"Could not open R2DBC Connection for transaction", e);
return Mono.error(ex);
});
}).then();
}
/**
* Determine the actual timeout to use for the given definition. Will fall back to this manager's default timeout if
* the transaction definition doesn't specify a non-default value.
*
* Determine the actual timeout to use for the given definition.
* Will fall back to this manager's default timeout if the
* transaction definition doesn't specify a non-default value.
* @param definition the transaction definition
* @return the actual timeout to use
* @see org.springframework.transaction.TransactionDefinition#getTimeout()
@ -280,14 +257,11 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
@Override
protected Mono<Void> doResume(TransactionSynchronizationManager synchronizationManager, Object transaction,
Object suspendedResources) throws TransactionException {
protected Mono<Void> doResume(TransactionSynchronizationManager synchronizationManager,
@Nullable Object transaction, Object suspendedResources) throws TransactionException {
return Mono.defer(() -> {
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction;
txObject.setConnectionHolder(null);
synchronizationManager.bindResource(obtainConnectionFactory(), suspendedResources);
return Mono.empty();
});
}
@ -301,7 +275,6 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
if (status.isDebug()) {
logger.debug("Committing R2DBC transaction on Connection [" + connection + "]");
}
return Mono.from(connection.commitTransaction())
.onErrorMap(R2dbcException.class, ex -> translateException("R2DBC commit", ex));
}
@ -315,7 +288,6 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
if (status.isDebug()) {
logger.debug("Rolling back R2DBC transaction on Connection [" + connection + "]");
}
return Mono.from(connection.rollbackTransaction())
.onErrorMap(R2dbcException.class, ex -> translateException("R2DBC rollback", ex));
}
@ -326,10 +298,9 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
return Mono.fromRunnable(() -> {
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger
.debug("Setting R2DBC transaction [" + txObject.getConnectionHolder().getConnection() + "] rollback-only");
logger.debug("Setting R2DBC transaction [" + txObject.getConnectionHolder().getConnection() +
"] rollback-only");
}
txObject.setRollbackOnly();
});
@ -380,20 +351,19 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* Prepare the transactional {@link Connection} right after transaction begin.
* <p>
* The default implementation executes a "SET TRANSACTION READ ONLY" statement if the {@link #setEnforceReadOnly
* "enforceReadOnly"} flag is set to {@code true} and the transaction definition indicates a read-only transaction.
* <p>
* The "SET TRANSACTION READ ONLY" is understood by Oracle, MySQL and Postgres and may work with other databases as
* well. If you'd like to adapt this treatment, override this method accordingly.
*
* <p>The default implementation executes a "SET TRANSACTION READ ONLY" statement if the
* {@link #setEnforceReadOnly "enforceReadOnly"} flag is set to {@code true} and the
* transaction definition indicates a read-only transaction.
* <p>The "SET TRANSACTION READ ONLY" is understood by Oracle, MySQL and Postgres
* and may work with other databases as well. If you'd like to adapt this treatment,
* override this method accordingly.
* @param con the transactional R2DBC Connection
* @param definition the current transaction definition
* @param transaction the transaction object
* @see #setEnforceReadOnly
*/
protected Mono<Void> prepareTransactionalConnection(Connection con, TransactionDefinition definition,
Object transaction) {
protected Mono<Void> prepareTransactionalConnection(
Connection con, TransactionDefinition definition, Object transaction) {
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction;
@ -410,8 +380,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
if (isolationLevelToUse != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
if (logger.isDebugEnabled()) {
logger
.debug("Changing isolation level of R2DBC Connection [" + con + "] to " + isolationLevelToUse.asSql());
logger.debug("Changing isolation level of R2DBC Connection [" + con + "] to " + isolationLevelToUse.asSql());
}
IsolationLevel currentIsolation = con.getTransactionIsolationLevel();
if (!currentIsolation.asSql().equalsIgnoreCase(isolationLevelToUse.asSql())) {
@ -439,10 +408,9 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
* Resolve the {@link TransactionDefinition#getIsolationLevel() isolation level constant} to a R2DBC
* {@link IsolationLevel}. If you'd like to extend isolation level translation for vendor-specific
* {@link IsolationLevel}s, override this method accordingly.
*
* @param isolationLevel the isolation level to translate.
* @return the resolved isolation level. Can be {@code null} if not resolvable or the isolation level should remain
* {@link TransactionDefinition#ISOLATION_DEFAULT default}.
* @return the resolved isolation level. Can be {@code null} if not resolvable or the isolation level
* should remain {@link TransactionDefinition#ISOLATION_DEFAULT default}.
* @see TransactionDefinition#getIsolationLevel()
*/
@Nullable
@ -461,9 +429,8 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
/**
* Translate the given R2DBC commit/rollback exception to a common Spring exception to propagate from the
* {@link #commit}/{@link #rollback} call.
*
* Translate the given R2DBC commit/rollback exception to a common Spring exception to propagate
* from the {@link #commit}/{@link #rollback} call.
* @param task the task description (commit or rollback).
* @param ex the SQLException thrown from commit/rollback.
* @return the translated exception to emit
@ -474,8 +441,8 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
/**
* ConnectionFactory transaction object, representing a ConnectionHolder. Used as transaction object by
* ConnectionFactoryTransactionManager.
* ConnectionFactory transaction object, representing a ConnectionHolder.
* Used as transaction object by R2dbcTransactionManager.
*/
private static class ConnectionFactoryTransactionObject {
@ -489,7 +456,6 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
private boolean mustRestoreAutoCommit;
void setConnectionHolder(@Nullable ConnectionHolder connectionHolder, boolean newConnectionHolder) {
setConnectionHolder(connectionHolder);
this.newConnectionHolder = newConnectionHolder;
@ -535,4 +501,3 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
}

View File

@ -91,10 +91,9 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Create a new {@link SingleConnectionFactory} using a R2DBC connection URL.
*
* @param url the R2DBC URL to use for accessing {@link ConnectionFactory} discovery.
* @param suppressClose if the returned {@link Connection} should be a close-suppressing proxy or the physical
* {@link Connection}.
* @param suppressClose if the returned {@link Connection} should be a close-suppressing proxy
* or the physical {@link Connection}.
* @see ConnectionFactories#get(String)
*/
public SingleConnectionFactory(String url, boolean suppressClose) {
@ -106,21 +105,18 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Create a new {@link SingleConnectionFactory} with a given {@link Connection} and
* {@link ConnectionFactoryMetadata}.
*
* @param target underlying target {@link Connection}.
* @param metadata {@link ConnectionFactory} metadata to be associated with this {@link ConnectionFactory}.
* @param suppressClose if the {@link Connection} should be wrapped with a {@link Connection} that suppresses
* {@code close()} calls (to allow for normal {@code close()} usage in applications that expect a pooled
* {@link Connection} but do not know our {@link SmartConnectionFactory} interface).
* @code close()} calls (to allow for normal {@code close()} usage in applications that expect a pooled
* @link Connection}).
*/
public SingleConnectionFactory(Connection target, ConnectionFactoryMetadata metadata,
boolean suppressClose) {
public SingleConnectionFactory(Connection target, ConnectionFactoryMetadata metadata, boolean suppressClose) {
super(new ConnectionFactory() {
@Override
public Publisher<? extends Connection> create() {
return Mono.just(target);
}
@Override
public ConnectionFactoryMetadata getMetadata() {
return metadata;
@ -136,15 +132,16 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Set whether the returned {@link Connection} should be a close-suppressing proxy or the physical {@link Connection}.
* Set whether the returned {@link Connection} should be a close-suppressing proxy
* or the physical {@link Connection}.
*/
public void setSuppressClose(boolean suppressClose) {
this.suppressClose = suppressClose;
}
/**
* Return whether the returned {@link Connection} will be a close-suppressing proxy or the physical
* {@link Connection}.
* Return whether the returned {@link Connection} will be a close-suppressing proxy
* or the physical {@link Connection}.
*/
protected boolean isSuppressClose() {
return this.suppressClose;
@ -159,7 +156,6 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
/**
* Return whether the returned {@link Connection}'s "autoCommit" setting should be overridden.
*
* @return the "autoCommit" value, or {@code null} if none to be applied
*/
@Nullable
@ -167,28 +163,25 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
return this.autoCommit;
}
@Override
public Mono<? extends Connection> create() {
Connection connection = this.target.get();
return this.connectionEmitter.map(connectionToUse -> {
if (connection == null) {
this.target.compareAndSet(connection, connectionToUse);
this.connection = (isSuppressClose() ? getCloseSuppressingConnectionProxy(connectionToUse) : connectionToUse);
this.target.compareAndSet(null, connectionToUse);
this.connection =
(isSuppressClose() ? getCloseSuppressingConnectionProxy(connectionToUse) : connectionToUse);
}
return this.connection;
}).flatMap(this::prepareConnection);
}
/**
* Close the underlying {@link Connection}. The provider of this {@link ConnectionFactory} needs to care for proper
* shutdown.
* <p>
* As this bean implements {@link DisposableBean}, a bean factory will automatically invoke this on destruction of its
* cached singletons.
* Close the underlying {@link Connection}.
* The provider of this {@link ConnectionFactory} needs to care for proper shutdown.
* <p>As this bean implements {@link DisposableBean}, a bean factory will automatically
* invoke this on destruction of its cached singletons.
*/
@Override
public void destroy() {
@ -199,46 +192,36 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
* Reset the underlying shared Connection, to be reinitialized on next access.
*/
public Mono<Void> resetConnection() {
Connection connection = this.target.get();
if (connection == null) {
return Mono.empty();
}
return Mono.defer(() -> {
if (this.target.compareAndSet(connection, null)) {
this.connection = null;
return Mono.from(connection.close());
}
return Mono.empty();
});
}
/**
* Prepare the {@link Connection} before using it. Applies {@link #getAutoCommitValue() auto-commit} settings if
* configured.
*
* Prepare the {@link Connection} before using it.
* Applies {@link #getAutoCommitValue() auto-commit} settings if configured.
* @param connection the requested {@link Connection}.
* @return the prepared {@link Connection}.
*/
protected Mono<Connection> prepareConnection(Connection connection) {
Boolean autoCommit = getAutoCommitValue();
if (autoCommit != null) {
return Mono.from(connection.setAutoCommit(autoCommit)).thenReturn(connection);
}
return Mono.just(connection);
}
/**
* Wrap the given {@link Connection} with a proxy that delegates every method call to it but suppresses close calls.
*
* Wrap the given {@link Connection} with a proxy that delegates every method call to it
* but suppresses close calls.
* @param target the original {@link Connection} to wrap.
* @return the wrapped Connection.
*/
@ -264,22 +247,18 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Invocation on ConnectionProxy interface coming in...
if (method.getName().equals("equals")) {
// Only consider equal when proxies are identical.
return proxy == args[0];
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of PersistenceManager proxy.
return System.identityHashCode(proxy);
}
else if (method.getName().equals("unwrap")) {
return this.target;
}
else if (method.getName().equals("close")) {
// Handle close method: suppress, not valid.
return Mono.empty();
switch (method.getName()) {
case "equals":
// Only consider equal when proxies are identical.
return proxy == args[0];
case "hashCode":
// Use hashCode of PersistenceManager proxy.
return System.identityHashCode(proxy);
case "unwrap":
return this.target;
case "close":
// Handle close method: suppress, not valid.
return Mono.empty();
}
// Invoke method on target Connection.
@ -290,7 +269,6 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
throw ex.getTargetException();
}
}
}
}

View File

@ -141,23 +141,18 @@ public class TransactionAwareConnectionFactoryProxy extends DelegatingConnection
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (ReflectionUtils.isObjectMethod(method)) {
if (ReflectionUtils.isToStringMethod(method)) {
return proxyToString(proxy);
}
if (ReflectionUtils.isEqualsMethod(method)) {
return (proxy == args[0]);
}
if (ReflectionUtils.isHashCodeMethod(method)) {
return System.identityHashCode(proxy);
}
}
// Invocation on ConnectionProxy interface coming in...
switch (method.getName()) {
case "unwrap":
return this.connection;
case "close":
@ -183,14 +178,7 @@ public class TransactionAwareConnectionFactoryProxy extends DelegatingConnection
private String proxyToString(@Nullable Object proxy) {
// Allow for differentiating between the proxy and the raw Connection.
StringBuilder sb = new StringBuilder("Transaction-aware proxy for target Connection ");
if (this.connection != null) {
sb.append("[").append(this.connection.toString()).append("]");
}
else {
sb.append(" from ConnectionFactory [").append(this.targetConnectionFactory).append("]");
}
return sb.toString();
return "Transaction-aware proxy for target Connection [" + this.connection.toString() + "]";
}
}

View File

@ -40,7 +40,7 @@ import org.springframework.util.Assert;
* or obtain a {@link DatabaseClient#builder()} to create an instance.
*
* Usage example:
* <p><pre class="code">
* <pre class="code">
* ConnectionFactory factory =
*
* DatabaseClient client = DatabaseClient.create(factory);
@ -60,7 +60,6 @@ public interface DatabaseClient extends ConnectionAccessor {
* SQL call along with options leading to the execution. The SQL string can
* contain either native parameter bind markers or named parameters (e.g.
* {@literal :foo, :bar}) when {@link NamedParameterExpander} is enabled.
*
* @param sql must not be {@code null} or empty
* @return a new {@link GenericExecuteSpec}
* @see NamedParameterExpander
@ -74,7 +73,6 @@ public interface DatabaseClient extends ConnectionAccessor {
* the execution. The SQL string can contain either native parameter
* bind markers or named parameters (e.g. {@literal :foo, :bar}) when
* {@link NamedParameterExpander} is enabled.
*
* <p>Accepts {@link PreparedOperation} as SQL and binding {@link Supplier}
* @param sqlSupplier must not be {@code null}
* @return a new {@link GenericExecuteSpec}
@ -85,7 +83,7 @@ public interface DatabaseClient extends ConnectionAccessor {
GenericExecuteSpec sql(Supplier<String> sqlSupplier);
// Static, factory methods
// Static factory methods
/**
* Create a {@code DatabaseClient} that will use the provided {@link ConnectionFactory}.
@ -146,7 +144,6 @@ public interface DatabaseClient extends ConnectionAccessor {
* Builder the {@link DatabaseClient} instance.
*/
DatabaseClient build();
}
@ -187,10 +184,8 @@ public interface DatabaseClient extends ConnectionAccessor {
/**
* Add the given filter to the end of the filter chain.
* <p>Filter functions are typically used to invoke methods on the Statement
* before it is executed.
*
* For example:
* <p><pre class="code">
* before it is executed. For example:
* <pre class="code">
* DatabaseClient client = ;
* client.sql("SELECT book_id FROM book").filter(statement -> statement.fetchSize(100))
* </pre>
@ -204,10 +199,8 @@ public interface DatabaseClient extends ConnectionAccessor {
/**
* Add the given filter to the end of the filter chain.
* <p>Filter functions are typically used to invoke methods on the Statement
* before it is executed.
*
* For example:
* <p><pre class="code">
* before it is executed. For example:
* <pre class="code">
* DatabaseClient client = ;
* client.sql("SELECT book_id FROM book").filter((statement, next) -> next.execute(statement.fetchSize(100)))
* </pre>
@ -244,7 +237,6 @@ public interface DatabaseClient extends ConnectionAccessor {
* @return a {@link Mono} ignoring its payload (actively dropping).
*/
Mono<Void> then();
}
}

View File

@ -53,7 +53,6 @@ import org.springframework.r2dbc.core.binding.BindTarget;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Default implementation of {@link DatabaseClient}.
*
@ -72,22 +71,17 @@ class DefaultDatabaseClient implements DatabaseClient {
private final ExecuteFunction executeFunction;
private final boolean namedParameters;
@Nullable
private final NamedParameterExpander namedParameterExpander;
DefaultDatabaseClient(BindMarkersFactory bindMarkersFactory,
ConnectionFactory connectionFactory, ExecuteFunction executeFunction,
boolean namedParameters) {
DefaultDatabaseClient(BindMarkersFactory bindMarkersFactory, ConnectionFactory connectionFactory,
ExecuteFunction executeFunction, boolean namedParameters) {
this.bindMarkersFactory = bindMarkersFactory;
this.connectionFactory = connectionFactory;
this.executeFunction = executeFunction;
this.namedParameters = namedParameters;
this.namedParameterExpander = namedParameters ? new NamedParameterExpander()
: null;
this.namedParameterExpander = (namedParameters ? new NamedParameterExpander() : null);
}
@ -233,9 +227,7 @@ class DefaultDatabaseClient implements DatabaseClient {
final StatementFilterFunction filterFunction;
DefaultGenericExecuteSpec(Supplier<String> sqlSupplier) {
this.byIndex = Collections.emptyMap();
this.byName = Collections.emptyMap();
this.sqlSupplier = sqlSupplier;
@ -255,8 +247,7 @@ class DefaultDatabaseClient implements DatabaseClient {
public DefaultGenericExecuteSpec bind(int index, Object value) {
assertNotPreparedOperation();
Assert.notNull(value, () -> String.format(
"Value at index %d must not be null. Use bindNull(…) instead.",
index));
"Value at index %d must not be null. Use bindNull(…) instead.", index));
Map<Integer, Parameter> byIndex = new LinkedHashMap<>(this.byIndex);
@ -286,8 +277,7 @@ class DefaultDatabaseClient implements DatabaseClient {
Assert.hasText(name, "Parameter name must not be null or empty!");
Assert.notNull(value, () -> String.format(
"Value for parameter %s must not be null. Use bindNull(…) instead.",
name));
"Value for parameter %s must not be null. Use bindNull(…) instead.", name));
Map<String, Parameter> byName = new LinkedHashMap<>(this.byName);
@ -314,9 +304,7 @@ class DefaultDatabaseClient implements DatabaseClient {
@Override
public DefaultGenericExecuteSpec filter(StatementFilterFunction filter) {
Assert.notNull(filter, "Statement FilterFunction must not be null");
return new DefaultGenericExecuteSpec(this.byIndex, this.byName, this.sqlSupplier, this.filterFunction.andThen(filter));
}
@ -336,38 +324,29 @@ class DefaultDatabaseClient implements DatabaseClient {
return fetch().rowsUpdated().then();
}
private <T> FetchSpec<T> execute(Supplier<String> sqlSupplier,
BiFunction<Row, RowMetadata, T> mappingFunction) {
private <T> FetchSpec<T> execute(Supplier<String> sqlSupplier, BiFunction<Row, RowMetadata, T> mappingFunction) {
String sql = getRequiredSql(sqlSupplier);
Function<Connection, Statement> statementFunction = connection -> {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}
if (sqlSupplier instanceof PreparedOperation<?>) {
Statement statement = connection.createStatement(sql);
BindTarget bindTarget = new StatementWrapper(statement);
((PreparedOperation<?>) sqlSupplier).bindTo(bindTarget);
return statement;
}
if (DefaultDatabaseClient.this.namedParameters) {
if (DefaultDatabaseClient.this.namedParameterExpander != null) {
Map<String, Parameter> remainderByName = new LinkedHashMap<>(this.byName);
Map<Integer, Parameter> remainderByIndex = new LinkedHashMap<>(this.byIndex);
Map<String, Parameter> remainderByName = new LinkedHashMap<>(
this.byName);
Map<Integer, Parameter> remainderByIndex = new LinkedHashMap<>(
this.byIndex);
List<String> parameterNames = DefaultDatabaseClient.this.namedParameterExpander.getParameterNames(sql);
MapBindParameterSource namedBindings = retrieveParameters(
sql, parameterNames, remainderByName, remainderByIndex);
MapBindParameterSource namedBindings = retrieveParameters(sql,
remainderByName, remainderByIndex);
PreparedOperation<String> operation = DefaultDatabaseClient.this.namedParameterExpander.expand(sql,
DefaultDatabaseClient.this.bindMarkersFactory, namedBindings);
PreparedOperation<String> operation = DefaultDatabaseClient.this.namedParameterExpander.expand(
sql, DefaultDatabaseClient.this.bindMarkersFactory, namedBindings);
String expanded = getRequiredSql(operation);
if (logger.isTraceEnabled()) {
@ -406,23 +385,16 @@ class DefaultDatabaseClient implements DatabaseClient {
mappingFunction);
}
private MapBindParameterSource retrieveParameters(String sql,
Map<String, Parameter> remainderByName,
Map<Integer, Parameter> remainderByIndex) {
List<String> parameterNames = DefaultDatabaseClient.this.namedParameterExpander.getParameterNames(sql);
Map<String, Parameter> namedBindings = new LinkedHashMap<>(
parameterNames.size());
private MapBindParameterSource retrieveParameters(String sql, List<String> parameterNames,
Map<String, Parameter> remainderByName, Map<Integer, Parameter> remainderByIndex) {
Map<String, Parameter> namedBindings = new LinkedHashMap<>(parameterNames.size());
for (String parameterName : parameterNames) {
Parameter parameter = getParameter(remainderByName, remainderByIndex,
parameterNames, parameterName);
Parameter parameter = getParameter(remainderByName, remainderByIndex, parameterNames, parameterName);
if (parameter == null) {
throw new InvalidDataAccessApiUsageException(
String.format("No parameter specified for [%s] in query [%s]",
parameterName, sql));
String.format("No parameter specified for [%s] in query [%s]", parameterName, sql));
}
namedBindings.put(parameterName, parameter);
}
return new MapBindParameterSource(namedBindings);
@ -430,8 +402,7 @@ class DefaultDatabaseClient implements DatabaseClient {
@Nullable
private Parameter getParameter(Map<String, Parameter> remainderByName,
Map<Integer, Parameter> remainderByIndex, List<String> parameterNames,
String parameterName) {
Map<Integer, Parameter> remainderByIndex, List<String> parameterNames, String parameterName) {
if (this.byName.containsKey(parameterName)) {
remainderByName.remove(parameterName);
@ -456,8 +427,9 @@ class DefaultDatabaseClient implements DatabaseClient {
private void bindByName(Statement statement, Map<String, Parameter> byName) {
byName.forEach((name, parameter) -> {
if (parameter.hasValue()) {
statement.bind(name, parameter.getValue());
Object value = parameter.getValue();
if (value != null) {
statement.bind(name, value);
}
else {
statement.bindNull(name, parameter.getType());
@ -467,8 +439,9 @@ class DefaultDatabaseClient implements DatabaseClient {
private void bindByIndex(Statement statement, Map<Integer, Parameter> byIndex) {
byIndex.forEach((i, parameter) -> {
if (parameter.hasValue()) {
statement.bind(i, parameter.getValue());
Object value = parameter.getValue();
if (value != null) {
statement.bind(i, value);
}
else {
statement.bindNull(i, parameter.getType());
@ -477,10 +450,8 @@ class DefaultDatabaseClient implements DatabaseClient {
}
private String getRequiredSql(Supplier<String> sqlSupplier) {
String sql = sqlSupplier.get();
Assert.state(StringUtils.hasText(sql),
"SQL returned by SQL supplier must not be empty!");
Assert.state(StringUtils.hasText(sql), "SQL returned by SQL supplier must not be empty!");
return sql;
}
@ -497,32 +468,26 @@ class DefaultDatabaseClient implements DatabaseClient {
private final Connection target;
CloseSuppressingInvocationHandler(Connection target) {
this.target = target;
}
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// Invocation on ConnectionProxy interface coming in...
if (method.getName().equals("equals")) {
// Only consider equal when proxies are identical.
return proxy == args[0];
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of PersistenceManager proxy.
return System.identityHashCode(proxy);
}
else if (method.getName().equals("unwrap")) {
return this.target;
}
else if (method.getName().equals("close")) {
// Handle close method: suppress, not valid.
return Mono.error(
new UnsupportedOperationException("Close is not supported!"));
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "equals":
// Only consider equal when proxies are identical.
return proxy == args[0];
case "hashCode":
// Use hashCode of PersistenceManager proxy.
return System.identityHashCode(proxy);
case "unwrap":
return this.target;
case "close":
// Handle close method: suppress, not valid.
return Mono.error(
new UnsupportedOperationException("Close is not supported!"));
}
// Invoke method on target Connection.
@ -533,12 +498,11 @@ class DefaultDatabaseClient implements DatabaseClient {
throw ex.getTargetException();
}
}
}
/**
* Holder for a connection that makes sure the close action is invoked atomically only
* once.
* Holder for a connection that makes sure the close action is invoked atomically only once.
*/
static class ConnectionCloseHolder extends AtomicBoolean {
@ -548,7 +512,6 @@ class DefaultDatabaseClient implements DatabaseClient {
final Function<Connection, Publisher<Void>> closeFunction;
ConnectionCloseHolder(Connection connection,
Function<Connection, Publisher<Void>> closeFunction) {
this.connection = connection;
@ -556,24 +519,20 @@ class DefaultDatabaseClient implements DatabaseClient {
}
Mono<Void> close() {
return Mono.defer(() -> {
if (compareAndSet(false, true)) {
return Mono.from(this.closeFunction.apply(this.connection));
}
return Mono.empty();
});
}
}
static class StatementWrapper implements BindTarget {
final Statement statement;
StatementWrapper(Statement statement) {
this.statement = statement;
}
@ -597,7 +556,6 @@ class DefaultDatabaseClient implements DatabaseClient {
public void bindNull(int index, Class<?> type) {
this.statement.bindNull(index, type);
}
}
}

View File

@ -74,8 +74,7 @@ public final class Parameter {
/**
* Returns the column value. Can be {@code null}.
* @return the column value. Can be {@code null}
* Return the column value. Can be {@code null}.
* @see #hasValue()
*/
@Nullable
@ -85,7 +84,6 @@ public final class Parameter {
/**
* Returns the column value type. Must be also present if the {@code value} is {@code null}.
* @return the column value type
*/
public Class<?> getType() {
return this.type;
@ -93,29 +91,30 @@ public final class Parameter {
/**
* Returns whether this {@link Parameter} has a value.
* @return whether this {@link Parameter} has a value. {@code false} if {@link #getValue()} is {@code null}
* @return {@code false} if {@link #getValue()} is {@code null}
*/
public boolean hasValue() {
return this.value != null;
return (this.value != null);
}
/**
* Returns whether this {@link Parameter} has a empty.
* @return whether this {@link Parameter} is empty. {@code true} if {@link #getValue()} is {@code null}
* @return {@code true} if {@link #getValue()} is {@code null}
*/
public boolean isEmpty() {
return this.value == null;
return (this.value == null);
}
@Override
public boolean equals(Object o) {
if (this == o) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(o instanceof Parameter)) {
if (!(obj instanceof Parameter)) {
return false;
}
Parameter other = (Parameter) o;
Parameter other = (Parameter) obj;
return ObjectUtils.nullSafeEquals(this.value, other.value) && ObjectUtils.nullSafeEquals(this.type, other.type);
}
@ -126,12 +125,7 @@ public final class Parameter {
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Parameter");
sb.append("[value=").append(this.value);
sb.append(", type=").append(this.type);
sb.append(']');
return sb.toString();
return "Parameter[value=" + this.value + ", type=" + this.type + ']';
}
}

View File

@ -32,13 +32,12 @@ import io.r2dbc.spi.Statement;
public interface BindMarker {
/**
* Returns the database-specific placeholder for a given substitution.
* Return the database-specific placeholder for a given substitution.
*/
String getPlaceholder();
/**
* Bind the given {@code value} to the {@link Statement} using the underlying binding strategy.
*
* @param bindTarget the target to bind the value to
* @param value the actual value. Must not be {@code null}
* Use {@link #bindNull(BindTarget, Class)} for {@code null} values

View File

@ -28,6 +28,7 @@ import java.util.function.Consumer;
import io.r2dbc.spi.Statement;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -69,37 +70,10 @@ public class Bindings implements Iterable<Bindings.Binding> {
}
/**
* Create a new, empty {@link Bindings} object.
*
* @return a new, empty {@link Bindings} object.
*/
public static Bindings empty() {
return EMPTY;
}
protected Map<BindMarker, Binding> getBindings() {
return this.bindings;
}
/**
* Merge this bindings with an other {@link Bindings} object and create a new merged
* {@link Bindings} object.
* @param left the left object to merge with
* @param right the right object to merge with
* @return a new, merged {@link Bindings} object
*/
public static Bindings merge(Bindings left, Bindings right) {
Assert.notNull(left, "Left side Bindings must not be null");
Assert.notNull(right, "Right side Bindings must not be null");
List<Binding> result = new ArrayList<>(
left.getBindings().size() + right.getBindings().size());
result.addAll(left.getBindings().values());
result.addAll(right.getBindings().values());
return new Bindings(result);
}
/**
* Merge this bindings with an other {@link Bindings} object and create a new merged
* {@link Bindings} object.
@ -141,6 +115,32 @@ public class Bindings implements Iterable<Bindings.Binding> {
}
/**
* Create a new, empty {@link Bindings} object.
* @return a new, empty {@link Bindings} object.
*/
public static Bindings empty() {
return EMPTY;
}
/**
* Merge this bindings with an other {@link Bindings} object and create a new merged
* {@link Bindings} object.
* @param left the left object to merge with
* @param right the right object to merge with
* @return a new, merged {@link Bindings} object
*/
public static Bindings merge(Bindings left, Bindings right) {
Assert.notNull(left, "Left side Bindings must not be null");
Assert.notNull(right, "Right side Bindings must not be null");
List<Binding> result = new ArrayList<>(
left.getBindings().size() + right.getBindings().size());
result.addAll(left.getBindings().values());
result.addAll(right.getBindings().values());
return new Bindings(result);
}
/**
* Base class for value objects representing a value or a {@code NULL} binding.
*/
@ -188,7 +188,6 @@ public class Bindings implements Iterable<Bindings.Binding> {
* @param bindTarget the target to apply bindings to
*/
public abstract void apply(BindTarget bindTarget);
}
@ -199,19 +198,18 @@ public class Bindings implements Iterable<Bindings.Binding> {
private final Object value;
ValueBinding(BindMarker marker, Object value) {
super(marker);
this.value = value;
}
@Override
public boolean hasValue() {
return true;
}
@Override
@NonNull
public Object getValue() {
return this.value;
}
@ -220,9 +218,9 @@ public class Bindings implements Iterable<Bindings.Binding> {
public void apply(BindTarget bindTarget) {
getBindMarker().bind(bindTarget, getValue());
}
}
/**
* {@code NULL} binding.
*/
@ -230,13 +228,11 @@ public class Bindings implements Iterable<Bindings.Binding> {
private final Class<?> valueType;
NullBinding(BindMarker marker, Class<?> valueType) {
super(marker);
this.valueType = valueType;
}
@Override
public boolean hasValue() {
return false;
@ -256,7 +252,6 @@ public class Bindings implements Iterable<Bindings.Binding> {
public void apply(BindTarget bindTarget) {
getBindMarker().bindNull(bindTarget, getValueType());
}
}
}

View File

@ -69,6 +69,7 @@ public class RequestEntity<T> extends HttpEntity<T> {
@Nullable
private final HttpMethod method;
@Nullable
private final URI url;
@Nullable
@ -138,7 +139,7 @@ public class RequestEntity<T> extends HttpEntity<T> {
* @since 4.3
*/
public RequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers,
@Nullable HttpMethod method, URI url, @Nullable Type type) {
@Nullable HttpMethod method, @Nullable URI url, @Nullable Type type) {
super(body, headers);
this.method = method;
@ -160,6 +161,9 @@ public class RequestEntity<T> extends HttpEntity<T> {
* Return the URL of the request.
*/
public URI getUrl() {
if (this.url == null) {
throw new UnsupportedOperationException();
}
return this.url;
}
@ -679,14 +683,13 @@ public class RequestEntity<T> extends HttpEntity<T> {
*/
public static class UriTemplateRequestEntity<T> extends RequestEntity<T> {
String uriTemplate;
private final String uriTemplate;
@Nullable
private Object[] uriVarsArray;
private final Object[] uriVarsArray;
@Nullable
Map<String, ?> uriVarsMap;
private final Map<String, ?> uriVarsMap;
UriTemplateRequestEntity(
@Nullable T body, @Nullable MultiValueMap<String, String> headers,
@ -699,7 +702,6 @@ public class RequestEntity<T> extends HttpEntity<T> {
this.uriVarsMap = uriVarsMap;
}
public String getUriTemplate() {
return this.uriTemplate;
}
@ -714,11 +716,6 @@ public class RequestEntity<T> extends HttpEntity<T> {
return this.uriVarsMap;
}
@Override
public URI getUrl() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return format(getMethod(), getUriTemplate(), getBody(), getHeaders());

View File

@ -667,9 +667,15 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
private URI resolveUrl(RequestEntity<?> entity) {
if (entity instanceof RequestEntity.UriTemplateRequestEntity) {
RequestEntity.UriTemplateRequestEntity<?> ext = (RequestEntity.UriTemplateRequestEntity<?>) entity;
return (ext.getVars() != null ?
this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVars()) :
this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVarsMap()));
if (ext.getVars() != null) {
return this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVars());
}
else if (ext.getVarsMap() != null) {
return this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVarsMap());
}
else {
throw new IllegalStateException("No variables specified for URI template: " + ext.getUriTemplate());
}
}
else {
return entity.getUrl();

View File

@ -137,19 +137,19 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
private static String getForwardedPrefix(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String header = headers.getFirst("X-Forwarded-Prefix");
if (header != null) {
StringBuilder prefix = new StringBuilder(header.length());
String[] rawPrefixes = StringUtils.tokenizeToStringArray(header, ",");
for (String rawPrefix : rawPrefixes) {
int endIndex = rawPrefix.length();
while (endIndex > 1 && rawPrefix.charAt(endIndex - 1) == '/') {
endIndex--;
}
prefix.append((endIndex != rawPrefix.length() ? rawPrefix.substring(0, endIndex) : rawPrefix));
}
return prefix.toString();
if (header == null) {
return null;
}
return header;
StringBuilder prefix = new StringBuilder(header.length());
String[] rawPrefixes = StringUtils.tokenizeToStringArray(header, ",");
for (String rawPrefix : rawPrefixes) {
int endIndex = rawPrefix.length();
while (endIndex > 1 && rawPrefix.charAt(endIndex - 1) == '/') {
endIndex--;
}
prefix.append((endIndex != rawPrefix.length() ? rawPrefix.substring(0, endIndex) : rawPrefix));
}
return prefix.toString();
}
}

View File

@ -411,10 +411,10 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
@Override
public RequestMatchResult match(HttpServletRequest request, String pattern) {
Assert.isNull(getPatternParser(), "This HandlerMapping requires a PathPattern.");
Assert.isNull(getPatternParser(), "This HandlerMapping requires a PathPattern");
RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
RequestMappingInfo match = info.getMatchingCondition(request);
return (match != null ?
return (match != null && match.getPatternsCondition() != null ?
new RequestMatchResult(
match.getPatternsCondition().getPatterns().iterator().next(),
UrlPathHelper.getResolvedLookupPath(request),