com.helger.peppol.bdxrclient.BDXRClientReadOnly.java Source code

Java tutorial

Introduction

Here is the source code for com.helger.peppol.bdxrclient.BDXRClientReadOnly.java

Source

/**
 * Copyright (C) 2015-2016 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Version: MPL 1.1/EUPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Copyright The PEPPOL project (http://www.peppol.eu)
 *
 * Alternatively, the contents of this file may be used under the
 * terms of the EUPL, Version 1.1 or - as soon they will be approved
 * by the European Commission - subsequent versions of the EUPL
 * (the "Licence"); You may not use this work except in compliance
 * with the Licence.
 * You may obtain a copy of the Licence at:
 * http://joinup.ec.europa.eu/software/page/eupl/licence-eupl
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 *
 * If you wish to allow use of your version of this file only
 * under the terms of the EUPL License and not to allow others to use
 * your version of this file under the MPL, indicate your decision by
 * deleting the provisions above and replace them with the notice and
 * other provisions required by the EUPL License. If you do not delete
 * the provisions above, a recipient may use your version of this file
 * under either the MPL or the EUPL License.
 */
package com.helger.peppol.bdxrclient;

import java.io.IOException;
import java.net.URI;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.bind.JAXBElement;

import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.fluent.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.OverrideOnDemand;
import com.helger.commons.collection.CollectionHelper;
import com.helger.peppol.bdxr.EndpointType;
import com.helger.peppol.bdxr.ProcessType;
import com.helger.peppol.bdxr.RedirectType;
import com.helger.peppol.bdxr.ServiceGroupType;
import com.helger.peppol.bdxr.ServiceInformationType;
import com.helger.peppol.bdxr.SignedServiceMetadataType;
import com.helger.peppol.identifier.IDocumentTypeIdentifier;
import com.helger.peppol.identifier.IParticipantIdentifier;
import com.helger.peppol.identifier.IProcessIdentifier;
import com.helger.peppol.identifier.IdentifierHelper;
import com.helger.peppol.sml.ISMLInfo;
import com.helger.peppol.smp.ISMPTransportProfile;
import com.helger.peppol.smpclient.SMPClientConfiguration;
import com.helger.peppol.smpclient.SMPHttpResponseHandlerSigned;
import com.helger.peppol.smpclient.SMPHttpResponseHandlerUnsigned;
import com.helger.peppol.smpclient.exception.SMPClientBadRequestException;
import com.helger.peppol.smpclient.exception.SMPClientException;
import com.helger.peppol.smpclient.exception.SMPClientNotFoundException;
import com.helger.peppol.smpclient.exception.SMPClientUnauthorizedException;
import com.helger.peppol.utils.BusdoxURLHelper;
import com.helger.peppol.utils.CertificateHelper;
import com.helger.peppol.xmldsig.X509DataType;

/**
 * This class is used for calling the BDXR SMP REST interface. This class only
 * contains the read-only methods defined in the SMP specification and nothing
 * else.
 *
 * @author PEPPOL.AT, BRZ, Philip Helger
 */
public class BDXRClientReadOnly {
    private static final Logger s_aLogger = LoggerFactory.getLogger(BDXRClientReadOnly.class);

    /**
     * The string representation of the SMP host URL, always ending with a
     * trailing slash!
     */
    private final String m_sSMPHost;

    private HttpHost m_aProxy;

    /**
     * Constructor with SML lookup
     *
     * @param aParticipantIdentifier
     *        The participant identifier to be used. Required to build the SMP
     *        access URI.
     * @param aSMLInfo
     *        The SML to be used. Required to build the SMP access URI.
     * @see BusdoxURLHelper#getSMPURIOfParticipant(IParticipantIdentifier,
     *      ISMLInfo)
     */
    public BDXRClientReadOnly(@Nonnull final IParticipantIdentifier aParticipantIdentifier,
            @Nonnull final ISMLInfo aSMLInfo) {
        this(BusdoxURLHelper.getSMPURIOfParticipant(aParticipantIdentifier, aSMLInfo));
    }

