/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.remoting3;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.wildfly.common.Assert;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.xnio.Cancellable;
import org.xnio.FutureResult;
import org.xnio.IoFuture;
import org.xnio.OptionMap;
/**
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
final class ConnectionInfo {
final OptionMap connectOptions;
State state = new None();
private static final IoFuture<Connection> RETRY = new EmptyIoFuture();
ConnectionInfo(final OptionMap connectOptions) {
this.connectOptions = connectOptions;
}
IoFuture<Connection> getConnection(final EndpointImpl endpoint, ConnectionKey key, AuthenticationConfiguration authenticationConfiguration, boolean doConnect) {
IoFuture<Connection> result;
State state;
do {
synchronized (this) {
state = this.state;
}
result = state.getConnection(endpoint, key, authenticationConfiguration, doConnect);
} while (result == RETRY);
return result;
}
void connectionClosed(AuthenticationConfiguration authenticationConfiguration, final FutureResult<Connection> futureResult) {
State state;
do {
synchronized (this) {
state = this.state;
}
} while (! state.connectionClosed(authenticationConfiguration, futureResult));
}
abstract static class State {
abstract IoFuture<Connection> getConnection(EndpointImpl endpoint, ConnectionKey key, AuthenticationConfiguration authenticationConfiguration, boolean doConnect);
abstract boolean connectionClosed(AuthenticationConfiguration authenticationConfiguration, FutureResult<Connection> futureResult);
}
final class None extends State {
IoFuture<Connection> getConnection(final EndpointImpl endpoint, final ConnectionKey key, final AuthenticationConfiguration authenticationConfiguration, boolean doConnect) {
if (! doConnect) return null;
State oldState;
synchronized (ConnectionInfo.this) {
oldState = state;
if (oldState == this) {
final IoFuture<Connection> attempt = endpoint.connect(key.getRealUri(), null, connectOptions, key.getSslContext(), authenticationConfiguration);
final MaybeShared maybeShared = new MaybeShared(authenticationConfiguration, attempt);
final FutureResult<Connection> futureResult = new FutureResult<>();
splice(futureResult, attempt, authenticationConfiguration);
attempt.addNotifier(new IoFuture.HandlingNotifier<Connection, Void>() {
public void handleCancelled(final Void attachment) {
clear();
}
public void handleFailed(final IOException exception, final Void attachment) {
clear();
}
public void handleDone(final Connection connection, final Void attachment) {
final ConnectionInfo outer = ConnectionInfo.this;
synchronized (outer) {
assert state == maybeShared;
if (connection.supportsRemoteAuth()) {
// shared!
state = new Shared(futureResult, Collections.emptyMap());
} else {
// unsharable :(
state = new NotShared(Collections.singletonMap(authenticationConfiguration, futureResult));
}
}
}
private void clear() {
final ConnectionInfo outer = ConnectionInfo.this;
synchronized (outer) {
assert state == maybeShared;
state = None.this;
}
}
}, null);
state = maybeShared;
return futureResult.getIoFuture();
}
}
// try again :(
return RETRY;
}
boolean connectionClosed(final AuthenticationConfiguration authenticationConfiguration, final FutureResult<Connection> futureResult) {
// we can't possibly care; this is probably a bug, even
return true;
}
}
final class MaybeShared extends State {
private final AuthenticationConfiguration authenticationConfiguration;
private final IoFuture<Connection> attempt;
private final Map<AuthenticationConfiguration, FutureResult<Connection>> pendingAttempts = new ConcurrentHashMap<>();
MaybeShared(final AuthenticationConfiguration authenticationConfiguration, final IoFuture<Connection> attempt) {
this.authenticationConfiguration = authenticationConfiguration;
this.attempt = attempt;
}
IoFuture<Connection> getConnection(final EndpointImpl endpoint, final ConnectionKey key, final AuthenticationConfiguration authenticationConfiguration, boolean doConnect) {
if (authenticationConfiguration.equals(this.authenticationConfiguration)) {
return attempt;
} else {
FutureResult<Connection> futureResult = pendingAttempts.get(authenticationConfiguration);
if (futureResult != null) {
return futureResult.getIoFuture();
} else {
if (! doConnect) {
return null;
}
futureResult = new FutureResult<>(endpoint.getExecutor());
final FutureResult<Connection> appearing = pendingAttempts.putIfAbsent(authenticationConfiguration, futureResult);
if (appearing != null) {
return appearing.getIoFuture();
}
}
assert doConnect;
final IoFuture<Connection> ioFuture = futureResult.getIoFuture();
final AtomicBoolean cancelFlag = new AtomicBoolean();
final FutureResult<Connection> finalFutureResult = futureResult;
futureResult.addCancelHandler(new Cancellable() {
public Cancellable cancel() {
cancelFlag.set(true);
finalFutureResult.setCancelled();
return this;
}
});
attempt.addNotifier((f2, futureResult1) -> {
if (cancelFlag.get()) {
futureResult1.setCancelled();
} else {
final IoFuture<Connection> realAttempt = ConnectionInfo.this.getConnection(endpoint, key, authenticationConfiguration, true);
futureResult1.addCancelHandler(realAttempt);
splice(futureResult1, realAttempt, authenticationConfiguration);
}
}, futureResult);
return ioFuture;
}
}
boolean connectionClosed(final AuthenticationConfiguration authenticationConfiguration, final FutureResult<Connection> futureResult) {
// an early notification... we should be done though, so synchronize and retry
attempt.await();
// try again
return false;
}
}
final class Shared extends State {
private final FutureResult<Connection> sharedConnection;
private final Map<AuthenticationConfiguration, FutureResult<Connection>> leftovers;
Shared(final FutureResult<Connection> sharedConnection, final Map<AuthenticationConfiguration, FutureResult<Connection>> leftovers) {
this.sharedConnection = sharedConnection;
this.leftovers = leftovers;
}
IoFuture<Connection> getConnection(final EndpointImpl endpoint, final ConnectionKey key, final AuthenticationConfiguration authenticationConfiguration, boolean doConnect) {
return leftovers.getOrDefault(authenticationConfiguration, sharedConnection).getIoFuture();
}
boolean connectionClosed(final AuthenticationConfiguration authenticationConfiguration, final FutureResult<Connection> futureResult) {
final State newState;
if (futureResult == sharedConnection) {
// shared connection closed :-(
if (leftovers.isEmpty()) {
newState = new None();
} else {
// not ideal, but also extremely unlikely
newState = new NotShared(leftovers);
}
} else {
final FutureResult<Connection> mapVal = leftovers.get(authenticationConfiguration);
if (! futureResult.equals(mapVal)) {
// nothing to do
return true;
}
// swap map, maybe
Map<AuthenticationConfiguration, FutureResult<Connection>> newMap;
if (leftovers.size() == 1) {
newMap = Collections.emptyMap();
} else {
newMap = new HashMap<>(leftovers);
newMap.remove(authenticationConfiguration);
}
newState = new Shared(sharedConnection, newMap);
}
synchronized (ConnectionInfo.this) {
if (state == this) {
state = newState;
return true;
}
}
// try again :(
return false;
}
}
final class NotShared extends State {
private final Map<AuthenticationConfiguration, FutureResult<Connection>> connections;
NotShared(final Map<AuthenticationConfiguration, FutureResult<Connection>> connections) {
this.connections = connections;
}
IoFuture<Connection> getConnection(final EndpointImpl endpoint, final ConnectionKey key, final AuthenticationConfiguration authenticationConfiguration, boolean doConnect) {
final FutureResult<Connection> future = connections.get(authenticationConfiguration);
if (future != null) {
return future.getIoFuture();
}
if (! doConnect) {
return null;
}
// add a new unshared connection
State oldState;
synchronized (ConnectionInfo.this) {
oldState = ConnectionInfo.this.state;
if (oldState == this) {
final IoFuture<Connection> attempt = endpoint.connect(key.getRealUri(), null, connectOptions, key.getSslContext(), authenticationConfiguration);
Map<AuthenticationConfiguration, FutureResult<Connection>> newConnections = new HashMap<>(connections);
final FutureResult<Connection> futureResult = new FutureResult<>();
splice(futureResult, attempt, authenticationConfiguration);
newConnections.put(authenticationConfiguration, futureResult);
return attempt;
}
}
// try again :(
return RETRY;
}
boolean connectionClosed(final AuthenticationConfiguration authenticationConfiguration, final FutureResult<Connection> futureResult) {
final FutureResult<Connection> mapVal = connections.get(authenticationConfiguration);
if (! futureResult.equals(mapVal)) {
// nothing to do
return true;
}
// swap map, maybe
final State newState;
if (connections.size() == 1) {
newState = new None();
} else {
Map<AuthenticationConfiguration, FutureResult<Connection>> newMap = new HashMap<>(connections);
newMap.remove(authenticationConfiguration);
newState = new NotShared(newMap);
}
synchronized (ConnectionInfo.this) {
if (state == this) {
state = newState;
return true;
}
}
// try again :(
return false;
}
}
void splice(FutureResult<Connection> futureResult, IoFuture<Connection> realFuture, final AuthenticationConfiguration authConfig) {
// always add in this order
futureResult.addCancelHandler(realFuture);
realFuture.addNotifier(new IoFuture.HandlingNotifier<Connection, FutureResult<Connection>>() {
public void handleCancelled(final FutureResult<Connection> futureResult1) {
futureResult1.setCancelled();
}
public void handleFailed(final IOException exception, final FutureResult<Connection> futureResult1) {
futureResult1.setException(exception);
}
public void handleDone(final Connection connection, final FutureResult<Connection> futureResult1) {
futureResult1.setResult(new ManagedConnection(connection, ConnectionInfo.this, authConfig, futureResult1));
}
}, futureResult);
}
static class EmptyIoFuture implements IoFuture<Connection> {
public IoFuture<Connection> cancel() {
throw Assert.unsupported();
}
public Status getStatus() {
throw Assert.unsupported();
}
public Status await() {
throw Assert.unsupported();
}
public Status await(final long time, final TimeUnit timeUnit) {
throw Assert.unsupported();
}
public Status awaitInterruptibly() throws InterruptedException {
throw Assert.unsupported();
}
public Status awaitInterruptibly(final long time, final TimeUnit timeUnit) throws InterruptedException {
throw Assert.unsupported();
}
public Connection get() throws IOException, CancellationException {
throw Assert.unsupported();
}
public Connection getInterruptibly() throws IOException, InterruptedException, CancellationException {
throw Assert.unsupported();
}
public IOException getException() throws IllegalStateException {
throw Assert.unsupported();
}
public <A> IoFuture<Connection> addNotifier(final Notifier<? super Connection, A> notifier, final A attachment) {
throw Assert.unsupported();
}
}
}