org.wildfly.httpclient.naming.HttpRootContext Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* 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.wildfly.httpclient.naming;
import static java.security.AccessController.doPrivileged;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.naming.Binding;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.net.ssl.SSLContext;
import org.jboss.marshalling.InputStreamByteInput;
import org.jboss.marshalling.Marshaller;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
import org.jboss.marshalling.Unmarshaller;
import org.wildfly.httpclient.common.ContentType;
import org.wildfly.httpclient.common.HttpTargetContext;
import org.wildfly.httpclient.common.WildflyHttpContext;
import org.wildfly.naming.client.AbstractContext;
import org.wildfly.naming.client.CloseableNamingEnumeration;
import org.wildfly.naming.client.ExhaustedDestinationsException;
import org.wildfly.naming.client.NamingOperation;
import org.wildfly.naming.client.ProviderEnvironment;
import org.wildfly.naming.client.RetryContext;
import org.wildfly.naming.client._private.Messages;
import org.wildfly.naming.client.util.FastHashtable;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
import org.xnio.IoUtils;
import io.undertow.client.ClientRequest;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.Methods;
import io.undertow.util.StatusCodes;
/**
* @author Stuart Douglas
*/
public class HttpRootContext extends AbstractContext {
private static final int MAX_NOT_FOUND_RETRY = Integer.getInteger("org.wildfly.httpclient.naming.max-retries", 8);
private static final AuthenticationContextConfigurationClient CLIENT = doPrivileged(AuthenticationContextConfigurationClient.ACTION);
private static final PrivilegedAction GET_TCCL_ACTION = new PrivilegedAction() {
@Override
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
};
private final String ACCEPT_VALUE = "application/x-wf-jndi-jbmar-value;version=1,application/x-wf-jbmar-exception;version=1";
private final ContentType VALUE_TYPE = new ContentType("application/x-wf-jndi-jbmar-value", 1);
private final HttpNamingProvider httpNamingProvider;
private final String scheme;
private static final HttpNamingEjbObjectResolverHelper helper;
static {
HttpNamingEjbObjectResolverHelper h = null;
ServiceLoader sl = doPrivileged((PrivilegedAction>)
() -> ServiceLoader.load(HttpNamingEjbObjectResolverHelper.class));
for (Iterator it = sl.iterator(); it.hasNext(); ) {
h = it.next();
break;
}
helper = h;
}
protected HttpRootContext(FastHashtable environment, HttpNamingProvider httpNamingProvider, String scheme) {
super(environment);
this.httpNamingProvider = httpNamingProvider;
this.scheme = scheme;
}
@Override
public void bind(String name, Object obj) throws NamingException {
super.bind(name, obj);
}
@Override
protected Object lookupNative(Name name) throws NamingException {
return processInvocation(name, Methods.POST, "naming/v1/lookup/");
}
@Override
protected Object lookupLinkNative(Name name) throws NamingException {
return processInvocation(name, Methods.POST, "naming/v1/lookuplink/");
}
@Override
protected CloseableNamingEnumeration listNative(Name name) throws NamingException {
Collection result = (Collection) processInvocation(name, Methods.GET, "naming/v1/list/");
return CloseableNamingEnumeration.fromIterable(result);
}
@Override
protected CloseableNamingEnumeration listBindingsNative(Name name) throws NamingException {
Collection result = (Collection) processInvocation(name, Methods.GET, "naming/v1/list-bindings/");
return CloseableNamingEnumeration.fromIterable(result);
}
@Override
protected void bindNative(Name name, Object obj) throws NamingException {
processInvocation(name, Methods.PUT, obj, "naming/v1/bind/", null);
}
@Override
protected void rebindNative(Name name, Object obj) throws NamingException {
processInvocation(name, Methods.PATCH, obj, "naming/v1/rebind/", null);
}
@Override
protected void unbindNative(Name name) throws NamingException {
processInvocation(name, Methods.DELETE, null,"naming/v1/unbind/", null);
}
@Override
protected void renameNative(Name oldName, Name newName) throws NamingException {
//TODO no result expected
processInvocation(oldName, Methods.PATCH, null,"naming/v1/rename/", newName);
}
@Override
protected void destroySubcontextNative(Name name) throws NamingException {
processInvocation(name, Methods.DELETE, null,"naming/v1/dest-subctx/", null);
}
@Override
protected Context createSubcontextNative(Name name) throws NamingException {
processInvocation(name, Methods.PUT, "naming/v1/create-subcontext/");
return new HttpRemoteContext(this, name.toString());
}
private static MarshallingConfiguration createMarshallingConfig(URI uri) {
final MarshallingConfiguration marshallingConfiguration = new MarshallingConfiguration();
marshallingConfiguration.setVersion(2);
if (helper != null) {
marshallingConfiguration.setObjectResolver(helper.getObjectResolver(uri));
}
return marshallingConfiguration;
}
private R performWithRetry(NamingOperation function, ProviderEnvironment environment, RetryContext context, Name name, T param) throws NamingException {
// Directly pass-through single provider executions
if (context == null) {
return function.apply(null, name, param);
}
for (int notFound = 0; ; ) {
try {
R result = function.apply(context, name, param);
environment.dropFromBlocklist(context.currentDestination());
return result;
} catch (NameNotFoundException e) {
if (notFound++ > MAX_NOT_FOUND_RETRY) {
Messages.log.tracef("Maximum name not found attempts exceeded,");
throw e;
}
URI location = context.currentDestination();
Messages.log.tracef("Provider (%s) did not have name \"%s\" (or a portion), retrying other nodes", location, name);
// Always throw NameNotFoundException, unless we find it on another host
context.addExplicitFailure(e);
context.addTransientFail(location);
} catch (ExhaustedDestinationsException e) {
throw e;
} catch (CommunicationException t) {
URI location = context.currentDestination();
Messages.log.tracef(t, "Communication error while contacting %s", location);
updateBlocklist(environment, context, t);
context.addFailure(injectDestination(t, location));
} catch (NamingException e) {
// All other naming exceptions are legit errors
environment.dropFromBlocklist(context.currentDestination());
throw e;
} catch (Throwable t) {
// Don't black-list generic throwables since it may indicate a client bug
URI location = context.currentDestination();
Messages.log.tracef(t, "Unexpected throwable while contacting %s", location);
context.addTransientFail(location);
context.addFailure(injectDestination(t, location));
}
}
}
private static Throwable injectDestination(Throwable t, URI destination) {
StackTraceElement[] stackTrace = new StackTraceElement[5];
System.arraycopy(t.getStackTrace(), 0, stackTrace, 1, 4);
stackTrace[0] = new StackTraceElement("", "..use of destination...", destination.toString(), -1);
t.setStackTrace(stackTrace);
return t;
}
private void updateBlocklist(ProviderEnvironment environment, RetryContext context, Throwable t) {
URI location = context.currentDestination();
Messages.log.tracef(t, "Provider (%s) failed, blocklisting and retrying", location);
environment.updateBlocklist(location);
}
private Object processInvocation(Name name, HttpString method, String pathSegment) throws NamingException {
ProviderEnvironment environment = httpNamingProvider.getProviderEnvironment();
final RetryContext context = canRetry(environment) ? new RetryContext() : null;
return performWithRetry((contextOrNull, name1, param) -> {
try {
HttpNamingProvider.HttpPeerIdentity peerIdentity = (HttpNamingProvider.HttpPeerIdentity) httpNamingProvider.getPeerIdentityForNamingUsingRetry(contextOrNull);
StringBuilder sb = new StringBuilder();
String uriPath = peerIdentity.getUri().getPath();
sb.append(uriPath);
if (!uriPath.endsWith("/")) {
sb.append("/");
}
sb.append(pathSegment)
.append(URLEncoder.encode(name.toString(), StandardCharsets.UTF_8.name()));
final ClientRequest clientRequest = new ClientRequest()
.setPath(sb.toString())
.setMethod(method);
clientRequest.getRequestHeaders().put(Headers.ACCEPT, ACCEPT_VALUE);
return performOperation(name1, peerIdentity.getUri(), clientRequest);
} catch (UnsupportedEncodingException e) {
NamingException namingException = new NamingException(e.getMessage());
namingException.initCause(e);
throw namingException;
}
}, environment, context, name, null);
}
private Object performOperation(Name name, URI providerUri, ClientRequest clientRequest) throws NamingException {
final CompletableFuture