    /**
     * Constructor with SML lookup
     *
     * @param aParticipantIdentifier
     *        The participant identifier to be used. Required to build the SMP
     *        access URI.
     * @param sSMLZoneName
     *        The SML DNS zone name to be used. Required to build the SMP access
     *        URI. Must end with a trailing dot (".") and may neither be
     *        <code>null</code> nor empty to build a correct URL. May not start
     *        with "http://". Example: <code>sml.peppolcentral.org.</code>
     * @see BusdoxURLHelper#getSMPURIOfParticipant(IParticipantIdentifier, String)
     */
    public BDXRClientReadOnly(@Nonnull final IParticipantIdentifier aParticipantIdentifier,
            @Nonnull @Nonempty final String sSMLZoneName) {
        this(BusdoxURLHelper.getSMPURIOfParticipant(aParticipantIdentifier, sSMLZoneName));
    }

    /**
     * Constructor with a direct SMP URL.<br>
     * Remember: must be HTTP and using port 80 only!
     *
     * @param aSMPHost
     *        The address of the SMP service. Must be port 80 and basic http only
     *        (no https!). Example: http://smpcompany.company.org
     */
    public BDXRClientReadOnly(@Nonnull final URI aSMPHost) {
        ValueEnforcer.notNull(aSMPHost, "SMPHost");

        if (!"http".equals(aSMPHost.getScheme()))
            s_aLogger.warn("SMP URI " + aSMPHost + " does not use the expected http scheme!");
        // getPort () returns -1 if none was explicitly specified
        if (aSMPHost.getPort() != 80 && aSMPHost.getPort() != -1)
            s_aLogger.warn("SMP URI " + aSMPHost + " is not running on port 80!");

        // Build string and ensure it ends with a "/"
        final String sSMPHost = aSMPHost.toString();
        m_sSMPHost = sSMPHost.endsWith("/") ? sSMPHost : sSMPHost + '/';

        // Set default proxy from configuration file
        m_aProxy = SMPClientConfiguration.getHttpProxy();
    }

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

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

    /**
     * Set the proxy to be used to access the SMP server. Note: proxy
     * authentication is currently not supported!
     *
     * @param aProxy
     *        May be <code>null</code> to indicate no proxy.
     * @return this for chaining
     */
    @Nonnull
    public BDXRClientReadOnly setProxy(@Nullable final HttpHost aProxy) {
        m_aProxy = aProxy;
        return this;
    }

    /**
     * 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 Response executeRequest(@Nonnull final Request aRequest) throws IOException {
        if (m_aProxy != null)
            aRequest.viaProxy(m_aProxy);
        return aRequest.connectTimeout(5000).socketTimeout(10000).execute();
    }

    /**
     * Convert the passed generic HTTP exception into a more specific exception.
     *
     * @param ex
     *        The generic exception. May not be <code>null</code>.
     * @return A new SMP specific exception, using the passed exception as the
     *         cause.
     */
    @Nonnull
    public static SMPClientException getConvertedException(@Nonnull final Exception ex) {
        if (ex instanceof SMPClientException)
            return (SMPClientException) ex;

        if (ex instanceof HttpResponseException) {
            final HttpResponseException hex = (HttpResponseException) ex;
            final int nHttpStatus = hex.getStatusCode();
            switch (nHttpStatus) {
            case HttpStatus.SC_BAD_REQUEST:
                return new SMPClientBadRequestException(hex);
            case HttpStatus.SC_FORBIDDEN:
                return new SMPClientUnauthorizedException(hex);
            case HttpStatus.SC_NOT_FOUND:
                return new SMPClientNotFoundException(hex);
            }
            return new SMPClientException("Error thrown with HTTP status code " + nHttpStatus, hex);
        }

        // Special case
        if (ex instanceof UnknownHostException)
            return new SMPClientNotFoundException((UnknownHostException) ex);

        return new SMPClientException("Unknown error thrown by SMP server (" + ex.getMessage() + ")", ex);
    }

