/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Copyright (c) 2004-2005 Sun Microsystems Inc., All Rights Reserved.
/*
* MessageDenormalizerImpl.java
*
* SUN PROPRIETARY/CONFIDENTIAL.
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*
*/
package com.sun.enterprise.jbi.serviceengine.util.soap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jbi.messaging.Fault;
import javax.jbi.messaging.NormalizedMessage;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.AttachmentPart;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.activation.DataHandler;
import java.util.logging.Logger;
/**
* This Basic Profile 1.0 aware implementation is used to denormalize a JBI Normalized
* Message and convert it into a SOAP message format.
*
* @author Sun Microsystems, Inc.
*/
public class MessageDenormalizerImpl implements MessageDenormalizer
{
/**
* Namespace prefix for the payload.
*/
private static final String PAYLOAD_NAMESPACE_PREFIX = "jbisb0";
/**
* SOAP Namespace prefix.
*/
private static final String SOAP_NAMESPACE_PREFIX = "soap";
/**
* XML Schema Instance prefix.
*/
private static final String XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX = "xsi";
/**
* Internal handle to the message factory
*/
private MessageFactory mMessageFactory;
/**
* Internal handle to the logger instance
*/
private Logger mLogger;
/**
* Internal handle to String Translator instance.
*/
private StringTranslator mStringTranslator;
/**
* Internal handle to the transformer instance
*/
private Transformer mTransformer;
/**
* Creates a new instance of MessageDenormalizerImpl.
*/
public MessageDenormalizerImpl()
{
try
{
mLogger = Logger.getLogger(this.getClass().getPackage().getName());
mStringTranslator = new StringTranslator(this.getClass().getPackage().getName(), this.getClass().getClassLoader());
mMessageFactory = MessageFactory.newInstance();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
mTransformer = transformerFactory.newTransformer();
mTransformer.setOutputProperty("method", "xml");
mTransformer.setOutputProperty("omit-xml-declaration", "yes");
}
catch (Exception exception)
{
// This should not happen. In case it does, log the exception and
// set the factory object to null
mLogger.severe( mStringTranslator.getString("SBC_MESSAGE_FACTORY_CREATION_FAILURE") );
mLogger.severe( mStringTranslator.getString("SBC_ERROR_DETAILS", exception.toString()) );
mMessageFactory = null;
mTransformer = null;
}
}
/**
* Converts a JBI normalized message to a <code> javax.jbi.soap.SOAPMessage </code>
* instance. The SOAP Header information is extracted from the NormalizedMessage
* property "SoapHeader" and the SOAP Body content is extracted from the Normalized
* Message content. Any attachments present in the NormalizedMessage are also
* denormalized and added to the created <code> javax.jbi.soap.SOAPMessage </code>
* instance.
*
* @param normalizedMessage message to be denormalized.
* @param operation operation invoked
* @param isResponse indicates if a response messages needs to be generated
*
* @return the SOAP Message.
*/
public SOAPWrapper denormalizeMessage(
NormalizedMessage normalizedMessage, Operation operation, boolean isResponse)
{
SOAPWrapper wrapper = null;
Writer writer = null;
mLogger.fine( mStringTranslator.getString("SBC_DENORMALIZE_JBI_MESSAGE") );
try
{
// Create a SOAP Message
ByteArrayOutputStream bufferedStream = new ByteArrayOutputStream();
writer = new OutputStreamWriter(bufferedStream, "UTF-8");
writeEnvelopeHeader(writer);
if ( normalizedMessage != null)
{
// Writer the header to the writer instance.
writeHeader(normalizedMessage, writer);
}
// Extract the body information from the Normalized Message
writeBody(normalizedMessage, operation, isResponse, writer);
writeEnvelopeFooter(writer);
writer.flush();
// Create a soap message
SOAPMessage soapMessage = createSOAPMessage(bufferedStream);
// Denormalize Attachments
denormalizeAttachments ( soapMessage, normalizedMessage);
// Create a soap response wrapper
wrapper = new SOAPWrapper(soapMessage);
if (normalizedMessage instanceof Fault)
{
wrapper.setStatus(SOAPConstants.JBI_FAULT);
}
else
{
wrapper.setStatus(SOAPConstants.JBI_SUCCESS);
}
}
catch (RuntimeException runtimeException)
{
mLogger.severe( mStringTranslator.getString("SBC_DENORMALIZE_JBI_MESSAGE_FAILURE_RT_EXP") );
// Create a soap fault wrapper
wrapper = denormalizeMessage(runtimeException);
}
catch (Exception exception)
{
mLogger.warning( mStringTranslator.getString("SBC_DENORMALIZE_JBI_MESSAGE_FAILURE_EXP") );
mLogger.warning( mStringTranslator.getString("SBC_ERROR_DETAILS", exception.toString()) );
mLogger.warning( mStringTranslator.getString("SBC_CREATE_SOAP_FAULT") );
// Create a soap fault wrapper
wrapper = denormalizeMessage(exception);
}
finally
{
closeWriter(writer);
}
mLogger.fine( mStringTranslator.getString("SBC_SUCCESS_DENORMALIZE_JBI_MESSAGE") );
return wrapper;
}
/**
* Converts an exception to a SOAP Message. It uses the default Server fault code
* for denormalization.
*
* @param exception exception instance
*
* @return denormalized exception object
*/
public SOAPWrapper denormalizeMessage(Exception exception)
{
return denormalizeMessage(exception, SOAPConstants.SERVER_FAULT_CODE);
}
/**
* Converts an exception to a SOAP Message using the provided faultCode. The code
* expects the faultcode passed to be part of the soap namespace.
*
* @param exception exception instance
* @param faultCode fault code
*
* @return denormalized exception object
*/
public SOAPWrapper denormalizeMessage(Exception exception, String faultCode)
{
SOAPWrapper wrapper = null;
Writer writer = null;
mLogger.fine( mStringTranslator.getString("SBC_DENORMALIZE_EXCEPTION") );
try
{
// Create the ws-i compliant fault message from the exception
ByteArrayOutputStream bufferedStream = new ByteArrayOutputStream();
writer = new OutputStreamWriter(bufferedStream, "UTF-8");
if (exception == null)
{
mLogger.warning( mStringTranslator.getString("SBC_NULL_OBJECT_DENORMALIZATION") );
}
writeEnvelopeHeader(writer);
writer.write("<" + SOAP_NAMESPACE_PREFIX + ":Body>");
writeFault(exception, faultCode, writer);
writer.write("</" + SOAP_NAMESPACE_PREFIX + ":Body>");
writeEnvelopeFooter(writer);
writer.flush();
// Create a soap message
SOAPMessage soapMessage = createSOAPMessage(bufferedStream);
// Create a SOAP wrapper with service url as null
wrapper = new SOAPWrapper(soapMessage);
wrapper.setStatus(SOAPConstants.JBI_ERROR);
}
catch (RuntimeException runtimeException)
{
mLogger.severe( mStringTranslator.getString( "SBC_SOAP_FAULT_GENERATION_FAILURE_RT_EXP") );
}
catch (Exception denormalizationException)
{
// This should not happen. In case it does do nothing. Log message
mLogger.severe( mStringTranslator.getString("SBC_SOAP_FAULT_GENERATION_FAILURE") );
}
finally
{
closeWriter(writer);
}
mLogger.fine( mStringTranslator.getString("SBC_SUCCESS_DENORMALIZE_EXCEPTION") );
return wrapper;
}
/**
* This method extracts the payload from the Normalized Message and writes it
* using the writer stream. The payload content is enclosed between the SOAP:Body
* header and SOAP:Body footer information.
*
* @param normalizedMessage normalized message
* @param operation operation invoked
* @param isResponse indicates if a response messages needs to be generated
* @param writer writer object to be used
*
* @throws Exception if the body cannot be written
*/
protected void writeBody(
NormalizedMessage normalizedMessage, Operation operation, boolean isResponse,
Writer writer) throws Exception
{
StringWriter stringWriter = null;
try
{
boolean isEmptyResponse = isResponse && ( normalizedMessage == null );
// Add the body information
writeBodyHeader(operation, writer, isEmptyResponse);
if ( normalizedMessage != null)
{
stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
mTransformer.transform(normalizedMessage.getContent(), result);
writer.write(stringWriter.toString());
}
writeBodyFooter(operation, writer, isEmptyResponse);
writer.flush();
}
finally
{
closeWriter(stringWriter);
}
}
/**
* The method extracts the header information from the Normalized Message property
* "SoapHeader" and writes it using the writer instance. The header information
* is expected to be propagated as a <code> javax.xml.soap.SOAPHeader </code>
* implementation instance.
*
* @param normalizedMessage normalizedMessage
* @param writer writer object to be used
*
* @throws Exception if header cannot be used to write to the writer instance
*/
protected void writeHeader(NormalizedMessage normalizedMessage, Writer writer)
throws Exception
{
// Extract header information from the Normalized Message
SOAPHeader soapHeader =
(SOAPHeader) normalizedMessage.getProperty(
SOAPConstants.HEADER_PROPERTY_NAME );
StringWriter stringWriter = null;
if ( soapHeader != null)
{
try
{
stringWriter = new StringWriter();
Source source = new DOMSource( soapHeader );
Result result = new StreamResult(stringWriter);
mTransformer.transform(source, result);
// Add the header information
writer.write("<" + SOAP_NAMESPACE_PREFIX + ":Header>");
writer.write(stringWriter.toString());
writer.write("</" + SOAP_NAMESPACE_PREFIX + ":Header>");
writer.flush();
}
finally
{
closeWriter(stringWriter);
}
}
else
{
mLogger.fine( mStringTranslator.getString("SBC_NO_HEADER") );
}
}
/**
* Uses the writer object to write the SOAP:Body header information. This method
* is invoked before the body payload is written.
*
* @param operation operation invoked
* @param writer writer object to be used
* @param isEmptyResponse indicates if an empty response message needs to be generated
*
* @throws Exception if body header cannot be written.
*/
protected void writeBodyHeader(
Operation operation, Writer writer, boolean isEmptyResponse)
throws Exception
{
writer.write("<" + SOAP_NAMESPACE_PREFIX + ":Body>");
if ( isEmptyResponse)
{
writer.write("<" + PAYLOAD_NAMESPACE_PREFIX + ":");
writer.write(operation.getName() + "Response");
writer.write(" xmlns:" + PAYLOAD_NAMESPACE_PREFIX + "=\"");
writer.write(operation.getOutputNamespace() + "\"");
writer.write(">");
}
writer.flush();
}
/**
* Uses writer object to write the SOAP:Body footer information. This method is
* invoked after the body payload has been written.
*
* @param operation operation invoked
* @param writer writer object.
* @param isEmptyResponse indicates if a response messages needs to be generated
*
* @throws Exception if body footer cannot be written
*/
protected void writeBodyFooter(
Operation operation, Writer writer, boolean isEmptyResponse)
throws Exception
{
if ( isEmptyResponse )
{
writer.write("</" + PAYLOAD_NAMESPACE_PREFIX + ":");
writer.write(operation.getName() + "Response>");
}
writer.write("</" + SOAP_NAMESPACE_PREFIX + ":Body>");
writer.flush();
}
/**
* Uses the provided input data to create a <code> javax.xml.soap.SOAPMessage </code>
* instance.
*
* @param byteStream Stream which contains the soap messages information as bytes.
*
* @return SOAP Message object
*
* @throws SOAPException if soap message object cannot be created.
* @throws IOException if soap message object cannot be created.
*/
protected SOAPMessage createSOAPMessage(ByteArrayOutputStream byteStream)
throws SOAPException, IOException
{
if (mLogger.isLoggable(Level.FINEST))
{
mLogger.finest( mStringTranslator.getString("SBC_DEONRMALIZED_MESSAGE_DETAILS", byteStream.toString()) );
}
// Create a soap message
SOAPMessage soapMessage = mMessageFactory.createMessage();
// Populate the fault message in the soap Message
byte[] data = byteStream.toByteArray();
ByteArrayInputStream soapInputStream = new ByteArrayInputStream(data);
StreamSource streamSource = new StreamSource(soapInputStream);
soapMessage.getSOAPPart().setContent(streamSource);
soapInputStream.close();
return soapMessage;
}
/**
* Closes the writer instance. This method handles any exceptions thrown
* while handling this request.
*
* @param writer writer instance.
*/
protected void closeWriter(Writer writer)
{
if (writer != null)
{
try
{
writer.close();
}
catch (Exception ioException)
{
// This should not happen. In case it does do nothing
mLogger.warning( mStringTranslator.getString("SBC_CLOSE_OUTPUT_STREAM") );
mLogger.warning( mStringTranslator.getString("SBC_ERROR_DETAILS", ioException.toString()) );
}
}
}
/**
* Uses writer object to write the SOAP:Envelope header information. This method
* is invoked before writing the envelope content ( header and body content).
*
* @param writer writer object.
*
* @throws IOException if envelope header information cannot be written.
*/
protected void writeEnvelopeHeader(Writer writer) throws IOException
{
// Write the soap envelope
writer.write(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<" + SOAP_NAMESPACE_PREFIX +
":Envelope xmlns:" + SOAP_NAMESPACE_PREFIX +
"=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:" +
XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX +
"=\"http://www.w3.org/2001/XMLSchema-instance\">");
writer.flush();
}
/**
* Uses writer object to write the SOAP:Envelope footer information. This method
* is invoked after writing the envelope content ( header and body content).
*
* @param writer writer object
*
* @throws IOException if envelope footer information cannot be written.
*/
protected void writeEnvelopeFooter(Writer writer) throws IOException
{
writer.write("</" + SOAP_NAMESPACE_PREFIX + ":Envelope>");
writer.flush();
}
/**
* Create the SOAP:Fault message based on the provided exception details and writes
* it using the writer instance.
*
* @param exception exception thrown
* @param faultCode fault code
* @param writer writer object
*
* @throws IOException if fault message cannot be generated.
*/
protected void writeFault(Exception exception, String faultCode, Writer writer)
throws IOException
{
writer.write(
"<" + SOAP_NAMESPACE_PREFIX + ":Fault><faultcode>" + SOAP_NAMESPACE_PREFIX +
":" + faultCode + "</faultcode>");
if (exception != null)
{
writer.write("<faultstring>" +
sanitizeMessage(exception.getMessage()) + "</faultstring>");
}
writer.write("</" + SOAP_NAMESPACE_PREFIX + ":Fault>");
writer.flush();
}
/**
* Converts a JBI Fault mesage to a standard <code> javax.xml.soap.SOAPMessage </code>
* message instance. It uses the default Server fault code for denormalization.
*
* @param faultMessage JBI fault message.
*
* @return a new SOAPWrapper instance which contains the SOAP fault Message.
*/
public SOAPWrapper denormalizeFaultMessage(Fault faultMessage)
{
return denormalizeFaultMessage(faultMessage, SOAPConstants.SERVER_FAULT_CODE);
}
/**
* Converts a JBI Fault mesage to a SOAP Message using the specified fault code.
*
* @param faultMessage JBI fault message.
* @param faultCode fault code to be used in the fault message
*
* @return a new SOAPWrapper instance which contains the SOAP fault Message.
*/
public SOAPWrapper denormalizeFaultMessage(Fault faultMessage, String faultCode)
{
SOAPWrapper wrapper = null;
Writer writer = null;
mLogger.fine( mStringTranslator.getString("SBC_DENORMALIZE_FAULT_MESSAGE") );
try
{
// Create the ws-i compliant fault message from the exception
ByteArrayOutputStream bufferedStream = new ByteArrayOutputStream();
String messageFaultCode = (String) faultMessage.getProperty(
SOAPConstants.FAULT_CODE_PROPERTY_NAME);
String faultString = (String) faultMessage.getProperty(
SOAPConstants.FAULT_STRING_PROPERTY_NAME);
if ( messageFaultCode != null )
{
// Override the fault code with the message fault code.
faultCode = messageFaultCode;
}
if ( faultString == null )
{
faultString = mStringTranslator.getString("SBC_DEFAULT_FAULT_STRING");
}
writer = new OutputStreamWriter(bufferedStream, "UTF-8");
writeEnvelopeHeader(writer);
writer.write("<" + SOAP_NAMESPACE_PREFIX + ":Body>");
writer.write(
"<" + SOAP_NAMESPACE_PREFIX + ":Fault " +
XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX + ":type=\"" +
SOAP_NAMESPACE_PREFIX + ":Fault\"" + "><faultcode>" +
SOAP_NAMESPACE_PREFIX + ":" + faultCode + "</faultcode>");
writer.write("<faultstring>" + faultString + "</faultstring>");
writeFaultDetail(faultMessage, writer);
writer.write("</" + SOAP_NAMESPACE_PREFIX + ":Fault>");
writer.write("</" + SOAP_NAMESPACE_PREFIX + ":Body>");
writeEnvelopeFooter(writer);
writer.flush();
// Create a soap message
SOAPMessage soapMessage = createSOAPMessage(bufferedStream);
// Create a SOAP wrapper with service url as null
wrapper = new SOAPWrapper(soapMessage);
wrapper.setStatus(SOAPConstants.JBI_FAULT);
}
catch (RuntimeException runtimeException)
{
mLogger.severe( mStringTranslator.getString( "SBC_SOAP_FAULT_GENERATION_FAILURE_RT_EXP") );
}
catch (Exception exception)
{
// This should not happen. In case it does do nothing. Log message
mLogger.severe( mStringTranslator.getString("SBC_SOAP_FAULT_GENERATION_FAILURE") );
}
finally
{
closeWriter(writer);
}
mLogger.fine( mStringTranslator.getString("SBC_SUCCESS_DENORMALIZE_FAULT") );
return wrapper;
}
/**
* Writes the detailed fault message using the provided writer instance.
*
* @param faultMessage JBI Fault object which contains the fault details.
* @param writer writer object to be used.
*
* @throws Exception if the fault detail vould not be written.
*/
private void writeFaultDetail(Fault faultMessage, Writer writer)
throws Exception
{
StringWriter stringWriter = null;
try
{
stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
mTransformer.transform(faultMessage.getContent(), result);
// Add the fault detail
String detailString = stringWriter.toString().trim();
if (!detailString.equals(""))
{
writer.write("<detail>");
writer.write(detailString);
writer.write("</detail>");
writer.flush();
}
}
finally
{
closeWriter(stringWriter);
}
}
/**
* Sanitizes the messages so that it can be properly read by an XML parser.
*
* @param errorMessage error message to be sanitized.
*
* @return sanitized error message.
*/
protected String sanitizeMessage(String errorMessage)
{
StringBuffer sanitizedBuffer = new StringBuffer();
for (int i = 0; (errorMessage != null) && (i < errorMessage.length()); i++)
{
char currentChar = errorMessage.charAt(i);
switch (currentChar)
{
case '"':
sanitizedBuffer.append(""");
break;
case '&':
sanitizedBuffer.append("&");
break;
case '<':
sanitizedBuffer.append("<");
break;
case '>':
sanitizedBuffer.append(">");
break;
default:
sanitizedBuffer.append(currentChar);
}
}
if (errorMessage == null)
{
return "INTERNAL SERVER ERROR";
}
else
{
return sanitizedBuffer.toString();
}
}
/**
* Denormalizes the attachments present in the JBI Normalized Message and adds
* them to the <code> javax.xml.soap.SoapMessage </code> instance.
*
* @param soapMessage soap message.
* @param normalizedMessage normalized message instance.
*/
private void denormalizeAttachments ( SOAPMessage soapMessage,
NormalizedMessage normalizedMessage)
{
if ( normalizedMessage != null )
{
Iterator attachmentIter = normalizedMessage.getAttachmentNames().iterator();
for (; attachmentIter.hasNext();)
{
String attachmentIdentifier = (String) attachmentIter.next();
DataHandler dataHandler =
normalizedMessage.getAttachment( attachmentIdentifier);
AttachmentPart attachment =
soapMessage.createAttachmentPart( dataHandler);
attachment.setContentId ( attachmentIdentifier);
attachment.setContentType ( dataHandler.getContentType());
soapMessage.addAttachmentPart ( attachment );
}
}
}
}
|