it.txt.ens.client.impl.BasicENSClient.java Source code

Java tutorial

Introduction

Here is the source code for it.txt.ens.client.impl.BasicENSClient.java

Source

/***************************************************************************
 * Copyright 2012-2013 TXT e-solutions SpA
 * 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.
 * 
 * This work was performed within the IoT_at_Work Project
 * and partially funded by the European Commission's
 * 7th Framework Programme under the contract ICT-257367.
 *
 * Authors:
 *      Salvatore Piccione (TXT e-solutions SpA)
 *
 * Contributors:
 *        Domenico Rotondi (TXT e-solutions SpA)
 **************************************************************************/
package it.txt.ens.client.impl;

import it.txt.access.capability.commons.signer.X509DocumentSigner;
import it.txt.access.capability.commons.signer.model.X509CertificateKeyValues;
import it.txt.access.capability.commons.signer.model.X509CertificateSubjectInfo;
import it.txt.access.capability.commons.signer.model.X509DocumentSignerInfo;
import it.txt.access.capability.commons.utils.XMLPrinter;
import it.txt.access.capability.finder.CapabilitySearchReturn;
import it.txt.access.capability.finder.CapabilityXQuerySaxURISearch;
import it.txt.access.capability.finder.util.URIResourceIDSections;
import it.txt.ens.client.core.ENSBrokerConnectionParameters;
import it.txt.ens.client.core.ENSClient;
import it.txt.ens.client.core.factory.ENSBrokerConnectionParametersFactory;
import it.txt.ens.client.exception.ENSClientException;
import it.txt.ens.client.exception.ENSClientExceptionCodes;
import it.txt.ens.core.ENSAuthzServiceConnectionParameters;
import it.txt.ens.core.ENSOperation;
import it.txt.ens.core.ENSResource;
import it.txt.ens.core.ENSStatus;
import it.txt.ens.core.KeystoreParameters;
import it.txt.ens.core.X509CertificateRetrievalParameters;
import it.txt.ens.core.exception.ENSConnectionException;
import it.txt.ens.core.exception.ENSConnectionExceptionCodes;
import it.txt.ens.schema.FailureResponseType;
import it.txt.ens.schema.RequestType;
import it.txt.ens.schema.ResponseType;
import it.txt.ens.schema.SuccessResponseType;
import it.txt.ens.schema.factory.ENSRequestFactoryException;
import it.txt.ens.schema.factory.ENSResponseFactoryException;
import it.txt.ens.schema.request.factory.ENSAuthorisationRequestFactory;
import it.txt.ens.schema.response.factory.ENSAuthorisationResponseFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.transform.TransformerException;

import org.w3c.dom.Document;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.RpcClient;
import com.rabbitmq.client.ShutdownSignalException;

/**
 * Defines an abstract implementation of the {@link ENSClient} interface.<br/>
 * This implementation is abstract because a generic client is useless because can
 * only connect and disconnect to the ENS but cannot use the session. Subclasses (i.e.
 * classes that models publishers and subscribers) should use the operative broker's
 * connection parameters to connect to ENS resource they have been authorised to access
 * and publish or subscribe to an event stream.
 * 
 * @author Salvatore Piccione (TXT e-solutions SpA - salvatore.piccione AT txtgroup.com)
 * @author Domenico Rotondi (TXT e-solutions SpA - domenico.rotondi AT txtgroup.com)
 *
 */
public abstract class BasicENSClient implements ENSClient {

    /* (non-Javadoc)
     * @see it.txt.ens.client.core.ENSClient#getTargetResource()
     */
    @Override
    public ENSResource getTargetResource() {
        return targetResource;
    }