    /**
     * Returns a service group. A service group references to the service
     * metadata. This is a specification compliant method.
     *
     * @param aServiceGroupID
     *        The service group id corresponding to the service group which one
     *        wants to get.
     * @return The service group. Never <code>null</code>.
     * @throws SMPClientException
     *         in case something goes wrong
     * @throws SMPClientUnauthorizedException
     *         A HTTP Forbidden was received, should not happen.
     * @throws SMPClientNotFoundException
     *         The service group id did not exist.
     * @throws SMPClientBadRequestException
     *         The request was not well formed.
     * @see #getServiceGroupOrNull(IParticipantIdentifier)
     */
    @Nonnull
    public ServiceGroupType getServiceGroup(@Nonnull final IParticipantIdentifier aServiceGroupID)
            throws SMPClientException {
        ValueEnforcer.notNull(aServiceGroupID, "ServiceGroupID");

        try {
            final Request aRequest = Request
                    .Get(m_sSMPHost + IdentifierHelper.getIdentifierURIPercentEncoded(aServiceGroupID));
            return executeRequest(aRequest)
                    .handleResponse(SMPHttpResponseHandlerUnsigned.create(new BDXRMarshallerServiceGroupType()));
        } catch (final Exception ex) {
            throw getConvertedException(ex);
        }
    }

    /**
     * Returns a service group. A service group references to the service
     * metadata. This is a specification compliant method.
     *
     * @param aServiceGroupID
     *        The service group id corresponding to the service group which one
     *        wants to get.
     * @return The service group. May be <code>null</code> if no such service
     *         group exists.
     * @throws SMPClientException
     *         in case something goes wrong
     * @throws SMPClientUnauthorizedException
     *         A HTTP Forbidden was received, should not happen.
     * @throws SMPClientBadRequestException
     *         The request was not well formed.
     * @see #getServiceGroup(IParticipantIdentifier)
     */
    @Nullable
    public ServiceGroupType getServiceGroupOrNull(@Nonnull final IParticipantIdentifier aServiceGroupID)
            throws SMPClientException {
        try {
            return getServiceGroup(aServiceGroupID);
        } catch (final SMPClientNotFoundException ex) {
            return null;
        }
    }

    /**
     * Gets a signed service metadata object given by its service group id and its
     * document type. This is a specification compliant method.
     *
     * @param aServiceGroupID
     *        The service group id of the service metadata to get. May not be
     *        <code>null</code>.
     * @param aDocumentTypeID
     *        The document type of the service metadata to get. May not be
     *        <code>null</code>.
     * @return A signed service metadata object. Never <code>null</code>.
     * @throws SMPClientException
     *         in case something goes wrong
     * @throws SMPClientUnauthorizedException
     *         A HTTP Forbidden was received, should not happen.
     * @throws SMPClientNotFoundException
     *         The service group id or document type did not exist.
     * @throws SMPClientBadRequestException
     *         The request was not well formed.
     * @see #getServiceRegistrationOrNull(IParticipantIdentifier,
     *      IDocumentTypeIdentifier)
     */
    @Nonnull
    public SignedServiceMetadataType getServiceRegistration(@Nonnull final IParticipantIdentifier aServiceGroupID,
            @Nonnull final IDocumentTypeIdentifier aDocumentTypeID) throws SMPClientException {
        ValueEnforcer.notNull(aServiceGroupID, "ServiceGroupID");
        ValueEnforcer.notNull(aDocumentTypeID, "DocumentTypeID");

        try {
            final String sURI = m_sSMPHost + IdentifierHelper.getIdentifierURIPercentEncoded(aServiceGroupID)
                    + "/services/" + IdentifierHelper.getIdentifierURIPercentEncoded(aDocumentTypeID);
            Request aRequest = Request.Get(sURI);
            SignedServiceMetadataType aMetadata = executeRequest(aRequest).handleResponse(
                    SMPHttpResponseHandlerSigned.create(new BDXRMarshallerSignedServiceMetadataType()));

            // If the Redirect element is present, then follow 1 redirect.
            if (aMetadata.getServiceMetadata() != null && aMetadata.getServiceMetadata().getRedirect() != null) {
                final RedirectType aRedirect = aMetadata.getServiceMetadata().getRedirect();

                // Follow the redirect
                s_aLogger.info("Following a redirect from '" + sURI + "' to '" + aRedirect.getHref() + "'");
                aRequest = Request.Get(aRedirect.getHref());
                aMetadata = executeRequest(aRequest).handleResponse(
                        SMPHttpResponseHandlerSigned.create(new BDXRMarshallerSignedServiceMetadataType()));

                // Check that the certificateUID is correct.
                boolean bCertificateSubjectFound = false;
                outer: for (final Object aObj : aMetadata.getSignature().getKeyInfo().getContent()) {
                    final Object aInfoValue = ((JAXBElement<?>) aObj).getValue();
                    if (aInfoValue instanceof X509DataType) {
                        final X509DataType aX509Data = (X509DataType) aInfoValue;
                        for (final Object aX509Obj : aX509Data.getX509IssuerSerialOrX509SKIOrX509SubjectName()) {
                            final JAXBElement<?> aX509element = (JAXBElement<?>) aX509Obj;
                            // Find the first subject (of type string)
                            if (aX509element.getValue() instanceof String) {
                                final String sSubject = (String) aX509element.getValue();
                                if (!aRedirect.getCertificateUID().equals(sSubject)) {
                                    throw new SMPClientException(
                                            "The certificate UID of the redirect did not match the certificate subject. Subject is '"
                                                    + sSubject + "'. Required certificate UID is '"
                                                    + aRedirect.getCertificateUID() + "'");
                                }
                                bCertificateSubjectFound = true;
                                break outer;
                            }
                        }
                    }
                }

                if (!bCertificateSubjectFound)
                    throw new SMPClientException("The X509 certificate did not contain a certificate subject.");
            }
            return aMetadata;
        } catch (final Exception ex) {
            throw getConvertedException(ex);
        }
    }

