content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.apache.chemistry.opencmis.client.bindings.spi.http;
  
  import java.io.IOException;
  import java.net.InetAddress;
  import java.net.InetSocketAddress;
  import java.net.Socket;
  import java.net.SocketTimeoutException;
  import java.util.Collections;
  import java.util.List;
  
  import javax.net.ssl.HostnameVerifier;
  import javax.net.ssl.SSLSocket;
  import javax.net.ssl.SSLSocketFactory;
  
  import org.apache.chemistry.opencmis.client.bindings.impl.CmisBindingsHelper;
  import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
  import org.apache.chemistry.opencmis.commons.spi.AuthenticationProvider;
  import org.apache.http.Header;
  import org.apache.http.client.params.ClientPNames;
  import org.apache.http.conn.ConnectTimeoutException;
  import org.apache.http.conn.params.ConnManagerPNames;
  import org.apache.http.conn.params.ConnPerRouteBean;
  import org.apache.http.conn.scheme.LayeredSocketFactory;
  import org.apache.http.conn.scheme.PlainSocketFactory;
  import org.apache.http.conn.scheme.Scheme;
  import org.apache.http.conn.scheme.SchemeRegistry;
  import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
  import org.apache.http.cookie.Cookie;
  import org.apache.http.cookie.CookieOrigin;
  import org.apache.http.cookie.CookieSpec;
  import org.apache.http.cookie.CookieSpecFactory;
  import org.apache.http.cookie.MalformedCookieException;
  import org.apache.http.impl.client.DefaultHttpClient;
  import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
  import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
  import org.apache.http.impl.cookie.CookieSpecBase;
  import org.apache.http.params.HttpConnectionParams;
  import org.apache.http.params.HttpParams;
  
  /**
   * A {@link HttpInvoker} that uses The Apache HTTP client.
   */
  public class ApacheClientHttpInvoker extends AbstractApacheClientHttpInvoker {
  
      private static final String IGNORE_COOKIES = "ignoreCookies";
  
      @Override
      protected DefaultHttpClient createHttpClient(UrlBuilder url, BindingSession session) {
          // set params
          HttpParams params = createDefaultHttpParams(session);
          params.setParameter(ClientPNames.COOKIE_POLICY, IGNORE_COOKIES);
  
          // set max connection
          String maxConnStr = System.getProperty("http.maxConnections", "5");
          int maxConn = 5;
          try {
              maxConn = Integer.parseInt(maxConnStr);
          } catch (NumberFormatException nfe) {
              // ignore
          }
          params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, maxConn * 4);
          params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(maxConn));
  
          // set up scheme registry
          SchemeRegistry registry = new SchemeRegistry();
          registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
          registry.register(new Scheme("https", getSSLSocketFactory(url, session), 443));
  
          // set up connection manager
          ThreadSafeClientConnManager connManager = new ThreadSafeClientConnManager(params, registry);
  
          // set up proxy
          ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(registry, null);
  
          // set up client
          DefaultHttpClient httpclient = new DefaultHttpClient(connManager, params);
          httpclient.setRoutePlanner(routePlanner);
          httpclient.getCookieSpecs().register(IGNORE_COOKIES, new IgnoreSpecFactory());
  
          return httpclient;
      }
  
      /**
       * Builds a SSL Socket Factory for the Apache HTTP Client.
       */
      private LayeredSocketFactory getSSLSocketFactory(final UrlBuilder url, final BindingSession session) {
          // get authentication provider
          AuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
  
          // check SSL Socket Factory
          final SSLSocketFactory sf = authProvider.getSSLSocketFactory();
          if (sf == null) {
              // no custom factory -> return default factory
              return org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
          }
  
          // check hostame verifier and use default if not set
          final HostnameVerifier hv = (authProvider.getHostnameVerifier() == null ? new BrowserCompatHostnameVerifier()
                  : authProvider.getHostnameVerifier());
  
          // build new socket factory
          return new LayeredSocketFactory() {
  
              @Override
              public boolean isSecure(Socket sock) {
                  return true;
              }
  
              @Override
              public Socket createSocket() throws IOException {
                  return sf.createSocket();
              }
  
              @Override
              public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
                  SSLSocket sslSocket = (SSLSocket) sf.createSocket(socket, host, port, autoClose);
                  verify(hv, host, sslSocket);
  
                  return sslSocket;
              }
  
              @Override
              public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort,
                      HttpParams params) throws IOException {
                  SSLSocket sslSocket = (SSLSocket) (sock != null ? sock : createSocket());
  
                  if (localAddress != null || localPort > 0) {
                      if (localPort < 0) {
                          localPort = 0;
                      }
  
                      InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);
                      sslSocket.bind(isa);
                  }
  
                  InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
  
                  int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
                  int soTimeout = HttpConnectionParams.getSoTimeout(params);
  
                  try {
                      sslSocket.setSoTimeout(soTimeout);
                      sslSocket.connect(remoteAddress, connTimeout);
                  } catch (SocketTimeoutException ex) {
                      closeSocket(sock);
                      throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out!");
                  }
  
                  verify(hv, host, sslSocket);
  
                  return sslSocket;
              }
          };
      }
  
      /**
       * A cookies spec factory that ignores cookies.
       */
      private static class IgnoreSpecFactory implements CookieSpecFactory {
          @Override
          public CookieSpec newInstance(final HttpParams params) {
              return new CookieSpecBase() {
                  @Override
                  public int getVersion() {
                      return 0;
                  }
  
                  @Override
                  public List<Cookie> parse(Header header, CookieOrigin origin) throws MalformedCookieException {
                      return Collections.emptyList();
                  }
  
                  @Override
                  public List<Header> formatCookies(List<Cookie> cookies) {
                      return Collections.emptyList();
                  }
  
                  @Override
                  public Header getVersionHeader() {
                      return null;
                  }
              };
          }
      }
  }
  