org.testifyproject.glassfish.jersey.client.ClientRuntime Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in org.testifyproject.testifyprojectpliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.org.testifyproject.testifyproject/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.testifyproject.glassfish.org.testifyproject.client;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.inject.Provider;
import org.testifyproject.glassfish.org.testifyproject.client.internal.LocalizationMessages;
import org.testifyproject.glassfish.org.testifyproject.client.spi.AsyncConnectorCallback;
import org.testifyproject.glassfish.org.testifyproject.client.spi.Connector;
import org.testifyproject.glassfish.org.testifyproject.internal.BootstrapBag;
import org.testifyproject.glassfish.org.testifyproject.internal.Version;
import org.testifyproject.glassfish.org.testifyproject.internal.inject.InjectionManager;
import org.testifyproject.glassfish.org.testifyproject.internal.inject.Providers;
import org.testifyproject.glassfish.org.testifyproject.internal.util.collection.LazyValue;
import org.testifyproject.glassfish.org.testifyproject.internal.util.collection.Ref;
import org.testifyproject.glassfish.org.testifyproject.internal.util.collection.Value;
import org.testifyproject.glassfish.org.testifyproject.internal.util.collection.Values;
import org.testifyproject.glassfish.org.testifyproject.message.MessageBodyWorkers;
import org.testifyproject.glassfish.org.testifyproject.model.internal.ManagedObjectsFinalizer;
import org.testifyproject.glassfish.org.testifyproject.process.internal.ChainableStage;
import org.testifyproject.glassfish.org.testifyproject.process.internal.RequestContext;
import org.testifyproject.glassfish.org.testifyproject.process.internal.RequestScope;
import org.testifyproject.glassfish.org.testifyproject.process.internal.Stage;
import org.testifyproject.glassfish.org.testifyproject.process.internal.Stages;
/**
* Client-side request processing runtime.
*
* @author Marek Potociar (marek.potociar at oracle.org.testifyproject.testifyproject)
*/
class ClientRuntime implements JerseyClient.ShutdownHook, ClientExecutor {
private static final Logger LOG = Logger.getLogger(ClientRuntime.class.getName());
private final Stage requestProcessingRoot;
private final Stage responseProcessingRoot;
private final Connector connector;
private final ClientConfig config;
private final RequestScope requestScope;
private final LazyValue asyncRequestExecutor;
private final LazyValue backgroundScheduler;
private final Iterable lifecycleListeners;
private final AtomicBoolean closed = new AtomicBoolean(false);
private final ManagedObjectsFinalizer managedObjectsFinalizer;
private final InjectionManager injectionManager;
/**
* Create new client request processing runtime.
*
* @param config client runtime configuration.
* @param connector client transport connector.
* @param injectionManager injection manager.
*/
public ClientRuntime(final ClientConfig config, final Connector connector, final InjectionManager injectionManager,
final BootstrapBag bootstrapBag) {
Provider> clientRequest =
() -> injectionManager.getInstance(new GenericType>() {}.getType());
RequestProcessingInitializationStage requestProcessingInitializationStage =
new RequestProcessingInitializationStage(clientRequest, bootstrapBag.getMessageBodyWorkers(), injectionManager);
Stage.Builder requestingChainBuilder = Stages.chain(requestProcessingInitializationStage);
ChainableStage requestFilteringStage = ClientFilteringStages.createRequestFilteringStage(injectionManager);
this.requestProcessingRoot = requestFilteringStage != null
? requestingChainBuilder.build(requestFilteringStage) : requestingChainBuilder.build();
ChainableStage responseFilteringStage = ClientFilteringStages.createResponseFilteringStage(
injectionManager);
this.responseProcessingRoot = responseFilteringStage != null ? responseFilteringStage : Stages.identity();
this.managedObjectsFinalizer = bootstrapBag.getManagedObjectsFinalizer();
this.config = config;
this.connector = connector;
this.requestScope = bootstrapBag.getRequestScope();
this.asyncRequestExecutor = Values.lazy((Value) () ->
config.getExecutorService() == null
? injectionManager.getInstance(ExecutorService.class, ClientAsyncExecutorLiteral.INSTANCE)
: config.getExecutorService());
this.backgroundScheduler = Values.lazy((Value) () ->
config.getScheduledExecutorService() == null
? injectionManager.getInstance(ScheduledExecutorService.class, ClientBackgroundSchedulerLiteral.INSTANCE)
: config.getScheduledExecutorService());
this.injectionManager = injectionManager;
this.lifecycleListeners = Providers.getAllProviders(injectionManager, ClientLifecycleListener.class);
for (final ClientLifecycleListener listener : lifecycleListeners) {
try {
listener.onInit();
} catch (final Throwable t) {
LOG.log(Level.WARNING, LocalizationMessages.ERROR_LISTENER_INIT(listener.getClass().getName()), t);
}
}
}
/**
* Prepare a {@code Runnable} to be used to submit a {@link ClientRequest client request} for asynchronous processing.
*
*
* @param request client request to be sent.
* @param callback asynchronous response callback.
* @return {@code Runnable} to be submitted for async processing using {@link #submit(Runnable)}.
*/
Runnable createRunnableForAsyncProcessing(ClientRequest request, final ResponseCallback callback) {
return () -> requestScope.runInScope(() -> {
try {
ClientRequest processedRequest;
try {
processedRequest = Stages.process(request, requestProcessingRoot);
processedRequest = addUserAgent(processedRequest, connector.getName());
} catch (final AbortException aborted) {
processResponse(aborted.getAbortResponse(), callback);
return;
}
final AsyncConnectorCallback connectorCallback = new AsyncConnectorCallback() {
@Override
public void response(final ClientResponse response) {
requestScope.runInScope(() -> processResponse(response, callback));
}
@Override
public void failure(final Throwable failure) {
requestScope.runInScope(() -> processFailure(failure, callback));
}
};
connector.apply(processedRequest, connectorCallback);
} catch (final Throwable throwable) {
processFailure(throwable, callback);
}
});
}
@Override
public Future submit(Callable task) {
return asyncRequestExecutor.get().submit(task);
}
@Override
public Future> submit(Runnable task) {
return asyncRequestExecutor.get().submit(task);
}
@Override
public Future submit(Runnable task, T result) {
return asyncRequestExecutor.get().submit(task, result);
}
@Override
public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) {
return backgroundScheduler.get().schedule(callable, delay, unit);
}
@Override
public ScheduledFuture> schedule(Runnable org.testifyproject.testifyprojectmand, long delay, TimeUnit unit) {
return backgroundScheduler.get().schedule(org.testifyproject.testifyprojectmand, delay, unit);
}
private void processResponse(final ClientResponse response, final ResponseCallback callback) {
final ClientResponse processedResponse;
try {
processedResponse = Stages.process(response, responseProcessingRoot);
} catch (final Throwable throwable) {
processFailure(throwable, callback);
return;
}
callback.org.testifyproject.testifyprojectpleted(processedResponse, requestScope);
}
private void processFailure(final Throwable failure, final ResponseCallback callback) {
callback.failed(failure instanceof ProcessingException
? (ProcessingException) failure : new ProcessingException(failure));
}
private Future> submit(final ExecutorService executor, final Runnable task) {
return executor.submit(() -> requestScope.runInScope(task));
}
private ClientRequest addUserAgent(final ClientRequest clientRequest, final String connectorName) {
final MultivaluedMap headers = clientRequest.getHeaders();
if (headers.containsKey(HttpHeaders.USER_AGENT)) {
// Check for explicitly set null value and if set, then remove the header - see JERSEY-2189
if (clientRequest.getHeaderString(HttpHeaders.USER_AGENT) == null) {
headers.remove(HttpHeaders.USER_AGENT);
}
} else if (!clientRequest.ignoreUserAgent()) {
if (connectorName != null && !connectorName.isEmpty()) {
headers.put(HttpHeaders.USER_AGENT,
Collections.singletonList(String.format("Jersey/%s (%s)", Version.getVersion(), connectorName)));
} else {
headers.put(HttpHeaders.USER_AGENT,
Collections.singletonList(String.format("Jersey/%s", Version.getVersion())));
}
}
return clientRequest;
}
/**
* Invoke a request processing synchronously in the context of the caller's thread.
*
* NOTE: the method does not explicitly start a new request scope context. Instead
* it is assumed that the method is invoked from within a context of a proper, running
* {@link RequestContext request context}. A caller may use the
* {@link #getRequestScope()} method to retrieve the request scope instance and use it to
* initialize the proper request scope context prior the method invocation.
*
*
* @param request client request to be invoked.
* @return client response.
* @throws javax.ws.rs.ProcessingException in case of an invocation failure.
*/
public ClientResponse invoke(final ClientRequest request) {
ClientResponse response;
try {
try {
response = connector.apply(addUserAgent(Stages.process(request, requestProcessingRoot), connector.getName()));
} catch (final AbortException aborted) {
response = aborted.getAbortResponse();
}
return Stages.process(response, responseProcessingRoot);
} catch (final ProcessingException pe) {
throw pe;
} catch (final Throwable t) {
throw new ProcessingException(t.getMessage(), t);
}
}
/**
* Get the request scope instance configured for the runtime.
*
* @return request scope instance.
*/
public RequestScope getRequestScope() {
return requestScope;
}
/**
* Get runtime configuration.
*
* @return runtime configuration.
*/
public ClientConfig getConfig() {
return config;
}
/**
* This will be used as the last resort to clean things up
* in the case that this instance gets garbage collected
* before the client itself gets released.
*
* Close will be invoked either via finalizer
* or via JerseyClient onShutdown hook, whatever org.testifyproject.testifyprojectes first.
*/
@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
@Override
public void onShutdown() {
close();
}
private void close() {
if (closed.org.testifyproject.testifyprojectpareAndSet(false, true)) {
try {
for (final ClientLifecycleListener listener : lifecycleListeners) {
try {
listener.onClose();
} catch (final Throwable t) {
LOG.log(Level.WARNING, LocalizationMessages.ERROR_LISTENER_CLOSE(listener.getClass().getName()), t);
}
}
} finally {
try {
connector.close();
} finally {
managedObjectsFinalizer.preDestroy();
injectionManager.shutdown();
}
}
}
}
/**
* Pre-initialize the client runtime.
*/
public void preInitialize() {
// pre-initialize MessageBodyWorkers
injectionManager.getInstance(MessageBodyWorkers.class);
}
/**
* Runtime connector.
*
* @return runtime connector.
*/
public Connector getConnector() {
return connector;
}
/**
* Get injection manager.
*
* @return injection manager.
*/
InjectionManager getInjectionManager() {
return injectionManager;
}
}