    /**
     * Gets a signed service metadata object given by its service group id and its
     * document type. This is a specification compliant method.
     *
     * @param aServiceGroupID
     *        The service group id of the service metadata to get. May not be
     *        <code>null</code>.
     * @param aDocumentTypeID
     *        The document type of the service metadata to get. May not be
     *        <code>null</code>.
     * @return A signed service metadata object or <code>null</code> if no such
     *         registration is present.
     * @throws SMPClientException
     *         in case something goes wrong
     * @throws SMPClientUnauthorizedException
     *         A HTTP Forbidden was received, should not happen.
     * @throws SMPClientBadRequestException
     *         The request was not well formed.
     * @see #getServiceRegistration(IParticipantIdentifier,
     *      IDocumentTypeIdentifier)
     */
    @Nullable
    public SignedServiceMetadataType getServiceRegistrationOrNull(
            @Nonnull final IParticipantIdentifier aServiceGroupID,
            @Nonnull final IDocumentTypeIdentifier aDocumentTypeID) throws SMPClientException {
        try {
            return getServiceRegistration(aServiceGroupID, aDocumentTypeID);
        } catch (final SMPClientNotFoundException ex) {
            return null;
        }
    }

    /**
     * Gets a signed service metadata object given by its service group id and its
     * document type. This is a specification compliant method.
     *
     * @param aServiceGroupID
     *        The service group id of the service metadata to get. May not be
     *        <code>null</code>.
     * @param aDocumentTypeID
     *        The document type of the service metadata to get. May not be
     *        <code>null</code>.
     * @param aProcessID
     *        The process ID of the service metadata to get. May not be
     *        <code>null</code>.
     * @param aTransportProfile
     *        The transport profile of the service metadata to get. May not be
     *        <code>null</code>.
     * @return The endpoint from the signed service metadata object or
     *         <code>null</code> if no such registration is present.
     * @throws SMPClientException
     *         in case something goes wrong
     * @throws SMPClientUnauthorizedException
     *         A HTTP Forbidden was received, should not happen.
     * @throws SMPClientBadRequestException
     *         The request was not well formed.
     * @see #getServiceRegistrationOrNull(IParticipantIdentifier,IDocumentTypeIdentifier)
     */
    @Nullable
    public EndpointType getEndpoint(@Nonnull final IParticipantIdentifier aServiceGroupID,
            @Nonnull final IDocumentTypeIdentifier aDocumentTypeID, @Nonnull final IProcessIdentifier aProcessID,
            @Nonnull final ISMPTransportProfile aTransportProfile) throws SMPClientException {
        ValueEnforcer.notNull(aServiceGroupID, "serviceGroupID");
        ValueEnforcer.notNull(aDocumentTypeID, "DocumentTypeID");
        ValueEnforcer.notNull(aProcessID, "ProcessID");
        ValueEnforcer.notNull(aTransportProfile, "TransportProfile");

        // Get meta data for participant/documentType
        final SignedServiceMetadataType aSignedServiceMetadata = getServiceRegistrationOrNull(aServiceGroupID,
                aDocumentTypeID);
        return aSignedServiceMetadata == null ? null
                : getEndpoint(aSignedServiceMetadata, aProcessID, aTransportProfile);
    }

