From d0209e5f1f4d25da1dcf089d7feec242b1d451ad Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 23 Jun 2020 16:55:09 +0200 Subject: [PATCH] Nullability refinements and related polishing --- .../support/DatabaseStartupValidator.java | 14 +- .../connection/ConnectionFactoryUtils.java | 19 +-- .../r2dbc/connection/ConnectionHolder.java | 3 - .../connection/R2dbcTransactionManager.java | 113 ++++++--------- .../connection/SingleConnectionFactory.java | 88 +++++------- ...ransactionAwareConnectionFactoryProxy.java | 14 +- .../r2dbc/core/DatabaseClient.java | 20 +-- .../r2dbc/core/DefaultDatabaseClient.java | 132 ++++++------------ .../springframework/r2dbc/core/Parameter.java | 28 ++-- .../r2dbc/core/binding/BindMarker.java | 3 +- .../r2dbc/core/binding/Bindings.java | 63 ++++----- .../springframework/http/RequestEntity.java | 19 ++- .../web/client/RestTemplate.java | 12 +- .../adapter/ForwardedHeaderTransformer.java | 24 ++-- .../RequestMappingHandlerMapping.java | 4 +- 15 files changed, 206 insertions(+), 350 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java index b3e6aa1b43..f518103d2e 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java @@ -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) { diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java index ef1a0e582e..59e1a4e722 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java @@ -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. - * *

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. - * *

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 doReleaseConnection(Connection connection, - ConnectionFactory connectionFactory) { + public static Mono 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 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(); } } + } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java index c4efc83c27..1c713048db 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java @@ -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; } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java index c3f553021a..d4d8513d4d 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java @@ -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. - *

- * This will typically be a locally defined {@link ConnectionFactory}, for example an connection pool. - *

- * 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}. - *

- * The {@link ConnectionFactory} passed in here needs to return independent {@link Connection}s. 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. - * + *

This will typically be a locally defined {@link ConnectionFactory}, for example an connection pool. + *

The {@link ConnectionFactory} passed in here needs to return independent {@link Connection}s. + * 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. - *

