Java tutorial
/*************************************************************************** * 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(); } }