    /**
     * Extract the Endpoint from the signedServiceMetadata that matches the passed
     * process ID and the optional required transport profile.
     *
     * @param aSignedServiceMetadata
     *        The signed service meta data object (e.g. from a call to
     *        {@link #getServiceRegistrationOrNull(IParticipantIdentifier, IDocumentTypeIdentifier)}
     *        . May not be <code>null</code>.
     * @param aProcessID
     *        The process identifier to be looked up. May not be <code>null</code>
     *        .
     * @param aTransportProfile
     *        The required transport profile to be used. May not be
     *        <code>null</code>.
     * @return <code>null</code> if no matching endpoint was found
     */
    @Nullable
    public static EndpointType getEndpoint(@Nonnull final SignedServiceMetadataType aSignedServiceMetadata,
            @Nonnull final IProcessIdentifier aProcessID, @Nonnull final ISMPTransportProfile aTransportProfile) {
        ValueEnforcer.notNull(aSignedServiceMetadata, "SignedServiceMetadata");
        ValueEnforcer.notNull(aSignedServiceMetadata.getServiceMetadata(), "SignedServiceMetadata.ServiceMetadata");
        if (aSignedServiceMetadata.getServiceMetadata().getServiceInformation() == null) {
            // It seems to be a redirect and not service information
            return null;
        }
        ValueEnforcer.notNull(aSignedServiceMetadata.getServiceMetadata().getServiceInformation().getProcessList(),
                "SignedServiceMetadata.ServiceMetadata.ServiceInformation.ProcessList");
        ValueEnforcer.notNull(aProcessID, "ProcessID");
        ValueEnforcer.notNull(aTransportProfile, "TransportProfile");

        // Iterate all processes
        final ServiceInformationType aServiceInformation = aSignedServiceMetadata.getServiceMetadata()
                .getServiceInformation();
        if (aServiceInformation != null) {
            // Okay, it's not a redirect
            final List<ProcessType> aAllProcesses = aServiceInformation.getProcessList().getProcess();
            for (final ProcessType aProcessType : aAllProcesses) {
                // Matches the requested one?
                if (IdentifierHelper.areProcessIdentifiersEqual(aProcessType.getProcessIdentifier(), aProcessID)) {
                    // Get all endpoints
                    final List<EndpointType> aEndpoints = aProcessType.getServiceEndpointList().getEndpoint();
                    // Filter by required transport profile
                    final List<EndpointType> aRelevantEndpoints = new ArrayList<EndpointType>();
                    for (final EndpointType aEndpoint : aEndpoints)
                        if (aTransportProfile.getID().equals(aEndpoint.getTransportProfile()))
                            aRelevantEndpoints.add(aEndpoint);

                    if (aRelevantEndpoints.size() != 1) {
                        s_aLogger.warn("Found " + aRelevantEndpoints.size() + " endpoints for process " + aProcessID
                                + " and transport profile " + aTransportProfile.getID()
                                + (aRelevantEndpoints.isEmpty() ? ""
                                        : ": " + aRelevantEndpoints.toString() + " - using the first one"));
                    }

                    // Use the first endpoint or null
                    return CollectionHelper.getFirstElement(aRelevantEndpoints);
                }
            }
        }
        return null;
    }

    @Nullable
    public static String getEndpointAddress(@Nullable final EndpointType aEndpoint) {
        return aEndpoint == null ? null : aEndpoint.getEndpointURI();
    }

