com.helger.pd.client.jdk6.PDClient.java Source code

Java tutorial

Introduction

Here is the source code for com.helger.pd.client.jdk6.PDClient.java

Source

/**
 * Copyright (C) 2015-2016 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.pd.client.jdk6;

import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.CodingErrorAction;
import java.security.KeyStore;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.PrivateKeyDetails;
import org.apache.http.ssl.PrivateKeyStrategy;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.OverrideOnDemand;
import com.helger.commons.charset.CCharset;
import com.helger.commons.exception.InitializationException;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.state.ESuccess;
import com.helger.peppol.identifier.IParticipantIdentifier;
import com.helger.peppol.identifier.IdentifierHelper;
import com.helger.peppol.utils.KeyStoreHelper;

/**
 * This class is used for calling the PD indexer REST interface.
 *
 * @author Philip Helger
 */
public class PDClient implements Closeable {
    private static final Logger s_aLogger = LoggerFactory.getLogger(PDClient.class);

    /**
     * The string representation of the PEPPOL Directory host URL, always ending
     * with a trailing slash!
     */
    private final String m_sPDHost;
    private final String m_sPDIndexerURL;

    private HttpHost m_aProxy;
    private Credentials m_aProxyCredentials;
    private CloseableHttpClient m_aHttpClient;

    /**
     * Constructor with a direct PEPPOL Directory URL.
     *
     * @param aPDHost
     *        The address of the PEPPOL Directory Server including the application
     *        server context path but without the REST interface. May be http or
     *        https. Example: http://pyp.helger.com/
     */
    public PDClient(@Nonnull final URI aPDHost) {
        ValueEnforcer.notNull(aPDHost, "PDHost");

        // Build string and ensure it ends with a "/"
        final String sSMPHost = aPDHost.toString();
        m_sPDHost = sSMPHost.endsWith("/") ? sSMPHost : sSMPHost + '/';
        m_sPDIndexerURL = m_sPDHost + "indexer/1.0/";
    }

    /**
     * @return The PEPPOL Directory host URI string we're operating on. Never
     *         <code>null</code> . Always has a trailing "/".
     */
    @Nonnull
    public String getPDHostURI() {
        return m_sPDHost;
    }

    /**
     * @return The HTTP proxy to be used to access the PEPPOL Directory server. Is
     *         <code>null</code> by default.
     */
    @Nullable
    public HttpHost getProxy() {
        return m_aProxy;
    }

    /**
     * Set the proxy to be used to access the PEPPOL Directory server.
     *
     * @param aProxy
     *        May be <code>null</code> to indicate no proxy.
     */
    public void setProxy(@Nullable final HttpHost aProxy) {
        m_aProxy = aProxy;
    }

    /**
     * @return The HTTP proxy credentials to be used to access the PEPPOL
     *         Directory server. Is <code>null</code> by default.
     */
    @Nullable
    public Credentials getProxyCredentials() {
        return m_aProxyCredentials;
    }

    /**
     * Set the proxy Credentials to be used to access the PEPPOL Directory server.
     *
     * @param aProxyCredentials
     *        May be <code>null</code> to indicate no proxy credentials necessary.
     */
    public void setProxyCredentials(@Nullable final Credentials aProxyCredentials) {
        m_aProxyCredentials = aProxyCredentials;
    }

