diff --git a/src/main/java/org/jboss/ejb/client/ConfigurationBasedEJBClientContextSelector.java b/src/main/java/org/jboss/ejb/client/ConfigurationBasedEJBClientContextSelector.java index 5139dc2b7..9e545d6fc 100644 --- a/src/main/java/org/jboss/ejb/client/ConfigurationBasedEJBClientContextSelector.java +++ b/src/main/java/org/jboss/ejb/client/ConfigurationBasedEJBClientContextSelector.java @@ -41,11 +41,11 @@ import org.wildfly.common.Assert; /** - * A one-time, configuration-based EJB client context selector. + * A one-time, configuration-based EJB client context configurator. * * @author David M. Lloyd */ -public final class ConfigurationBasedEJBClientContextSelector implements Supplier { +final class ConfigurationBasedEJBClientContextSelector implements Supplier { private static final EJBClientContext configuredContext; private static final String NS_EJB_CLIENT_3_0 = "urn:jboss:ejb-client:3.0"; @@ -67,6 +67,7 @@ private static EJBClientContext loadConfiguration() { } catch (ConfigXMLParseException e) { throw new IllegalStateException(e); } + // TODO: parse ejb-client.properties instead right here // build a generic config instead final EJBClientContext.Builder builder = new EJBClientContext.Builder(); loadTransportProviders(builder, classLoader); @@ -228,6 +229,9 @@ private static void parseConnectionType(final ConfigurationXMLStreamReader strea streamReader.skipContent(); } } else if (next == END_ELEMENT) { + final EJBClientConnection.Builder connBuilder = new EJBClientConnection.Builder(); + connBuilder.setDestination(uri); + builder.addClientConnection(connBuilder.build()); return; } else { throw Assert.unreachableCode(); diff --git a/src/main/java/org/jboss/ejb/client/EJBClientConnection.java b/src/main/java/org/jboss/ejb/client/EJBClientConnection.java new file mode 100644 index 000000000..192568b73 --- /dev/null +++ b/src/main/java/org/jboss/ejb/client/EJBClientConnection.java @@ -0,0 +1,78 @@ +/* + * 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.ejb.client; + +import java.net.URI; + +import org.wildfly.common.Assert; + +/** + * Information about a configured connection on an EJB client context. + * + * @author David M. Lloyd + */ +public final class EJBClientConnection { + private final URI destination; + + EJBClientConnection(final Builder builder) { + destination = builder.destination; + } + + /** + * Get the connection destination URI. + * + * @return the connection destination URI (not {@code null}) + */ + public URI getDestination() { + return destination; + } + + /** + * A builder for a client connection definition. + */ + public static final class Builder { + URI destination; + + /** + * Construct a new instance. + */ + public Builder() { + } + + /** + * Set the destination URI. + * + * @param destination the destination URI (must not be {@code null}) + */ + public void setDestination(final URI destination) { + Assert.checkNotNullParam("destination", destination); + this.destination = destination; + } + + /** + * Build a new {@link EJBClientConnection} instance based on the current contents of this builder. + * + * @return the new instance (not {@code null}) + */ + public EJBClientConnection build() { + Assert.checkNotNullParam("destination", destination); + return new EJBClientConnection(this); + } + } +} diff --git a/src/main/java/org/jboss/ejb/client/EJBClientContext.java b/src/main/java/org/jboss/ejb/client/EJBClientContext.java index ab4edc8e1..014513f39 100644 --- a/src/main/java/org/jboss/ejb/client/EJBClientContext.java +++ b/src/main/java/org/jboss/ejb/client/EJBClientContext.java @@ -28,6 +28,7 @@ import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -37,7 +38,6 @@ import org.wildfly.common.context.Contextual; import org.wildfly.discovery.Discovery; import org.wildfly.discovery.FilterSpec; -import org.wildfly.discovery.ServiceRegistry; import org.wildfly.discovery.ServiceType; import org.wildfly.discovery.ServicesQueue; @@ -106,8 +106,8 @@ public final class EJBClientContext extends Attachable implements Contextual configuredConnections; EJBClientContext(Builder builder) { final List builderInterceptors = builder.interceptors; @@ -122,9 +122,16 @@ public final class EJBClientContext extends Attachable implements Contextual clientConnections = builder.clientConnections; + if (clientConnections == null || clientConnections.isEmpty()) { + configuredConnections = Collections.emptyList(); + } else if (clientConnections.size() == 1) { + configuredConnections = Collections.singletonList(clientConnections.get(0)); + } else { + configuredConnections = Collections.unmodifiableList(new ArrayList<>(clientConnections)); + } // this must be last for (EJBTransportProvider transportProvider : transportProviders) { transportProvider.notifyRegistered(receiverContext); @@ -158,6 +165,16 @@ public long getInvocationTimeout() { return invocationTimeout; } + /** + * Get the pre-configured connections for this context. This information may not be used by some transport providers + * and mainly exists for legacy compatibility purposes. + * + * @return the pre-configured connections for this context (not {@code null}) + */ + public List getConfiguredConnections() { + return configuredConnections; + } + /** * Get a copy of this context with the given interceptor(s) added. If the array is {@code null} or empty, the * current context is returned as-is. @@ -231,10 +248,6 @@ Discovery getDiscovery() { return DISCOVERY_SUPPLIER.get(); } - ServiceRegistry getServiceRegistry() { - return serviceRegistry; - } - /** * A builder for EJB client contexts. */ @@ -242,7 +255,7 @@ public static final class Builder { List interceptors; List transportProviders; - ServiceRegistry serviceRegistry = doPrivileged((PrivilegedAction) ServiceRegistry.getContextManager()::get); + List clientConnections; /** * Construct a new instance. @@ -259,6 +272,7 @@ public Builder() { if (transportProviders.length > 0) { this.transportProviders = new ArrayList<>(Arrays.asList(transportProviders)); } + clientConnections = new ArrayList<>(ejbClientContext.getConfiguredConnections()); } public void addInterceptor(EJBClientInterceptor interceptor) { @@ -277,9 +291,12 @@ public void addTransportProvider(EJBTransportProvider provider) { transportProviders.add(provider); } - public void setServiceRegistry(final ServiceRegistry serviceRegistry) { - Assert.checkNotNullParam("serviceRegistry", serviceRegistry); - this.serviceRegistry = serviceRegistry; + public void addClientConnection(EJBClientConnection connection) { + Assert.checkNotNullParam("connection", connection); + if (clientConnections == null) { + clientConnections = new ArrayList<>(); + } + clientConnections.add(connection); } public EJBClientContext build() { diff --git a/src/main/java/org/jboss/ejb/client/EJBReceiverContext.java b/src/main/java/org/jboss/ejb/client/EJBReceiverContext.java index c754f66a2..59f1d2a8d 100644 --- a/src/main/java/org/jboss/ejb/client/EJBReceiverContext.java +++ b/src/main/java/org/jboss/ejb/client/EJBReceiverContext.java @@ -18,8 +18,6 @@ package org.jboss.ejb.client; -import org.wildfly.discovery.ServiceRegistry; - /** * A context which is provided to EJB receiver implementations in order to perform operations on the client context. * @@ -32,15 +30,6 @@ public final class EJBReceiverContext { this.clientContext = clientContext; } - /** - * Get the discovery service registry to use for server discovery events. - * - * @return the discovery service registry - */ - public ServiceRegistry getServiceRegistry() { - return clientContext.getServiceRegistry(); - } - /** * Get the client context that corresponds to this receiver context. * diff --git a/src/main/java/org/jboss/ejb/protocol/remote/EJBClientChannel.java b/src/main/java/org/jboss/ejb/protocol/remote/EJBClientChannel.java index a2da11b93..e2ee56167 100644 --- a/src/main/java/org/jboss/ejb/protocol/remote/EJBClientChannel.java +++ b/src/main/java/org/jboss/ejb/protocol/remote/EJBClientChannel.java @@ -88,6 +88,8 @@ import org.wildfly.discovery.ServiceRegistration; import org.wildfly.discovery.ServiceRegistry; import org.wildfly.discovery.ServiceURL; +import org.wildfly.discovery.impl.LocalRegistryAndDiscoveryProvider; +import org.wildfly.discovery.spi.DiscoveryProvider; import org.wildfly.security.auth.AuthenticationException; import org.wildfly.transaction.client.ContextTransactionManager; import org.wildfly.transaction.client.LocalTransaction; @@ -122,6 +124,7 @@ class EJBClientChannel { private final RemoteTransactionContext transactionContext; private final AtomicReference> futureResultRef; + private final LocalRegistryAndDiscoveryProvider discoveryProvider = new LocalRegistryAndDiscoveryProvider(); EJBClientChannel(final Channel channel, final int version, final FutureResult futureResult) { this.channel = channel; @@ -139,7 +142,7 @@ class EJBClientChannel { // server does not present v3 unless the transaction service is also present } transactionContext = RemoteTransactionContext.getInstance(); - this.serviceRegistry = REGISTRY_SUPPLIER.get(); + this.serviceRegistry = ServiceRegistry.create(discoveryProvider); this.configuration = configuration; invocationTracker = new InvocationTracker(this.channel, channel.getOption(RemotingOptions.MAX_OUTBOUND_MESSAGES).intValue(), EJBClientChannel::mask); futureResultRef = new AtomicReference<>(futureResult); @@ -791,6 +794,10 @@ UserTransactionID allocateUserTransactionID() { } } + DiscoveryProvider getDiscoveryProvider() { + return discoveryProvider; + } + final class MethodInvocation extends Invocation { private final EJBReceiverInvocationContext receiverInvocationContext; private final AtomicInteger refCounter = new AtomicInteger(1); diff --git a/src/main/java/org/jboss/ejb/protocol/remote/RemotingEJBDiscoveryProvider.java b/src/main/java/org/jboss/ejb/protocol/remote/RemotingEJBDiscoveryProvider.java new file mode 100644 index 000000000..f60c81957 --- /dev/null +++ b/src/main/java/org/jboss/ejb/protocol/remote/RemotingEJBDiscoveryProvider.java @@ -0,0 +1,132 @@ +/* + * 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.ejb.protocol.remote; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jboss.ejb.client.EJBClientConnection; +import org.jboss.ejb.client.EJBClientContext; +import org.jboss.remoting3.Connection; +import org.jboss.remoting3.Endpoint; +import org.wildfly.discovery.FilterSpec; +import org.wildfly.discovery.ServiceType; +import org.wildfly.discovery.ServiceURL; +import org.wildfly.discovery.spi.DiscoveryProvider; +import org.wildfly.discovery.spi.DiscoveryRequest; +import org.wildfly.discovery.spi.DiscoveryResult; +import org.xnio.IoFuture; +import org.xnio.OptionMap; + +/** + * @author David M. Lloyd + */ +final class RemotingEJBDiscoveryProvider implements DiscoveryProvider { + static final RemotingEJBDiscoveryProvider INSTANCE = new RemotingEJBDiscoveryProvider(); + + private RemotingEJBDiscoveryProvider() { + } + + public DiscoveryRequest discover(final ServiceType serviceType, final FilterSpec filterSpec, final DiscoveryResult result) { + if (! serviceType.implies(ServiceType.of("ejb", "jboss"))) { + // only respond to requests for JBoss EJB services + result.complete(); + return DiscoveryRequest.NULL; + } + final EJBClientContext ejbClientContext = EJBClientContext.getCurrent(); + final RemoteEJBReceiver ejbReceiver = ejbClientContext.getAttachment(RemoteTransportProvider.ATTACHMENT_KEY); + if (ejbReceiver == null) { + // ??? + result.complete(); + return DiscoveryRequest.NULL; + } + final Endpoint endpoint = Endpoint.getCurrent(); + final List connections = ejbClientContext.getConfiguredConnections(); + if (connections.isEmpty()) { + result.complete(); + return DiscoveryRequest.NULL; + } + final AtomicInteger connectionCount = new AtomicInteger(connections.size() + 1); + final List cancellers = Collections.synchronizedList(new ArrayList<>()); + for (EJBClientConnection connection : connections) { + final URI uri = connection.getDestination(); + final String scheme = uri.getScheme(); + if (scheme == null || ! ejbReceiver.getRemoteTransportProvider().supportsProtocol(scheme) || ! endpoint.isValidUriScheme(scheme)) { + continue; + } + final IoFuture future = endpoint.getConnection(uri); + cancellers.add(future::cancel); + future.addNotifier(new IoFuture.HandlingNotifier() { + public void handleCancelled(final DiscoveryResult discoveryResult) { + countDown(connectionCount, discoveryResult); + } + + public void handleFailed(final IOException exception, final DiscoveryResult discoveryResult) { + countDown(connectionCount, discoveryResult); + } + + public void handleDone(final Connection data, final DiscoveryResult discoveryResult) { + final IoFuture future = ejbReceiver.serviceHandle.getClientService(data, OptionMap.EMPTY); + cancellers.add(future::cancel); + future.addNotifier(new IoFuture.HandlingNotifier() { + public void handleCancelled(final DiscoveryResult discoveryResult) { + countDown(connectionCount, discoveryResult); + } + + public void handleFailed(final IOException exception, final DiscoveryResult discoveryResult) { + countDown(connectionCount, discoveryResult); + } + + public void handleDone(final EJBClientChannel clientChannel, final DiscoveryResult discoveryResult) { + final DiscoveryRequest request = clientChannel.getDiscoveryProvider().discover(serviceType, filterSpec, new DiscoveryResult() { + public void complete() { + countDown(connectionCount, discoveryResult); + } + + public void addMatch(final ServiceURL serviceURL) { + discoveryResult.addMatch(serviceURL); + } + }); + cancellers.add(request::cancel); + } + }, discoveryResult); + } + + }, result); + } + countDown(connectionCount, result); + return () -> { + synchronized (cancellers) { + for (Runnable canceller : cancellers) { + canceller.run(); + } + } + }; + } + + static void countDown(final AtomicInteger connectionCount, final DiscoveryResult discoveryResult) { + if (connectionCount.decrementAndGet() == 0) { + discoveryResult.complete(); + } + } +}