diff --git a/jersey/jersey-client/pom.xml b/jersey/jersey-client/pom.xml index 3797a6d..9382820 100644 --- a/jersey/jersey-client/pom.xml +++ b/jersey/jersey-client/pom.xml @@ -54,6 +54,11 @@ ${project.version} + commons-httpclient + commons-httpclient + 3.1 + + junit junit 3.8.1 diff --git a/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/DefaultCredentialsProvider.java b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/DefaultCredentialsProvider.java new file mode 100644 index 0000000..964e225 --- /dev/null +++ b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/DefaultCredentialsProvider.java @@ -0,0 +1,138 @@ +/* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2007 Sun Microsystems, Inc. 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 https://jersey.dev.java.net/CDDL+GPL.html + * or jersey/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 jersey/legal/LICENSE.txt. + * Sun designates this particular file as subject to the "Classpath" exception + * as provided by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the License + * Header, with the fields enclosed by brackets [] replaced by your own + * identifying information: "Portions Copyrighted [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.impl.client.httpclient; + +import java.io.IOException; + +import javax.swing.JOptionPane; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.NTCredentials; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.CredentialsProvider; +import org.apache.commons.httpclient.auth.AuthScheme; +import org.apache.commons.httpclient.auth.CredentialsNotAvailableException; +import org.apache.commons.httpclient.auth.NTLMScheme; +import org.apache.commons.httpclient.auth.RFC2617Scheme; + + +/** + A very simple credential provider. + + @author jorgew + **/ + + +public class DefaultCredentialsProvider implements CredentialsProvider +{ + public Credentials getCredentials (AuthScheme scheme, + String host, + int port, + boolean proxy) + throws CredentialsNotAvailableException + { + if (scheme == null) { + return null; + } + + try { + JTextField userField = new JTextField(); + JPasswordField passwordField = new JPasswordField(); + int response; + + if (scheme instanceof NTLMScheme) { + JTextField domainField = new JTextField(); + Object[] msg = { + host+":"+port+" requires Windows authentication", + "Domain", + domainField, + "User Name", + userField, + "Password", + passwordField + }; + response = JOptionPane.showConfirmDialog (null, msg, "Authenticate", + JOptionPane.OK_CANCEL_OPTION); + + if ((response == JOptionPane.CANCEL_OPTION) || + (response == JOptionPane.CLOSED_OPTION)) + { + throw new CredentialsNotAvailableException ("User cancled windows authentication."); + } + + return new NTCredentials (userField.getText(), new String (passwordField.getPassword()), + host, domainField.getText()); + + + } else if (scheme instanceof RFC2617Scheme) { + Object[] msg = { + host+":"+port+" requires authentication with the realm '"+ + scheme.getRealm()+"'", + "User Name", + userField, + "Password", + passwordField + }; + + response = JOptionPane.showConfirmDialog (null, msg, "Authenticate", + JOptionPane.OK_CANCEL_OPTION); + + if ((response == JOptionPane.CANCEL_OPTION) || + (response == JOptionPane.CLOSED_OPTION)) + { + throw new CredentialsNotAvailableException ("User cancled windows authentication."); + } + + + return new UsernamePasswordCredentials (userField.getText(), + new String(passwordField.getPassword())); + + } else { + + throw new CredentialsNotAvailableException ("Unsupported authentication scheme: " + + scheme.getSchemeName()); + + } + }catch (IOException ioe) { + + throw new CredentialsNotAvailableException (ioe.getMessage(), ioe); + + } + } +} diff --git a/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/DefaultHttpClientConfig.java b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/DefaultHttpClientConfig.java new file mode 100644 index 0000000..669a15b --- /dev/null +++ b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/DefaultHttpClientConfig.java @@ -0,0 +1,162 @@ +/* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2007 Sun Microsystems, Inc. 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 https://jersey.dev.java.net/CDDL+GPL.html + * or jersey/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 jersey/legal/LICENSE.txt. + * Sun designates this particular file as subject to the "Classpath" exception + * as provided by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the License + * Header, with the fields enclosed by brackets [] replaced by your own + * identifying information: "Portions Copyrighted [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.impl.client.httpclient; + +import java.util.Map; + +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.NTCredentials; +import org.apache.commons.httpclient.auth.AuthScope; + +import com.sun.jersey.api.client.config.DefaultClientConfig; + +/** + A default client configuration for clients that root with the + {@link + com.sun.jersey.impl.client.httpclient.HttpClientHandler}. This + class may be extended for specific configuration purposes. + + @author jorgew +**/ + +public class DefaultHttpClientConfig extends DefaultClientConfig + implements HttpClientConfig +{ + /** + The HttpState of the current client. This is used to maintain + authentication credentials. + + The value MUST be an instance of {@link + org.apache.commons.httpclient.HttpState}. + + This shouldn't be used directly, instead use {@link + #setCredentials}, {@link #setProxyCredentials}, {@link + #clearCredentials}, or {@link #clearProxyCredentials}. + **/ + protected static final String PROPERTY_HTTP_STATE = + "com.sun.jersey.impl.client.httpclient.httpState"; + + private HttpState getHttpState() + { + Map props = getProperties(); + HttpState state = (HttpState) props.get(PROPERTY_HTTP_STATE); + + if (state == null) { + state = new HttpState(); + props.put (PROPERTY_HTTP_STATE, state); + } + + return state; + } + + public void setCredentials(String realm, String host, int port, + String usernamepassword) + { + AuthScope authScope = new AuthScope (host, port, realm); + UsernamePasswordCredentials creds = new UsernamePasswordCredentials (usernamepassword); + HttpState state = getHttpState(); + + state.setCredentials (authScope, creds); + } + + public void setCredentials(String realm, String host, int port, + String username, String password) + { + AuthScope authScope = new AuthScope (host, port, realm); + UsernamePasswordCredentials creds = new UsernamePasswordCredentials (username,password); + HttpState state = getHttpState(); + + state.setCredentials (authScope, creds); + } + + public void setCredentials(String realm, String host, int port, + String username, String password, + String domain, String thisHost) + { + AuthScope authScope = new AuthScope (host, port, realm); + NTCredentials creds = new NTCredentials (username,password, thisHost, domain); + HttpState state = getHttpState(); + + state.setCredentials (authScope, creds); + } + + public void setProxyCredentials(String realm, String host, int port, + String usernamepassword) + { + AuthScope authScope = new AuthScope (host, port, realm); + UsernamePasswordCredentials creds = new UsernamePasswordCredentials (usernamepassword); + HttpState state = getHttpState(); + + state.setProxyCredentials (authScope, creds); + } + + public void setProxyCredentials(String realm, String host, int port, + String username, String password) + { + AuthScope authScope = new AuthScope (host, port, realm); + UsernamePasswordCredentials creds = new UsernamePasswordCredentials (username,password); + HttpState state = getHttpState(); + + state.setProxyCredentials (authScope, creds); + } + + public void setProxyCredentials(String realm, String host, int port, + String username, String password, + String domain, String thisHost) + { + AuthScope authScope = new AuthScope (host, port, realm); + NTCredentials creds = new NTCredentials (username,password, thisHost, domain); + HttpState state = getHttpState(); + + state.setProxyCredentials (authScope, creds); + } + + public void clearCredentials() + { + HttpState state = getHttpState(); + state.clearCredentials(); + } + + public void clearProxyCredentials() + { + HttpState state = getHttpState(); + state.clearProxyCredentials(); + } +} + diff --git a/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/HttpClientConfig.java b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/HttpClientConfig.java new file mode 100644 index 0000000..a378b81 --- /dev/null +++ b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/HttpClientConfig.java @@ -0,0 +1,292 @@ +/* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2007 Sun Microsystems, Inc. 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 https://jersey.dev.java.net/CDDL+GPL.html + * or jersey/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 jersey/legal/LICENSE.txt. + * Sun designates this particular file as subject to the "Classpath" exception + * as provided by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the License + * Header, with the fields enclosed by brackets [] replaced by your own + * identifying information: "Portions Copyrighted [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.impl.client.httpclient; + +import com.sun.jersey.api.client.config.ClientConfig; + +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.Credentials; + +/** + Contains configuration options specific to clients that root with + the {@link com.sun.jersey.impl.client.httpclient.HttpClientHandler}. + + @author jorgew +**/ + +public interface HttpClientConfig extends ClientConfig +{ + /** + A value of "true" indicates that the client should + interactively prompt for credentials should it receive a 401 + response. + + The value MUST be an instance of {@link java.lang.Boolean}. + If the property is absent the default value is "true" + **/ + public static final String PROPERTY_INTERACTIVE = + "com.sun.jersey.impl.client.httpclient.interactive"; + + /** + A value of "true" indicates the client should handle cookies + automatically using HttpClient's default cookie policy. A value + of "false" will cause the client to ignore all cookies. + + The value MUST be an instance of {@link java.lang.Boolean}. + If the property is absent the default value is "false" + **/ + public static final String PROPERTY_HANDLE_COOKIES = + "com.sun.jersey.impl.client.httpclient.handleCookies"; + + /** + The credential provider that should be used to retrive + credentials from a user. The provider will be used only if + PROPERTY_INTERACTIVE is set to true. + + The value MUST be an instance of {@link + org.apache.commons.httpclient.auth.CredentialsProvider}. If + the property is absent a default provider will be used. + **/ + public static final String PROPERTY_CREDENTIALS_PROVIDER = + "com.sun.jersey.impl.client.httpclient.credentialsProvider"; + + /** + A value of "true" indicates that a client should send an + authentication request even before the server gives a 401 + response. + + If the value of this property is set to "true" default + credientials must be set for the target or proxy. + + The value MUST be an instance of {@link java.lang.Boolean}. + If the property is absent the default value is "false" + **/ + public static final String PROPERTY_PREEMPTIVE_AUTHENTICATION = + "com.sun.jersey.impl.client.httpclient.preemptiveAuthentication"; + + /** + A value of "true" indicates that a client should use a proxy + when connecting to a host. {@link #PROPERTY_PROXY_HOST} and + {@link #PROPERTY_PROXY_PORT} MUST be set. + + The value MUST be an instance of {@link java.lang.Boolean}. If + the property is absent the default value is "false". + **/ + public static final String PROPERTY_PROXY_SET = + "com.sun.jersey.impl.client.httpclient.proxySet"; + + /** + A value indicating the proxy host to use. The proxy host will + only be set if {@link #PROPERTY_PROXY_SET} is set to "true". + + The value MUST be an instance of {@link java.lang.String}. If + the property is absent the default value is "localhost". + **/ + public static final String PROPERTY_PROXY_HOST = + "com.sun.jersey.impl.client.httpclient.proxyHost"; + + /** + A value indicating the proxy port to use. The proxy port will + only be set if {@link #PROPERTY_PROXY_SET} is set to "true". + + The value MUST be an instance of {@link java.lang.Integer}. If + the property is absent the default value is 8080. + **/ + public static final String PROPERTY_PROXY_PORT = + "com.sun.jersey.impl.client.httpclient.proxyPort"; + + /** + Sets the credentials for the given authentication scope. Any + previous credentials for the given scope will be overwritten. + + @param realm The authentication realm. The null realm + signifies default credentials for the given host, which should + be used when no credentials have been explicitly supplied for + the challenging realm. + @param host The host the realm belongs to. The null host + signifies default credentials which should be used when no + credentials have been explicitly supplied for the challenging + host. + @param port The port the realm belongs to. A negitive port + signifies the credentials are applicaple to any port when no + credentials have been explicitly supplied for the challenging + port. + @param usernamepassword The username:password formed string. + **/ + public void setCredentials(String realm, String host, int port, + String usernamepassword); + + /** + Sets the credentials for the given authentication scope. Any + previous credentials for the given scope will be overwritten. + + @param realm The authentication realm. The null realm + signifies default credentials for the given host, which should + be used when no credentials have been explicitly supplied for + the challenging realm. + @param host The host the realm belongs to. The null host + signifies default credentials which should be used when no + credentials have been explicitly supplied for the challenging + host. + @param port The port the realm belongs to. A negitive port + signifies the credentials are applicaple to any port when no + credentials have been explicitly supplied for the challenging + port. + @param username The username + @param password The password + **/ + public void setCredentials(String realm, String host, int port, + String username, String password); + + /** + Sets the credentials for the given authentication scope. Any + previous credentials for the given scope will be overwritten. + + This method should be used when setting credentials for the + NTLM authentication scheme. + + @param realm The authentication realm. The null realm + signifies default credentials for the given host, which should + be used when no credentials have been explicitly supplied for + the challenging realm. + @param host The host the realm belongs to. The null host + signifies default credentials which should be used when no + credentials have been explicitly supplied for the challenging + host. + @param port The port the realm belongs to. A negitive port + signifies the credentials are applicaple to any port when no + credentials have been explicitly supplied for the challenging + port. + @param username The username, this should not include the + domain to authenticate with. For example: "user" is correct + wheareas "DOMAIN\\user" is not. + @param password The password + @param thisHost The host the authentication requiest is originating + from. Essentially, the computer name for this machine. + @param domain The domain to authentice with. + **/ + public void setCredentials(String realm, String host, int port, + String username, String password, + String thisHost, String domain); + + /** + Sets the proxy credentials for the given authentication scope. + Any previous credentials for the given scope will be + overwritten. + + @param realm The authentication realm. The null realm + signifies default credentials for the given host, which should + be used when no credentials have been explicitly supplied for + the challenging realm. + @param host The host the realm belongs to. The null host + signifies default credentials which should be used when no + credentials have been explicitly supplied for the challenging + host. + @param port The port the realm belongs to. A negitive port + signifies the credentials are applicaple to any port when no + credentials have been explicitly supplied for the challenging + port. + @param usernamepassword The username:password formed string. + **/ + public void setProxyCredentials(String realm, String host, int port, + String usernamepassword); + + /** + Sets the proxy credentials for the given authentication scope. + Any previous credentials for the given scope will be + overwritten. + + @param realm The authentication realm. The null realm + signifies default credentials for the given host, which should + be used when no credentials have been explicitly supplied for + the challenging realm. + @param host The host the realm belongs to. The null host + signifies default credentials which should be used when no + credentials have been explicitly supplied for the challenging + host. + @param port The port the realm belongs to. A negitive port + signifies the credentials are applicaple to any port when no + credentials have been explicitly supplied for the challenging + port. + @param username The username + @param password The password + **/ + public void setProxyCredentials(String realm, String host, int port, + String username, String password); + + /** + Sets the proxy credentials for the given authentication scope. + Any previous credentials for the given scope will be + overwritten. + + This method should be used when setting credentials for the + NTLM authentication scheme. + + @param realm The authentication realm. The null realm + signifies default credentials for the given host, which should + be used when no credentials have been explicitly supplied for + the challenging realm. + @param host The host the realm belongs to. The null host + signifies default credentials which should be used when no + credentials have been explicitly supplied for the challenging + host. + @param port The port the realm belongs to. A negitive port + signifies the credentials are applicaple to any port when no + credentials have been explicitly supplied for the challenging + port. + @param username The username, this should not include the + domain to authenticate with. For example: "user" is correct + wheareas "DOMAIN\\user" is not. + @param password The password + @param thisHost The host the authentication requiest is originating + from. Essentially, the computer name for this machine. + @param domain The domain to authentice with. + **/ + public void setProxyCredentials(String realm, String host, int port, + String username, String password, + String thisHost, String domain); + + /** + Clears all credentials. + **/ + public void clearCredentials(); + + /** + Clears all proxy credentials. + **/ + public void clearProxyCredentials(); +} diff --git a/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/HttpClientHandler.java b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/HttpClientHandler.java new file mode 100644 index 0000000..d27b12a --- /dev/null +++ b/jersey/jersey-client/src/main/java/com/sun/jersey/impl/client/httpclient/HttpClientHandler.java @@ -0,0 +1,478 @@ +/* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2007 Sun Microsystems, Inc. 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 https://jersey.dev.java.net/CDDL+GPL.html + * or jersey/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 jersey/legal/LICENSE.txt. + * Sun designates this particular file as subject to the "Classpath" exception + * as provided by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the License + * Header, with the fields enclosed by brackets [] replaced by your own + * identifying information: "Portions Copyrighted [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.impl.client.httpclient; + +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.RuntimeDelegate; +import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate; + +import com.sun.jersey.api.InBoundHeaders; +import com.sun.jersey.api.client.ClientHandler; +import com.sun.jersey.api.client.ClientHandlerException; +import com.sun.jersey.api.client.ClientRequest; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.GenericType; + +import com.sun.jersey.spi.MessageBodyWorkers; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.ProxyHost; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.httpclient.params.HttpConnectionManagerParams; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.OptionsMethod; +import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.methods.EntityEnclosingMethod; +import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.auth.CredentialsProvider; + + +/** + A root handler with Jakarta Commons HttpClient acting as a backend. +

+ Please Note: +

+

+ + @author jorgew +**/ + +public final class HttpClientHandler implements ClientHandler +{ + private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0]; + private static final DefaultCredentialsProvider DEFAULT_CREDENTIALS_PROVIDER = new DefaultCredentialsProvider(); + + @Context private MessageBodyWorkers bodyContext; + private HttpClient client = new HttpClient(new MultiThreadedHttpConnectionManager()); + + public ClientResponse handle (ClientRequest cr) + throws ClientHandlerException { + HttpMethod method = null; + try { + + Map props = cr.getProperties(); + HttpClientParams clientParams = client.getParams(); + HttpConnectionManagerParams params = client.getHttpConnectionManager().getParams(); + + Boolean handleCookies = (Boolean) props.get(HttpClientConfig.PROPERTY_HANDLE_COOKIES); + if ((handleCookies == null) || (!handleCookies)) { + clientParams.setCookiePolicy (CookiePolicy.IGNORE_COOKIES); + } + Boolean proxySet = (Boolean) props.get(HttpClientConfig.PROPERTY_PROXY_SET); + if ((proxySet != null) && (proxySet)) { + HostConfiguration hostConfig = client.getHostConfiguration(); + String proxyHost = (String) props.get(HttpClientConfig.PROPERTY_PROXY_HOST); + if (proxyHost == null) { + proxyHost = "localhost"; + } + + Integer proxyPort = (Integer) props.get(HttpClientConfig.PROPERTY_PROXY_PORT); + if (proxyPort == null) { + proxyPort = 8080; + } + + String setHost = hostConfig.getProxyHost(); + int setPort = hostConfig.getProxyPort(); + + if ((setHost == null) || + (!setHost.equals (proxyHost)) || + (setPort == -1) || + (setPort != proxyPort)) { + hostConfig.setProxyHost (new ProxyHost (proxyHost, proxyPort)); + } + + } else { + client.setHostConfiguration (HostConfiguration.ANY_HOST_CONFIGURATION); + } + + HttpState httpState = (HttpState) props.get(DefaultHttpClientConfig.PROPERTY_HTTP_STATE); + if (httpState != null) { + client.setState (httpState); + } + + Boolean preemptiveAuth = (Boolean) props.get(HttpClientConfig.PROPERTY_PREEMPTIVE_AUTHENTICATION); + if (preemptiveAuth != null) { + clientParams.setAuthenticationPreemptive (preemptiveAuth); + } else { + clientParams.setAuthenticationPreemptive (false); + } + + Boolean interactiveAuth = (Boolean) props.get(HttpClientConfig.PROPERTY_INTERACTIVE); + if ((interactiveAuth == null) || (interactiveAuth)) { + CredentialsProvider provider = (CredentialsProvider) props.get(HttpClientConfig.PROPERTY_CREDENTIALS_PROVIDER); + if (provider == null) { + provider = DEFAULT_CREDENTIALS_PROVIDER; + } + clientParams.setParameter(CredentialsProvider.PROVIDER, provider); + } else { + clientParams.setParameter(CredentialsProvider.PROVIDER, null); + } + + Integer readTimeout = (Integer) props.get(HttpClientConfig.PROPERTY_READ_TIMEOUT); + if (readTimeout != null) { + params.setSoTimeout (readTimeout); + } + + Integer connectTimeout = (Integer) props.get(HttpClientConfig.PROPERTY_CONNECT_TIMEOUT); + if (connectTimeout != null) { + params.setConnectionTimeout (connectTimeout); + } + + String strMethod = cr.getMethod(); + String uri = cr.getURI().toString(); + + if (strMethod.equals ("GET")) { + method = new GetMethod (uri); + } else if (strMethod.equals ("POST")) { + method = new PostMethod (uri); + } else if (strMethod.equals ("PUT")) { + method = new PutMethod (uri); + } else if (strMethod.equals ("DELETE")) { + method = new DeleteMethod (uri); + } else if (strMethod.equals ("HEAD")) { + method = new HeadMethod (uri); + } else if (strMethod.equals ("OPTIONS")) { + method = new OptionsMethod(uri); + } else { + throw new ClientHandlerException ("Method "+method+" is not supported."); + } + + method.setDoAuthentication (true); + + writeHeaders (cr.getMetadata(), method); + + if (method instanceof EntityEnclosingMethod) { + EntityEnclosingMethod entMethod = (EntityEnclosingMethod) method; + Integer chunkedEncodingSize = (Integer) props.get(HttpClientConfig.PROPERTY_CHUNKED_ENCODING_SIZE); + if (chunkedEncodingSize != null) { + // + // There doesn't seems to be a way to set the + // chunk size. + // + entMethod.setContentChunked (true); + } else { + entMethod.setContentChunked (false); + } + + Object entity = cr.getEntity(); + if (entity != null) { + writeEntity (entMethod, cr, entity); + } + } else { + Boolean followRedirects = (Boolean) props.get(HttpClientConfig.PROPERTY_FOLLOW_REDIRECTS); + if (followRedirects != null){ + method.setFollowRedirects (followRedirects); + } else { + method.setFollowRedirects (true); + } + } + + client.executeMethod (method); + + return new HttpClientResponse (method); + + }catch (Exception e) { + if (method != null) + { + method.releaseConnection(); + } + throw new ClientHandlerException (e); + } + } + + private void writeEntity (EntityEnclosingMethod entMethod, ClientRequest cr, Object entity) + throws IOException { + + MultivaluedMap metadata = cr.getMetadata(); + MediaType mediaType = null; + final Object mediaTypeHeader = metadata.getFirst("Content-Type"); + if (mediaTypeHeader instanceof MediaType) { + mediaType = (MediaType)mediaTypeHeader; + } else { + if (mediaTypeHeader != null) { + mediaType = MediaType.valueOf(mediaTypeHeader.toString()); + } else { + mediaType = new MediaType("application", "octet-stream"); + } + } + + Type entityType = null; + if (entity instanceof GenericEntity) { + final GenericEntity ge = (GenericEntity)entity; + entityType = ge.getType(); + entity = ge.getEntity(); + } else { + entityType = entity.getClass(); + } + final Class entityClass = entity.getClass(); + + final MessageBodyWriter bw = bodyContext.getMessageBodyWriter(entityClass, entityType, + EMPTY_ANNOTATIONS, mediaType); + if (bw == null) { + throw new ClientHandlerException( + "A message body writer for Java type, " + entity.getClass() + + ", and MIME media type, " + mediaType + ", was not found"); + } + + final ByteArrayOutputStream bout = new ByteArrayOutputStream(); + final OutputStream out = cr.getAdapter().adapt (cr, bout); + bw.writeTo(entity, entityClass, entityType, + EMPTY_ANNOTATIONS, mediaType, metadata, out); + out.flush(); + out.close(); + + entMethod.setRequestEntity (new ByteArrayRequestEntity (bout.toByteArray(), mediaType.toString())); + } + + private void writeHeaders (MultivaluedMap metadata, HttpMethod method) + { + for (Map.Entry> e : metadata.entrySet()) { + List vs = e.getValue(); + if (vs.size() == 1) { + method.setRequestHeader (e.getKey(), getHeaderValue(vs.get(0))); + } else { + StringBuilder b = new StringBuilder(); + boolean add = false; + for (Object v : e.getValue()) { + if (add) b.append(','); + add = true; + b.append(getHeaderValue(v)); + } + method.setRequestHeader(e.getKey(), b.toString()); + } + } + } + + @SuppressWarnings("unchecked") + private String getHeaderValue(Object headerValue) { + HeaderDelegate hp = RuntimeDelegate.getInstance(). + createHeaderDelegate(headerValue.getClass()); + return hp.toString(headerValue); + } + + private final class HttpClientResponseInputStream extends FilterInputStream { + private final HttpMethod method; + + HttpClientResponseInputStream (HttpMethod method) + throws IOException + { + super(method.getResponseBodyAsStream()); + this.method = method; + } + + @Override + public void close() + throws IOException + { + super.close(); + method.releaseConnection(); + } + } + + private final class HttpClientResponse extends ClientResponse { + private final HttpMethod method; + private final MultivaluedMap metadata; + private Map properties; + private InputStream in; + private int status; + + HttpClientResponse(HttpMethod method) + { + this.method = method; + this.status = method.getStatusCode(); + + this.metadata = new InBoundHeaders(); + Header[] respHeaders = method.getResponseHeaders(); + for (Header header : respHeaders) { + List list = metadata.get(header.getName()); + if (list == null) { + list = new ArrayList(); + } + list.add (header.getValue()); + metadata.put (header.getName(), list); + } + + try { + in = new HttpClientResponseInputStream(method); + } catch (IOException ex) { + throw new IllegalArgumentException (ex); + } + } + + @Override + public int getStatus() { + return status; + } + + @Override + public void setStatus (int status) { + this.status = status; + } + + @Override + public Response.Status getResponseStatus() { + return Response.Status.fromStatusCode(status); + } + + @Override + public void setResponseStatus(Response.Status status) { + setStatus(status.getStatusCode()); + } + + @Override + public MultivaluedMap getMetadata() { + return metadata; + } + + @Override + public boolean hasEntity() { + if ((method instanceof HeadMethod) || + (in == null)) { + return false; + } + + Header contentLength = method.getResponseHeader("Content-Length"); + if (contentLength != null){ + try { + int len = Integer.parseInt(contentLength.getValue()); + return len > 0 || len == -1; + }catch (NumberFormatException nfe) { + } + } + + return false; + } + + @Override + public InputStream getEntityInputStream() { + return in; + } + + @Override + public void setEntityInputStream(InputStream in) { + this.in = in; + } + + @Override + public T getEntity(Class c) { + return getEntity(c, c); + } + + @Override + public T getEntity(GenericType gt) { + return getEntity(gt.getRawClass(), gt.getType()); + } + + private T getEntity(Class c, Type type) { + try { + MediaType mediaType = getType(); + final MessageBodyReader br = bodyContext.getMessageBodyReader( + c, type, + EMPTY_ANNOTATIONS, mediaType); + if (br == null) { + throw new ClientHandlerException( + "A message body reader for Java type, " + c + + ", and MIME media type, " + mediaType + ", was not found"); + } + T t = br.readFrom(c, type, EMPTY_ANNOTATIONS, mediaType, metadata, in); + if (!(t instanceof InputStream)) { + in.close(); + } + in = null; + return t; + } catch (IOException ex) { + throw new IllegalArgumentException(ex); + } + } + + @Override + public Map getProperties() { + if (properties != null) return properties; + + return properties = new HashMap(); + } + } +}