    @Nonnull
    protected HttpClientBuilder createClientBuilder() {
        SSLConnectionSocketFactory aSSLSocketFactory = null;
        try {
            // Set SSL context
            final KeyStore aKeyStore = KeyStoreHelper.loadKeyStore(PDClientConfiguration.getKeyStorePath(),
                    PDClientConfiguration.getKeyStorePassword());
            final SSLContext aSSLContext = SSLContexts.custom().loadKeyMaterial(aKeyStore,
                    PDClientConfiguration.getKeyStoreKeyPassword(), new PrivateKeyStrategy() {
                        public String chooseAlias(final Map<String, PrivateKeyDetails> aAliases,
                                final Socket aSocket) {
                            final String sAlias = PDClientConfiguration.getKeyStoreKeyAlias();
                            return aAliases.containsKey(sAlias) ? sAlias : null;
                        }
                    }).build();
            // Allow TLSv1 protocol only
            aSSLSocketFactory = new SSLConnectionSocketFactory(aSSLContext, new String[] { "TLSv1" }, null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        } catch (final Throwable t) {
            s_aLogger.error("Failed to initialize keystore for service connection! Can only use http now!", t);
        }

        try {
            final RegistryBuilder<ConnectionSocketFactory> aRB = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory());
            if (aSSLSocketFactory != null)
                aRB.register("https", aSSLSocketFactory);
            final Registry<ConnectionSocketFactory> sfr = aRB.build();

            final PoolingHttpClientConnectionManager aConnMgr = new PoolingHttpClientConnectionManager(sfr);
            aConnMgr.setDefaultMaxPerRoute(100);
            aConnMgr.setMaxTotal(200);
            aConnMgr.setValidateAfterInactivity(1000);
            final ConnectionConfig aConnectionConfig = ConnectionConfig.custom()
                    .setMalformedInputAction(CodingErrorAction.IGNORE)
                    .setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Consts.UTF_8).build();
            aConnMgr.setDefaultConnectionConfig(aConnectionConfig);

            return HttpClientBuilder.create().setConnectionManager(aConnMgr);
        } catch (final Exception ex) {
            throw new InitializationException("Failed to init HTTP client", ex);
        }
    }

    @Nonnull
    @OverrideOnDemand
    protected RequestConfig createRequestConfig() {
        return RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(5000)
                .setConnectionRequestTimeout(5000).setProxy(m_aProxy).build();
    }

    /**
     * The main execution routine. Overwrite this method to add additional
     * properties to the call.
     *
     * @param aRequest
     *        The request to be executed. Never <code>null</code>.
     * @return The HTTP execution response. Never <code>null</code>.
     * @throws IOException
     *         On HTTP error
     */
    @Nonnull
    @OverrideOnDemand
    protected CloseableHttpResponse executeRequest(@Nonnull final HttpRequestBase aRequest) throws IOException {
        aRequest.setConfig(createRequestConfig());

        // Contextual attributes set the local context level will take
        // precedence over those set at the client level.
        final HttpClientContext aContext = HttpClientContext.create();
        if (m_aProxy != null && m_aProxyCredentials != null) {
            final CredentialsProvider aCredentialsProvider = new BasicCredentialsProvider();
            aCredentialsProvider.setCredentials(new AuthScope(m_aProxy), m_aProxyCredentials);
            aContext.setCredentialsProvider(aCredentialsProvider);
        }

        if (m_aHttpClient == null)
            m_aHttpClient = createClientBuilder().build();

        if (s_aLogger.isDebugEnabled())
            s_aLogger.debug("Executing request " + aRequest.getRequestLine());

        return m_aHttpClient.execute(aRequest, aContext);
    }

    @Nullable
    private static String _getResponseString(@Nonnull final CloseableHttpResponse aResponse) throws IOException {
        final HttpEntity aResponseEntity = aResponse.getEntity();
        final String sResponse = aResponseEntity == null ? null
                : StreamHelper.getAllBytesAsString(aResponseEntity.getContent(), CCharset.CHARSET_UTF_8_OBJ);
        EntityUtils.consume(aResponseEntity);
        return sResponse;
    }

    /**
     * Gets a list of references to the CompleteServiceGroup's owned by the
     * specified userId. This is a non-specification compliant method.
     *
     * @param aParticipantID
     *        Participant ID to query for existence. May not be <code>null</code>.
     * @return <code>true</code> if the participant is in the index,
     *         <code>false</code> otherwise.
     */
    @Nonnull
    public boolean isServiceGroupRegistered(@Nonnull final IParticipantIdentifier aParticipantID) {
        ValueEnforcer.notNull(aParticipantID, "ParticipantID");

        final HttpGet aGet = new HttpGet(
                m_sPDIndexerURL + IdentifierHelper.getIdentifierURIPercentEncoded(aParticipantID));
        CloseableHttpResponse aResponse = null;
        try {
            aResponse = executeRequest(aGet);
            final String sResponse = _getResponseString(aResponse);

            // Check result
            if (aResponse.getStatusLine().getStatusCode() >= 200 && aResponse.getStatusLine().getStatusCode() < 300)
                return true;

            if (aResponse.getStatusLine().getStatusCode() == 404)
                return false;

            s_aLogger.warn("Unexpected status returned from server for " + aGet.getRequestLine() + ": "
                    + aResponse.getStatusLine() + "\n" + sResponse);
        } catch (final IOException ex) {
            s_aLogger.error("Error performing request " + aGet.getRequestLine(), ex);
        } finally {
            StreamHelper.close(aResponse);
        }
        return false;
    }

    @Nonnull
    public ESuccess addServiceGroupToIndex(@Nonnull final IParticipantIdentifier aParticipantID) {
        ValueEnforcer.notNull(aParticipantID, "ParticipantID");
        final String sParticipantID = IdentifierHelper.getIdentifierURIEncoded(aParticipantID);

        final HttpPut aPut = new HttpPut(m_sPDIndexerURL);
        aPut.setEntity(new StringEntity(sParticipantID, CCharset.CHARSET_UTF_8_OBJ));
        CloseableHttpResponse aResponse = null;
        try {
            aResponse = executeRequest(aPut);
            final String sResponse = _getResponseString(aResponse);

            // Check result
            if (aResponse.getStatusLine().getStatusCode() >= 200
                    && aResponse.getStatusLine().getStatusCode() < 300) {
                s_aLogger.info("Added service group '" + sParticipantID
                        + "' to PEPPOL Directory index. May take some time until it shows up.");
                return ESuccess.SUCCESS;
            }

            s_aLogger.warn("Unexpected status returned from server for " + aPut.getRequestLine() + ": "
                    + aResponse.getStatusLine() + "\n" + sResponse);
        } catch (final IOException ex) {
            s_aLogger.error("Error performing request " + aPut.getRequestLine(), ex);
        } finally {
            StreamHelper.close(aResponse);
        }
        return ESuccess.FAILURE;
    }

    @Nonnull
    public ESuccess deleteServiceGroupFromIndex(@Nonnull final IParticipantIdentifier aParticipantID) {
        ValueEnforcer.notNull(aParticipantID, "ParticipantID");

        final HttpDelete aDelete = new HttpDelete(
                m_sPDIndexerURL + IdentifierHelper.getIdentifierURIPercentEncoded(aParticipantID));
        CloseableHttpResponse aResponse = null;
        try {
            aResponse = executeRequest(aDelete);
            final String sResponse = _getResponseString(aResponse);

            // Check result
            if (aResponse.getStatusLine().getStatusCode() >= 200
                    && aResponse.getStatusLine().getStatusCode() < 300) {
                final String sParticipantID = IdentifierHelper.getIdentifierURIEncoded(aParticipantID);
                s_aLogger.info("Removed service group '" + sParticipantID
                        + "' from PEPPOL Directory index. May take some time until it is removed.");
                return ESuccess.SUCCESS;
            }

            s_aLogger.warn("Unexpected status returned from server for " + aDelete.getRequestLine() + ": "
                    + aResponse.getStatusLine() + "\n" + sResponse);
        } catch (final IOException ex) {
            s_aLogger.error("Error performing request " + aDelete.getRequestLine(), ex);
        } finally {
            StreamHelper.close(aResponse);
        }
        return ESuccess.FAILURE;
    }

    public void close() throws IOException {
        if (m_aHttpClient != null)
            m_aHttpClient.close();
    }

    @Nonnull
    public static PDClient createDefaultClient() {
        final PDClient aClient = new PDClient(URI.create("http://pyp.helger.com"));
        final boolean bIsHttp = aClient.getPDHostURI().startsWith("http:");

        // Get proxy settings
        final String sProxyHost = bIsHttp ? PDClientConfiguration.getHttpProxyHost()
                : PDClientConfiguration.getHttpsProxyHost();
        final int nProxyPort = bIsHttp ? PDClientConfiguration.getHttpProxyPort()
                : PDClientConfiguration.getHttpsProxyPort();
        if (sProxyHost != null && nProxyPort > 0)
            aClient.setProxy(new HttpHost(sProxyHost, nProxyPort));

        return aClient;
    }
}