    private static final String BUNDLE_LOCATION = "resource-bundles/" + BasicENSClient.class.getSimpleName();
    private static final ResourceBundle LOG_MESSAGES = ResourceBundle.getBundle(BUNDLE_LOCATION, Locale.ROOT);
    private static final Logger LOGGER = Logger.getLogger(BasicENSClient.class.getName(), BUNDLE_LOCATION);
    private static final String ENCODING = "UTF-8";
    private static final int TIMEOUT = 100000;
    /**
     * The AMQP connection to the operative broker.
     */
    protected Connection connection;
    /**
     * The AMQP channel to the operative broker.
     */
    protected Channel channel;
    /**
     * The AMQP connection factory.
     */
    protected ConnectionFactory factory;
    private final File capabilityDirectory;
    private final String subjectID;
    /**
     * The ENS resource the client has been authorised to access.
     */
    protected final ENSResource targetResource;
    private final ENSAuthorisationRequestFactory requestFactory;
    private final ENSAuthorisationResponseFactory responseFactory;
    private final ENSOperation operation;
    private final ENSAuthzServiceConnectionParameters authzServiceConnParams;
    private final X509CertificateRetrievalParameters certParams;
    private final KeystoreParameters keystoreParams;
    private final ENSBrokerConnectionParametersFactory connParamsFactory;
    private final Date sessionExpiration;
    private final String debugID;

    /**
     * Sets the fields of this objects.
     * 
     * @param subjectID
     * @param targetResource
     * @param operation
     * @param sessionExpiration
     * @param authzServiceConnParams
     * @param keystoreParams
     * @param certParams
     * @param capabilityDirectory
     * @param requestFactory
     * @param responseFactory
     * @param connParamsFactory
     */
    protected BasicENSClient(String subjectID, ENSResource targetResource, ENSOperation operation,
            Date sessionExpiration, ENSAuthzServiceConnectionParameters authzServiceConnParams,
            KeystoreParameters keystoreParams, X509CertificateRetrievalParameters certParams,
            File capabilityDirectory, ENSAuthorisationRequestFactory requestFactory,
            ENSAuthorisationResponseFactory responseFactory,
            ENSBrokerConnectionParametersFactory connParamsFactory) {

        this.capabilityDirectory = capabilityDirectory;
        this.targetResource = targetResource;
        this.subjectID = subjectID;
        this.requestFactory = requestFactory;
        this.responseFactory = responseFactory;
        this.operation = operation;
        this.authzServiceConnParams = authzServiceConnParams;
        this.certParams = certParams;
        this.keystoreParams = keystoreParams;
        this.connParamsFactory = connParamsFactory;
        this.sessionExpiration = sessionExpiration;
        this.debugID = this.getClass().getSimpleName() + UUID.randomUUID().toString();
    }