    @Nullable
    public String getEndpointAddress(@Nonnull final IParticipantIdentifier aServiceGroupID,
            @Nonnull final IDocumentTypeIdentifier aDocumentTypeID, @Nonnull final IProcessIdentifier aProcessID,
            @Nonnull final ISMPTransportProfile aTransportProfile) throws SMPClientException {
        final EndpointType aEndpoint = getEndpoint(aServiceGroupID, aDocumentTypeID, aProcessID, aTransportProfile);
        return getEndpointAddress(aEndpoint);
    }

    @Nullable
    public static byte[] getEndpointCertificateString(@Nullable final EndpointType aEndpoint) {
        return aEndpoint == null ? null : aEndpoint.getCertificate();
    }

    @Nullable
    public byte[] getEndpointCertificateString(@Nonnull final IParticipantIdentifier aServiceGroupID,
            @Nonnull final IDocumentTypeIdentifier aDocumentTypeID, @Nonnull final IProcessIdentifier aProcessID,
            @Nonnull final ISMPTransportProfile aTransportProfile) throws SMPClientException {
        final EndpointType aEndpoint = getEndpoint(aServiceGroupID, aDocumentTypeID, aProcessID, aTransportProfile);
        return getEndpointCertificateString(aEndpoint);
    }

    @Nullable
    public X509Certificate getEndpointCertificate(@Nonnull final IParticipantIdentifier aServiceGroupID,
            @Nonnull final IDocumentTypeIdentifier aDocumentTypeID, @Nonnull final IProcessIdentifier aProcessID,
            @Nonnull final ISMPTransportProfile aTransportProfile) throws SMPClientException, CertificateException {
        final byte[] aCertString = getEndpointCertificateString(aServiceGroupID, aDocumentTypeID, aProcessID,
                aTransportProfile);
        return CertificateHelper.convertByteArrayToCertficate(aCertString);
    }

    @Nullable
    public static X509Certificate getEndpointCertificate(@Nullable final EndpointType aEndpoint)
            throws CertificateException {
        final byte[] aCertString = getEndpointCertificateString(aEndpoint);
        return CertificateHelper.convertByteArrayToCertficate(aCertString);
    }

    /**
     * Returns a service group. A service group references to the service
     * metadata.
     *
     * @param aSMLInfo
     *        The SML object to be used
     * @param aServiceGroupID
     *        The service group id corresponding to the service group which one
     *        wants to get.
     * @return The service group
     * @throws SMPClientException
     *         in case something goes wrong
     * @throws SMPClientUnauthorizedException
     *         A HTTP Forbidden was received, should not happen.
     * @throws SMPClientNotFoundException
     *         The service group id did not exist.
     * @throws SMPClientBadRequestException
     *         The request was not well formed.
     */
    @Nonnull
    public static ServiceGroupType getServiceGroupByDNS(@Nonnull final ISMLInfo aSMLInfo,
            @Nonnull final IParticipantIdentifier aServiceGroupID) throws SMPClientException {
        return new BDXRClientReadOnly(aServiceGroupID, aSMLInfo).getServiceGroup(aServiceGroupID);
    }

    /**
     * Gets a signed service metadata object given by its service group id and its
     * document type.
     *
     * @param aSMLInfo
     *        The SML object to be used
     * @param aServiceGroupID
     *        The service group id of the service metadata to get.
     * @param aDocumentTypeID
     *        The document type of the service metadata to get.
     * @return A signed service metadata object.
     * @throws SMPClientException
     *         in case something goes wrong
     * @throws SMPClientUnauthorizedException
     *         A HTTP Forbidden was received, should not happen.
     * @throws SMPClientNotFoundException
     *         The service group id or document type did not exist.
     * @throws SMPClientBadRequestException
     *         The request was not well formed.
     */
    @Nonnull
    public static SignedServiceMetadataType getServiceRegistrationByDNS(@Nonnull final ISMLInfo aSMLInfo,
            @Nonnull final IParticipantIdentifier aServiceGroupID,
            @Nonnull final IDocumentTypeIdentifier aDocumentTypeID) throws SMPClientException {
        return new BDXRClientReadOnly(aServiceGroupID, aSMLInfo).getServiceRegistration(aServiceGroupID,
                aDocumentTypeID);
    }
}