- * 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. + *

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 doBegin(TransactionSynchronizationManager synchronizationManager, Object transaction, TransactionDefinition definition) throws TransactionException { + ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; return Mono.defer(() -> { - Mono connectionMono; if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Mono 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 doResume(TransactionSynchronizationManager synchronizationManager, Object transaction, - Object suspendedResources) throws TransactionException { + protected Mono 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. - *

- * 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. - *

- * 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. - * + *

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. + *

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 prepareTransactionalConnection(Connection con, TransactionDefinition definition, - Object transaction) { + protected Mono 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 } } - diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/SingleConnectionFactory.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/SingleConnectionFactory.java index 1970a6c676..bbcd28fcbc 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/SingleConnectionFactory.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/SingleConnectionFactory.java @@ -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 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 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. - *

- * 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. + *

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 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 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(); } } - } } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/TransactionAwareConnectionFactoryProxy.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/TransactionAwareConnectionFactoryProxy.java index 9cbd9083b7..406b2662cf 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/TransactionAwareConnectionFactoryProxy.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/TransactionAwareConnectionFactoryProxy.java @@ -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() + "]"; } } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java index 02f7ed352d..b2a66ea65a 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java @@ -40,7 +40,7 @@ import org.springframework.util.Assert; * or obtain a {@link DatabaseClient#builder()} to create an instance. * * Usage example: - *

+ * 
  * 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.
-	 *
 	 * 

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 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. *

Filter functions are typically used to invoke methods on the Statement - * before it is executed. - * - * For example: - *

+		 * before it is executed. For example:
+		 * 
 		 * DatabaseClient client = …;
 		 * client.sql("SELECT book_id FROM book").filter(statement -> statement.fetchSize(100))
 		 * 
@@ -204,10 +199,8 @@ public interface DatabaseClient extends ConnectionAccessor { /** * Add the given filter to the end of the filter chain. *

Filter functions are typically used to invoke methods on the Statement - * before it is executed. - * - * For example: - *

+		 * before it is executed. For example:
+		 * 
 		 * DatabaseClient client = …;
 		 * client.sql("SELECT book_id FROM book").filter((statement, next) -> next.execute(statement.fetchSize(100)))
 		 * 
@@ -244,7 +237,6 @@ public interface DatabaseClient extends ConnectionAccessor { * @return a {@link Mono} ignoring its payload (actively dropping). */ Mono then(); - } } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java index 78f79d7277..0f8c319772 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java @@ -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 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 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 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 FetchSpec execute(Supplier sqlSupplier, - BiFunction mappingFunction) { - + private FetchSpec execute(Supplier sqlSupplier, BiFunction mappingFunction) { String sql = getRequiredSql(sqlSupplier); - Function 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 remainderByName = new LinkedHashMap<>(this.byName); + Map remainderByIndex = new LinkedHashMap<>(this.byIndex); - Map remainderByName = new LinkedHashMap<>( - this.byName); - Map remainderByIndex = new LinkedHashMap<>( - this.byIndex); + List parameterNames = DefaultDatabaseClient.this.namedParameterExpander.getParameterNames(sql); + MapBindParameterSource namedBindings = retrieveParameters( + sql, parameterNames, remainderByName, remainderByIndex); - MapBindParameterSource namedBindings = retrieveParameters(sql, - remainderByName, remainderByIndex); - - PreparedOperation operation = DefaultDatabaseClient.this.namedParameterExpander.expand(sql, - DefaultDatabaseClient.this.bindMarkersFactory, namedBindings); + PreparedOperation 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 remainderByName, - Map remainderByIndex) { - List parameterNames = DefaultDatabaseClient.this.namedParameterExpander.getParameterNames(sql); - Map namedBindings = new LinkedHashMap<>( - parameterNames.size()); + private MapBindParameterSource retrieveParameters(String sql, List parameterNames, + Map remainderByName, Map remainderByIndex) { + + Map 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 remainderByName, - Map remainderByIndex, List parameterNames, - String parameterName) { + Map remainderByIndex, List 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 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 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 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> closeFunction; - ConnectionCloseHolder(Connection connection, Function> closeFunction) { this.connection = connection; @@ -556,24 +519,20 @@ class DefaultDatabaseClient implements DatabaseClient { } Mono 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); } - } } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java index f74d9b349c..714bfe5504 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java @@ -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 + ']'; } } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java index 71687186a8..7e90397e2b 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarker.java @@ -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 diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/Bindings.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/Bindings.java index b430d96c0f..7e851d2020 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/Bindings.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/Bindings.java @@ -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 { } - /** - * Create a new, empty {@link Bindings} object. - * - * @return a new, empty {@link Bindings} object. - */ - public static Bindings empty() { - return EMPTY; - } - - protected Map 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 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 { } + /** + * 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 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 { * @param bindTarget the target to apply bindings to */ public abstract void apply(BindTarget bindTarget); - } @@ -199,19 +198,18 @@ public class Bindings implements Iterable { 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 { public void apply(BindTarget bindTarget) { getBindMarker().bind(bindTarget, getValue()); } - } + /** * {@code NULL} binding. */ @@ -230,13 +228,11 @@ public class Bindings implements Iterable { 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 { public void apply(BindTarget bindTarget) { getBindMarker().bindNull(bindTarget, getValueType()); } - } } diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java index 6b62e7222b..4c77123332 100644 --- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java +++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java @@ -69,6 +69,7 @@ public class RequestEntity extends HttpEntity { @Nullable private final HttpMethod method; + @Nullable private final URI url; @Nullable @@ -138,7 +139,7 @@ public class RequestEntity extends HttpEntity { * @since 4.3 */ public RequestEntity(@Nullable T body, @Nullable MultiValueMap 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 extends HttpEntity { * 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 extends HttpEntity { */ public static class UriTemplateRequestEntity extends RequestEntity { - String uriTemplate; + private final String uriTemplate; @Nullable - private Object[] uriVarsArray; + private final Object[] uriVarsArray; @Nullable - Map uriVarsMap; - + private final Map uriVarsMap; UriTemplateRequestEntity( @Nullable T body, @Nullable MultiValueMap headers, @@ -699,7 +702,6 @@ public class RequestEntity extends HttpEntity { this.uriVarsMap = uriVarsMap; } - public String getUriTemplate() { return this.uriTemplate; } @@ -714,11 +716,6 @@ public class RequestEntity extends HttpEntity { return this.uriVarsMap; } - @Override - public URI getUrl() { - throw new UnsupportedOperationException(); - } - @Override public String toString() { return format(getMethod(), getUriTemplate(), getBody(), getHeaders()); diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index 84870ca1a2..0071fabd38 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -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(); diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java index a34cef914e..531d734135 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java @@ -137,19 +137,19 @@ public class ForwardedHeaderTransformer implements Function 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(); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index 8f8c84a9b6..1ae8d0c6c8 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -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),