com.sun.jersey.api.client.Client Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2011 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 compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/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 packager/legal/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 com.sun.jersey.api.client;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.api.client.filter.Filterable;
import com.sun.jersey.client.impl.CopyOnWriteHashMap;
import com.sun.jersey.client.proxy.ViewProxy;
import com.sun.jersey.client.proxy.ViewProxyProvider;
import com.sun.jersey.client.urlconnection.URLConnectionClientHandler;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentInjector;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.core.spi.component.ProviderFactory;
import com.sun.jersey.core.spi.component.ProviderServices;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProcessor;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProcessorFactory;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProcessorFactoryInitializer;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
import com.sun.jersey.core.spi.component.ioc.IoCProviderFactory;
import com.sun.jersey.core.spi.factory.ContextResolverFactory;
import com.sun.jersey.core.spi.factory.InjectableProviderFactory;
import com.sun.jersey.core.spi.factory.MessageBodyFactory;
import com.sun.jersey.core.util.FeaturesAndProperties;
import com.sun.jersey.core.util.LazyVal;
import com.sun.jersey.spi.MessageBodyWorkers;
import com.sun.jersey.spi.inject.ClientSide;
import com.sun.jersey.spi.inject.Errors;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider;
import com.sun.jersey.spi.service.ServiceFinder;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The main class for creating {@link WebResource} instances and configuring
* the properties of connections and requests.
*
* {@link ClientFilter} instances may be added to the client for filtering
* requests and responses, including those of {@link WebResource} instances
* created from the client.
*
* A client may be configured by passing a {@link ClientConfig} instance to
* the appropriate constructor.
*
* Methods to create instances of {@link WebResource} are thread-safe. Methods
* that modify configuration and or filters are not guaranteed to be
* thread-safe.
*
* The creation of a Client
instance is an expensive operation and
* the instance may make use of and retain many resources. It is therefore
* recommended that a Client
instance is reused for the creation of
* {@link WebResource} instances that require the same configuration settings.
*
* A client may integrate with an IoC framework by passing a
* {@link IoCComponentProviderFactory} instance to the appropriate constructor.
*
* @author [email protected]
*/
public class Client extends Filterable implements ClientHandler {
private static final Logger LOGGER = Logger.getLogger(Client.class.getName());
private ProviderFactory componentProviderFactory;
private Providers providers;
private boolean destroyed = false;
private LazyVal executorService;
private CopyOnWriteHashMap properties;
private Set vpps;
private MessageBodyFactory workers;
private static class ContextInjectableProvider extends
SingletonTypeInjectableProvider {
ContextInjectableProvider(Type type, T instance) {
super(type, instance);
}
}
/**
* Create a new client instance.
*
*/
public Client() {
this(createDefaultClientHander(), new DefaultClientConfig(), null);
}
/**
* Create a new client instance.
*
* @param root the root client handler for dispatching a request and
* returning a response.
*/
public Client(ClientHandler root) {
this(root, new DefaultClientConfig(), null);
}
/**
* Create a new client instance with a client configuration.
*
* @param root the root client handler for dispatching a request and
* returning a response.
* @param config the client configuration.
*/
public Client(ClientHandler root, ClientConfig config) {
this(root, config, null);
}
/**
* Create a new instance with a client configuration and a
* component provider.
*
* @param root the root client handler for dispatching a request and
* returning a response.
* @param config the client configuration.
* @param provider the IoC component provider factory.
*/
public Client(final ClientHandler root, final ClientConfig config,
final IoCComponentProviderFactory provider) {
// Defer instantiation of root to component provider
super(root);
Errors.processWithErrors(new Errors.Closure() {
@Override
public Void f() {
Errors.setReportMissingDependentFieldOrMethod(false);
init(root, config, provider);
return null;
}
});
}
private void init(ClientHandler root, ClientConfig config,
IoCComponentProviderFactory provider) {
final Object threadpoolSize = config.getProperties().get(ClientConfig.PROPERTY_THREADPOOL_SIZE);
this.executorService = new LazyVal() {
@Override
protected ExecutorService instance() {
if(threadpoolSize != null && threadpoolSize instanceof Integer && (Integer)threadpoolSize > 0) {
return Executors.newFixedThreadPool((Integer) threadpoolSize);
} else {
return Executors.newCachedThreadPool();
}
}
};
Class>[] components = ServiceFinder.find("jersey-client-components").toClassArray();
if (components.length > 0) {
if (LOGGER.isLoggable(Level.INFO)) {
StringBuilder b = new StringBuilder();
b.append("Adding the following classes declared in META-INF/services/jersey-client-components to the client configuration:");
for (Class c : components)
b.append('\n').append(" ").append(c);
LOGGER.log(Level.INFO, b.toString());
}
config = new ComponentsClientConfig(config, components);
}
final InjectableProviderFactory injectableFactory = new InjectableProviderFactory();
getProperties().putAll(config.getProperties());
if (provider != null) {
if (provider instanceof IoCComponentProcessorFactoryInitializer) {
IoCComponentProcessorFactoryInitializer i = (IoCComponentProcessorFactoryInitializer)provider;
i.init(new ComponentProcessorFactoryImpl(injectableFactory));
}
}
// Set up the component provider factory
this.componentProviderFactory = (provider == null)
? new ProviderFactory(injectableFactory)
: new IoCProviderFactory(injectableFactory, provider);
ProviderServices providerServices = new ProviderServices(
ClientSide.class,
this.componentProviderFactory,
config.getClasses(),
config.getSingletons());
// Get the set of WebResourceProxy
vpps = providerServices.getServices(ViewProxyProvider.class);
// Allow injection of features and properties
injectableFactory.add(new ContextInjectableProvider(
FeaturesAndProperties.class, config));
// Allow injection of client config
injectableFactory.add(new ContextInjectableProvider(
ClientConfig.class, config));
// Allow injection of client
injectableFactory.add(new ContextInjectableProvider(
Client.class, this));
injectableFactory.configure(providerServices);
// Obtain all context resolvers
final ContextResolverFactory crf = new ContextResolverFactory();
// Obtain all message body readers/writers
final MessageBodyFactory bodyContext = new MessageBodyFactory(providerServices,
config.getFeature(FeaturesAndProperties.FEATURE_PRE_1_4_PROVIDER_PRECEDENCE));
workers = bodyContext;
// Allow injection of message body context
injectableFactory.add(new ContextInjectableProvider(
MessageBodyWorkers.class, bodyContext));
// Injection of Providers
this.providers = new Providers() {
@Override
public MessageBodyReader getMessageBodyReader(Class c, Type t,
Annotation[] as, MediaType m) {
return bodyContext.getMessageBodyReader(c, t, as, m);
}
@Override
public MessageBodyWriter getMessageBodyWriter(Class c, Type t,
Annotation[] as, MediaType m) {
return bodyContext.getMessageBodyWriter(c, t, as, m);
}
@Override
public ExceptionMapper getExceptionMapper(Class c) {
throw new IllegalArgumentException("This method is not supported on the client side");
}
@Override
public ContextResolver getContextResolver(Class ct, MediaType m) {
return crf.resolve(ct, m);
}
};
injectableFactory.add(
new ContextInjectableProvider(
Providers.class, this.providers));
injectableFactory.add(new InjectableProvider() {
@Override
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
@Override
public Injectable getInjectable(ComponentContext ic, Context a, Type c) {
if (c instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType)c;
if (pt.getRawType() == Injectable.class) {
if (pt.getActualTypeArguments().length == 1) {
final Injectable> i = injectableFactory.getInjectable(
a.annotationType(),
ic,
a,
pt.getActualTypeArguments()[0],
ComponentScope.PERREQUEST_UNDEFINED_SINGLETON);
if (i == null)
return null;
return new Injectable() {
@Override
public Injectable getValue() {
return i;
}
};
}
}
}
return null;
}
});
// Initiate context resolvers
crf.init(providerServices, injectableFactory);
// Initiate message body readers/writers
bodyContext.init();
// Inject on all components
Errors.setReportMissingDependentFieldOrMethod(true);
componentProviderFactory.injectOnAllComponents();
componentProviderFactory.injectOnProviderInstances(config.getSingletons());
componentProviderFactory.injectOnProviderInstance(root);
}
private class ComponentProcessorFactoryImpl implements IoCComponentProcessorFactory {
private final InjectableProviderFactory injectableFactory;
ComponentProcessorFactoryImpl(InjectableProviderFactory injectableFactory) {
this.injectableFactory = injectableFactory;
}
@Override
public ComponentScope getScope(Class c) {
return ComponentScope.Singleton;
}
@Override
public IoCComponentProcessor get(Class c, ComponentScope scope) {
final ComponentInjector ci = new ComponentInjector(injectableFactory, c);
return new IoCComponentProcessor() {
@Override
public void preConstruct() {
}
@Override
public void postConstruct(Object o) {
ci.inject(o);
}
};
}
}
/**
* Destroy the client. Any system resources associated with the client
* will be cleaned up.
*
* This method must be called when there are not responses pending otherwise
* undefined behavior will occur.
*
* The client must not be reused after this method is called otherwise
* undefined behavior will occur.
*/
public void destroy() {
if (!destroyed) {
componentProviderFactory.destroy();
destroyed = true;
}
}
/**
* Defer to {@link #destroy() }
*/
@Override
@SuppressWarnings("FinalizeDeclaration")
protected void finalize() throws Throwable {
destroy();
super.finalize();
}
/**
* Get the {@link Providers} utilized by the client.
*
* @return the {@link Providers} utilized by the client.
*/
public Providers getProviders() {
return providers;
}
/**
* Get the {@link MessageBodyWorkers} utilized by the client.
*
* @return the {@link MessageBodyWorkers} utilized by the client.
*/
public MessageBodyWorkers getMessageBodyWorkers() {
return workers;
}
/**
* Create a Web resource from the client.
*
* @param u the URI of the resource.
* @return the Web resource.
*/
public WebResource resource(String u) {
return resource(URI.create(u));
}
/**
* Create a Web resource from the client.
*
* @param u the URI of the resource.
* @return the Web resource.
*/
public WebResource resource(URI u) {
return new WebResource(this, this.properties, u);
}
/**
* Create an asynchronous Web resource from the client.
*
* @param u the URI of the resource.
* @return the Web resource.
*/
public AsyncWebResource asyncResource(String u) {
return asyncResource(URI.create(u));
}
/**
* Create an asynchronous Web resource from the client.
*
* @param u the URI of the resource.
* @return the Web resource.
*/
public AsyncWebResource asyncResource(URI u) {
return new AsyncWebResource(this, this.properties, u);
}
public ViewResource viewResource(String u) {
return viewResource(URI.create(u));
}
public ViewResource viewResource(URI u) {
return new ViewResource(this, u);
}
public AsyncViewResource asyncViewResource(String u) {
return asyncViewResource(URI.create(u));
}
public AsyncViewResource asyncViewResource(URI u) {
return new AsyncViewResource(this, u);
}
public T view(String u, Class type) {
ViewResource vr = viewResource(u);
return vr.get(type);
}
public T view(URI uri, Class type) {
ViewResource vr = viewResource(uri);
return vr.get(type);
}
public T view(String u, T t) {
ViewResource vr = viewResource(u);
return vr.get(t);
}
public T view(URI uri, T t) {
ViewResource vr = viewResource(uri);
return vr.get(t);
}
public Future asyncView(String u, Class type) {
AsyncViewResource vr = asyncViewResource(u);
return vr.get(type);
}
public Future asyncView(URI uri, Class type) {
AsyncViewResource vr = asyncViewResource(uri);
return vr.get(type);
}
public Future asyncView(String u, T t) {
AsyncViewResource vr = asyncViewResource(u);
return vr.get(t);
}
public Future asyncView(URI uri, T t) {
AsyncViewResource vr = asyncViewResource(uri);
return vr.get(t);
}
public T view(Class c, ClientResponse response) {
return getViewProxy(c).view(c, response);
}
public T view(T t, ClientResponse response) {
return getViewProxy((Class)t.getClass()).view(t, response);
}
public ViewProxy getViewProxy(Class c) {
for (ViewProxyProvider vpp : vpps) {
ViewProxy vp = vpp.proxy(this, c);
if (vp != null) {
return vp;
}
}
throw new IllegalArgumentException("A view proxy is not " +
"available for the class '" + c.getName() + "'");
}
/**
* Set the {@link ExecutorService} for sending asynchronous
* HTTP requests when no underlying asynchronous HTTP implementation is
* utilized.
*
* @param es the {@link ExecutorService}.
* @since 1.4
*/
public void setExecutorService(ExecutorService es) {
if (es == null)
throw new IllegalArgumentException("ExecutorService service MUST not be null");
this.executorService.set(es);
}
/**
* Get the {@link ExecutorService} for sending asynchronous
* HTTP requests when no underlying asynchronous HTTP implementation is
* utilized.
*
* By default the implementation returned
* from {@link Executors#newCachedThreadPool() } is utilized.
*
* @return the {@link ExecutorService}.
* @since 1.4
*/
public ExecutorService getExecutorService() {
return executorService.get();
}
/**
* Get the mutable property bag.
*
* @return the property bag.
*/
public Map getProperties() {
if (properties == null)
properties = new CopyOnWriteHashMap();
return properties;
}
/**
* Set if redirection should be performed or not.
*
* This method is the functional equivalent to setting the property
* {@link ClientConfig#PROPERTY_FOLLOW_REDIRECTS} on the property bag
* returned from {@link #getProperties}
*
* @param redirect if true then the client will automatically redirect
* to the URI declared in 3xx responses.
*/
public void setFollowRedirects(Boolean redirect) {
getProperties().put(ClientConfig.PROPERTY_FOLLOW_REDIRECTS, redirect);
}
/**
* Set the read timeout interval, in milliseconds.
*
* This method is the functional equivalent to setting the property
* {@link ClientConfig#PROPERTY_READ_TIMEOUT} on the property bag
* returned from {@link #getProperties}
*
* @param interval the read timeout interval. If null or 0 then
* an interval of infinity is declared.
*/
public void setReadTimeout(Integer interval) {
getProperties().put(ClientConfig.PROPERTY_READ_TIMEOUT, interval);
}
/**
* Set the connect timeout interval, in milliseconds.
*
* This method is the functional equivalent to setting the property
* {@link ClientConfig#PROPERTY_CONNECT_TIMEOUT} on the property bag
* returned from {@link #getProperties}
*
* @param interval the connect timeout interval. If null or 0 then
* an interval of infinity is declared.
*/
public void setConnectTimeout(Integer interval) {
getProperties().put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, interval);
}
/**
* Set the client to send request entities using chunked encoding
* with a particular chunk size.
*
* This method is the functional equivalent to setting the property
* {@link ClientConfig#PROPERTY_CHUNKED_ENCODING_SIZE} on the property bag
* returned from {@link #getProperties}
*
* @param chunkSize the chunked encoding size. If <= 0 then the default
* size will be used. If null then chunked encoding will not be
* utilized.
*/
public void setChunkedEncodingSize(Integer chunkSize) {
getProperties().put(ClientConfig.PROPERTY_CHUNKED_ENCODING_SIZE, chunkSize);
}
// ClientHandler
@Override
public ClientResponse handle(final ClientRequest request) throws ClientHandlerException {
request.getProperties().putAll(properties);
request.getProperties().put(Client.class.getName(), this);
final ClientResponse response = getHeadHandler().handle(request);
response.getProperties().put(Client.class.getName(), this);
return response;
}
/**
* Inject client-side bindings on an instance.
*
* @param o the instance to inject on.
*/
public void inject(Object o) {
componentProviderFactory.injectOnProviderInstance(o);
}
/**
* Create a default client.
*
* @return a default client.
*/
public static Client create() {
return new Client(createDefaultClientHander());
}
/**
* Create a default client with client configuration.
*
* @param cc the client configuration.
* @return a default client.
*/
public static Client create(ClientConfig cc) {
return new Client(createDefaultClientHander(), cc);
}
/**
* Create a default client with client configuration and component provider.
*
* @param cc the client configuration.
* @param provider the IoC component provider factory.
* @return a default client.
*/
public static Client create(ClientConfig cc, IoCComponentProviderFactory provider) {
return new Client(createDefaultClientHander(), cc, provider);
}
/**
* Create a default client handler.
*
* This implementation returns a client handler implementation that
* utilizes {@link HttpURLConnection}.
*
* @return a default client handler.
*/
private static ClientHandler createDefaultClientHander() {
return new URLConnectionClientHandler();
}
}