    /* (non-Javadoc)
     * @see it.txt.ens.client.core.ENSClient#connect()
     */
    @Override
    public void connect() throws ENSClientException, ENSConnectionException {
        //create and configure the RabbitMQ Connection Factory
        factory = new ConnectionFactory();
        factory.setUsername(authzServiceConnParams.getSubjectID());
        factory.setPassword(authzServiceConnParams.getAccessToken());
        factory.setPort(authzServiceConnParams.getBrokerPort());
        factory.setHost(authzServiceConnParams.getBrokerHost());
        factory.setVirtualHost(authzServiceConnParams.getVirtualHost());

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "authorisation.step1", new Object[] { capabilityDirectory.getAbsoluteFile(),
                    subjectID, targetResource.getURI().toString(), operation.toString() });
        }

        //START CAPABILITY SEARCH
        //retrieve the best suitable capabilities
        CapabilityXQuerySaxURISearch capabilityFinder = new CapabilityXQuerySaxURISearch(capabilityDirectory);

        URIResourceIDSections uriSections = new URIResourceIDSections();
        uriSections.setAuthority(targetResource.getHost());
        uriSections.setScheme(ENSResource.URI_SCHEME);
        uriSections.setNamespace(targetResource.getNamespace());
        uriSections.setPattern(targetResource.getPattern());
        uriSections.setService(targetResource.getPath());
        List<CapabilitySearchReturn> capabilities;
        try {
            capabilities = capabilityFinder.doSearchCapability(subjectID, uriSections.toURI().toString(),
                    operation.toString());
        } catch (UnsupportedEncodingException e) {
            ENSClientException ece = new ENSClientException(
                    ENSClientExceptionCodes.TARGET_RESOURCE_URI_CREATION_ERROR, e);
            LOGGER.log(Level.SEVERE, ece.getMessage(), e);
            throw ece;
        } catch (URISyntaxException e) {
            ENSClientException ece = new ENSClientException(
                    ENSClientExceptionCodes.TARGET_RESOURCE_URI_CREATION_ERROR, e);
            LOGGER.log(Level.SEVERE, ece.getMessage(), e);
            throw ece;
        }
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "capabilitySearch.selectedCapabilities", capabilities.size());
        }
        if (capabilities.isEmpty())
            throw new ENSClientException(ENSClientExceptionCodes.NO_SUITABLE_CAPABILITY_FOUND);
        if (capabilities.size() > 1)
            Collections.sort(capabilities, new Comparator<CapabilitySearchReturn>() {
                public int compare(CapabilitySearchReturn o1, CapabilitySearchReturn o2) {
                    XMLGregorianCalendar issueDate1 = o1.getCapabilityIssueDateToXmlGregorianCalendar();
                    XMLGregorianCalendar isssueDate2 = o2.getCapabilityIssueDateToXmlGregorianCalendar();
                    return issueDate1.compare(isssueDate2);
                }
            });
        CapabilitySearchReturn selectedCapability = capabilities.get(0);

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "authorisation.step1OK",
                    new Object[] {
                            selectedCapability.getCapabilityID(), selectedCapability
                                    .getCapabilityIssueDateToXmlGregorianCalendar().toGregorianCalendar().getTime(),
                            selectedCapability.getCapabilityFile().getAbsolutePath() });
            LOGGER.log(Level.FINE, "authorisation.step2");
        }
        //STOP CAPABILITY SEARCH

        //create a JAXB request object
        FileInputStream capabilityStream = null;
        RequestType requestType = null;
        try {
            capabilityStream = new FileInputStream(selectedCapability.getCapabilityFile());
            requestType = requestFactory.create(targetResource.getURI(), subjectID, operation.toString(),
                    sessionExpiration, capabilityStream);
        } catch (FileNotFoundException e) {
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE,
                        MessageFormat.format(LOG_MESSAGES.getString("capabilitySearch.missingExistingFile"),
                                selectedCapability.getCapabilityFile().getAbsolutePath()),
                        e);
            throw new ENSClientException(ENSClientExceptionCodes.MISSING_SELECTED_CAPABILITY, e);
        } catch (ENSRequestFactoryException e) {
            ENSClientException clientExc = new ENSClientException(ENSClientExceptionCodes.REQUEST_CREATION, e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, clientExc.getMessage(), e);
            throw clientExc;
        }

        //here we are sure that the request type has been instantiated
        Document requestDOM = null;
        try {
            requestDOM = requestFactory.marshal(requestType);
        } catch (ENSRequestFactoryException e) {
            ENSClientException clientExc = new ENSClientException(ENSClientExceptionCodes.REQUEST_MARSHALLING_ERROR,
                    e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, clientExc.getMessage(), e);
            throw clientExc;
        }
        //we are sure that the request DOM has been instantiated
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "authorisation.step2OK",
                    XMLPrinter.printDocumentElement(requestDOM.getDocumentElement(), true));
            LOGGER.log(Level.FINE, "authorisation.step3");
        }

        X509DocumentSignerInfo signerInfo = new X509DocumentSignerInfo();
        signerInfo.setKeystorePath(keystoreParams.getKeystorePath().getAbsolutePath());
        signerInfo.setKeystorePwd(keystoreParams.getKeystorePassword());
        signerInfo.setPrivateKeyPwd(certParams.getPrivateKeyPassword());
        signerInfo.setSignerID(certParams.getX509CertificateSubject());
        try {
            X509CertificateKeyValues keyValues = X509DocumentSigner.getCertificateKeyValues(signerInfo);
            X509DocumentSigner.signXMLElement(requestDOM.getDocumentElement(), keyValues,
                    ENSAuthorisationRequestFactory.SIGN_DOCUMENT_AFTER_NODE);
        } catch (GeneralSecurityException e) {
            ENSClientException clientExc = new ENSClientException(
                    ENSClientExceptionCodes.DIGITAL_SIGNATURE_CREATION_ERROR, e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, clientExc.getMessage(), e);
            throw clientExc;
        }
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.log(Level.FINE, "authorisation.step3OK",
                    XMLPrinter.printDocumentElement(requestDOM.getDocumentElement(), true));

        //transformation of the digitally signed XML DOM into an array of byte
        ByteArrayOutputStream xmlos;

        try {
            xmlos = (ByteArrayOutputStream) XMLPrinter
                    .convertDOMIntoByteStream(requestDOM, false, null, new ByteArrayOutputStream())
                    .getOutputStream();
        } catch (TransformerException e) {
            ENSClientException clientExc = new ENSClientException(ENSClientExceptionCodes.SIGNATURE_TO_BYTES_ERROR,
                    e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, clientExc.getMessage(), e);
            throw clientExc;
        }

        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.log(Level.FINE, "authorisation.step4");
        Connection authorisationConnection = null;
        Channel authorisationChannel = null;
        String rawResponse = null;
        try {
            //initialise the connection to the ENS access request broker
            authorisationConnection = factory.newConnection();
            authorisationChannel = authorisationConnection.createChannel();

            //create an RPC Client
            //FIXME SHOULD WE INDICATE THE EXCHANGE??
            RpcClient client = new RpcClient(authorisationChannel, "", authzServiceConnParams.getDestinationName(),
                    TIMEOUT);
            rawResponse = client.stringCall(xmlos.toString(ENCODING));
            //            rawResponse = client.stringCall(xmlos.toString());
        } catch (IOException e) {
            ENSConnectionException connExc = new ENSConnectionException(
                    ENSConnectionExceptionCodes.ACCESS_REQUEST_BROKER_CONNECTION_ERROR, e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, connExc.getMessage(), e);
            throw connExc;
        } catch (ShutdownSignalException e) {
            ENSConnectionException connExc = new ENSConnectionException(
                    ENSConnectionExceptionCodes.ACCESS_REQUEST_BROKER_CONNECTION_ERROR, e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, connExc.getMessage(), e);
            throw connExc;
        } catch (TimeoutException e) {
            ENSConnectionException connExc = new ENSConnectionException(
                    ENSConnectionExceptionCodes.ACCESS_REQUEST_BROKER_CONNECTION_ERROR, e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, connExc.getMessage(), e);
            throw connExc;
        } finally {
            if (authorisationChannel != null)
                try {
                    authorisationChannel.close();
                } catch (IOException e) {
                }

            if (authorisationConnection != null)
                try {
                    authorisationConnection.close();
                } catch (IOException e) {
                }
        }

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "authorisation.step4OK", rawResponse);
        }
        if (ENSStatus.FailureCodes.INTERNAL_ERROR.equalsIgnoreCase(rawResponse)) {
            throw new ENSClientException(ENSClientExceptionCodes.INTERNAL_SERVER_ERROR);
        }

        ResponseType responseObject = null;
        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(rawResponse.getBytes(ENCODING));
            //            inputStream = new ByteArrayInputStream(rawResponse.getBytes());
            responseObject = responseFactory.parseInputStream(inputStream);
            Document responseDOM = responseFactory.marshal(responseObject);

            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "authorisation.step5");
            }
            boolean isSignatureVerified = X509DocumentSigner.verifyXMLElementSign(responseDOM.getDocumentElement(),
                    new X509CertificateSubjectInfo());
            if (!isSignatureVerified) {
                throw new ENSClientException(ENSClientExceptionCodes.TAMPERED_RESPONSE);
            }

        } catch (UnsupportedEncodingException e) {
            ENSClientException ece = new ENSClientException(ENSClientExceptionCodes.UNSUPPORTED_ENCODING, e,
                    ENCODING, debugID);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, ece.getMessage(), e);
            throw ece;
        } catch (ENSResponseFactoryException e) {
            ENSClientException ece = new ENSClientException(ENSClientExceptionCodes.RESPONSE_MARSHALLING_ERROR, e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, ece.getMessage(), e);
            throw ece;
        } catch (GeneralSecurityException e) {
            ENSClientException ece = new ENSClientException(
                    ENSClientExceptionCodes.DIGITAL_SIGNATURE_VERIFICATION_ERROR, e);
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, ece.getMessage(), e);
            throw ece;
        }

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "authorisation.step5OK");
            LOGGER.log(Level.FINE, "authorisation.step6");
        }

        //analysis of the response
        if (responseObject.isResult()) {
            SuccessResponseType success = responseObject.getResultDetails().getSuccessResponse();
            ENSBrokerConnectionParameters operativeBrokerConnParams = connParamsFactory.create(
                    success.getSubject().getSubjectID(), success.getAccessToken(), success.getBrokerHost(),
                    success.getBrokerPort().intValue(), success.getVirtualHost(), success.getQueueName(),
                    success.isUseTLS(), success.getSessionExpiration().toGregorianCalendar().getTime(),
                    success.getSessionToken());
            if (LOGGER.isLoggable(Level.INFO)) {
                LOGGER.log(Level.INFO, "authorisation.step6OK",
                        new String[] { debugID, operativeBrokerConnParams.toString() });
            }
            factory = new ConnectionFactory();
            factory.setHost(operativeBrokerConnParams.getBrokerHost());
            factory.setPassword(operativeBrokerConnParams.getAccessToken());
            factory.setPort(operativeBrokerConnParams.getBrokerPort());
            factory.setUsername(operativeBrokerConnParams.getSubjectID());
            factory.setVirtualHost(operativeBrokerConnParams.getVirtualHost());
            useENSBrokerConnectionParameters(operativeBrokerConnParams);
            try {
                connection = factory.newConnection();
            } catch (IOException e) {
                ENSClientException ce = new ENSClientException(ENSClientExceptionCodes.OPERATIVE_CONNECTION_ERROR,
                        e, factory.getVirtualHost(), factory.getHost());
                LOGGER.log(Level.SEVERE, ce.getMessage(), e);
                throw ce;
            }
        } else {
            FailureResponseType failure = responseObject.getResultDetails().getFailureResponse();
            ENSClientException failureException = new ENSClientException(failure);
            if (LOGGER.isLoggable(Level.SEVERE)) {
                LOGGER.log(Level.SEVERE, "authorisation.step6FAILURE",
                        new String[] { failure.getErrorCode(), failure.getErrorReason() });
            }
            throw failureException;
        }
    }

    /* (non-Javadoc)
     * @see it.txt.ens.client.core.ENSClient#disconnect()
     */
    @Override
    public void disconnect() {
        closeConnection();
    }

    protected void closeConnection() {
        try {
            if (isConnected()) {
                connection.close();
            }
        } catch (IOException e) {
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.log(Level.WARNING, "exceptionWhileClosingConnection",
                        connection.getAddress() + ":" + connection.getPort());
            }
        }
    }

    /**
     * Uses the connection parameters according to the kind of ENS client<br/>Subclasses should use this
     * method to retrieve from the connection parameters above  
     * 
     * @param parameters the operative broker's connection parameters.
     */
    protected abstract void useENSBrokerConnectionParameters(ENSBrokerConnectionParameters parameters);

    public boolean isConnected() {
        if (connection == null)
            return false;
        return connection.isOpen();
    }

}