/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.axis2.context.externalize; import org.apache.axiom.attachments.Attachments; import org.apache.axiom.om.OMException; import org.apache.axiom.om.OMOutputFormat; import org.apache.axiom.om.OMXMLBuilderFactory; import org.apache.axiom.om.OMXMLParserWrapper; import org.apache.axiom.om.impl.MTOMConstants; import org.apache.axiom.om.util.StAXParserConfiguration; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axis2.AxisFault; import org.apache.axis2.Constants; import org.apache.axis2.builder.BuilderUtil; import org.apache.axis2.context.MessageContext; import org.apache.axis2.transport.MessageFormatter; import org.apache.axis2.util.MessageProcessorSelector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.OutputStream; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.stream.XMLStreamException; /** * Utility to read/write the Message of a MessageContext * Message Object Format. * * <tt> * Format := Prolog {DataBlocks} EndBlocks * * Prolog := * NAME (UTF) * REVISION (INT) * ACTIVE (BOOL) * [OPTIMIZED (BOOL)] * [OPTIMIZED_CONTENT_TYPE (UTF)] <--- If OPTIMIZED=TRUE * [CHARSET (UTF)] * [NAMESPACE (UTF)] * * DataBlock := * SIZE (INT >0) * DATA (BYTES) * * EndBlocks * SIZE (INT) {0 indicates end -1 indicates failure} * * * </tt> */ public class MessageExternalizeUtils implements ExternalizeConstants { static final Log log = LogFactory.getLog(MessageExternalizeUtils.class); /* * @serial Tracks the revision level of a class to identify changes to the * class definition that are compatible to serialization/externalization. * If a class definition changes, then the serialization/externalization * of the class is affected. * Refer to the writeExternal() and readExternal() methods. */ // supported revision levels, add a new level to manage compatible changes private static final int REVISION_2 = 2; // current revision level of this object private static final int revisionID = REVISION_2; /** * Private Constructor. * This class only supports static methods */ private MessageExternalizeUtils() {} /** * Write out the Message * @param out * @param mc * @param correlationIDString * @param outputFormat * @throws IOException */ public static void writeExternal(ObjectOutput out, MessageContext mc, String correlationIDString, OMOutputFormat outputFormat) throws IOException { if (log.isDebugEnabled()) { log.debug(correlationIDString + ":writeExternal(): start"); } SOAPEnvelope envelope = mc.getEnvelope(); if (envelope == null) { // Case: No envelope out.writeUTF("NULL_ENVELOPE"); out.writeInt(revisionID); out.writeBoolean(EMPTY_OBJECT); // Not Active out.writeInt(0); // EndBlocks if (log.isDebugEnabled()) { log.debug(correlationIDString + ":writeExternal(): end: msg is Empty"); } return; } // Write Prolog String msgClass = envelope.getClass().getName(); out.writeUTF(msgClass); out.writeInt(revisionID); out.writeBoolean(ACTIVE_OBJECT); if (outputFormat.isOptimized()) { out.writeBoolean(true); // Write out the contentType. out.writeUTF(outputFormat.getContentType()); } else { out.writeBoolean(false); } out.writeUTF(outputFormat.getCharSetEncoding()); out.writeUTF(envelope.getNamespace().getNamespaceURI()); if (log.isDebugEnabled()) { log.debug(correlationIDString + ":writeExternal(): " + "optimized=[" + outputFormat.isOptimized() + "] " + "optimizedContentType " + outputFormat.getContentType() + "] " + "charSetEnc=[" + outputFormat.getCharSetEncoding() + "] " + "namespaceURI=[" + envelope.getNamespace().getNamespaceURI() + "]"); } // Write DataBlocks // MessageOutputStream writes out the DataBlocks in chunks // BufferedOutputStream buffers the data to prevent numerous, small blocks MessageOutputStream mos = new MessageOutputStream(out); BufferedOutputStream bos = new BufferedOutputStream(mos); boolean errorOccurred = false; try { // Write out the message using the same logic as the // transport layer. MessageFormatter msgFormatter = MessageProcessorSelector.getMessageFormatter(mc); msgFormatter.writeTo(mc, outputFormat, bos, true); // Preserve the original message } catch (IOException e) { throw e; } catch (Throwable t) { throw AxisFault.makeFault(t); } finally { bos.flush(); bos.close(); } // Write End of Data Blocks if (errorOccurred) { out.writeInt(-1); } else { out.writeInt(0); } if (log.isDebugEnabled()) { log.debug(correlationIDString + ":writeExternal(): end"); } } private static OMXMLParserWrapper getAttachmentsBuilder(MessageContext msgContext, InputStream inStream, String contentTypeString, boolean isSOAP) throws OMException, XMLStreamException, FactoryConfigurationError { Attachments attachments = BuilderUtil.createAttachmentsMap(msgContext, inStream, contentTypeString); String charSetEncoding = BuilderUtil.getCharSetEncoding(attachments.getRootPartContentType()); if ((charSetEncoding == null) || "null".equalsIgnoreCase(charSetEncoding)) { charSetEncoding = MessageContext.UTF_8; } msgContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, charSetEncoding); // Setting the Attachments map to new SwA API msgContext.setAttachmentMap(attachments); if (isSOAP) { if (attachments.getAttachmentSpecType().equals( MTOMConstants.MTOM_TYPE)) { return OMXMLBuilderFactory.createSOAPModelBuilder(attachments); } else { return OMXMLBuilderFactory.createSOAPModelBuilder(attachments.getRootPartInputStream(), charSetEncoding); } } // To handle REST XOP case else { if (attachments.getAttachmentSpecType().equals(MTOMConstants.MTOM_TYPE)) { return OMXMLBuilderFactory.createOMBuilder(StAXParserConfiguration.DEFAULT, attachments); } else { return OMXMLBuilderFactory.createOMBuilder(attachments.getRootPartInputStream(), charSetEncoding); } } } /** * Read the Message * @param in * @param mc * @param correlationIDString * @return * @throws IOException */ public static SOAPEnvelope readExternal(ObjectInput in, MessageContext mc, String correlationIDString) throws IOException, ClassNotFoundException { if (log.isDebugEnabled()) { log.debug(correlationIDString + ":readExternal(): start"); } SOAPEnvelope envelope = null; // Read Prolog // Read the class name and object state String name = in.readUTF(); int revision = in.readInt(); if (log.isDebugEnabled()) { log.debug(correlationIDString + ":readExternal(): name= " + name + " revision= " + revision); } // make sure the object data is in a revision level we can handle if (revision != REVISION_2) { throw new ClassNotFoundException(ExternalizeConstants.UNSUPPORTED_REVID); } boolean gotMsg = in.readBoolean(); if (gotMsg != ACTIVE_OBJECT) { if (log.isDebugEnabled()) { log.debug(correlationIDString + ":readExternal(): end:" + "no message present"); } in.readInt(); // Read end of data blocks return envelope; } // Read optimized, optimized content-type, charset encoding and namespace uri boolean optimized= in.readBoolean(); String optimizedContentType = null; if (optimized) { optimizedContentType = in.readUTF(); } String charSetEnc = in.readUTF(); String namespaceURI = in.readUTF(); if (log.isDebugEnabled()) { log.debug(correlationIDString + ":readExternal(): " + "optimized=[" + optimized + "] " + "optimizedContentType=[" + optimizedContentType + "] " + "charSetEnc=[" + charSetEnc + "] " + "namespaceURI=[" + namespaceURI + "]"); } MessageInputStream mis = new MessageInputStream(in); OMXMLParserWrapper builder = null; try { if (optimized) { boolean isSOAP = true; builder = getAttachmentsBuilder(mc, mis, optimizedContentType, isSOAP); envelope = (SOAPEnvelope) builder.getDocumentElement(); } else { builder = OMXMLBuilderFactory.createSOAPModelBuilder(mis, charSetEnc); envelope = (SOAPEnvelope) builder.getDocumentElement(); } } catch (Exception ex) { // TODO: what to do if can't get the XML stream reader // For now, log the event log.error(correlationIDString + ":readExternal(): Error when deserializing persisted envelope: [" + ex.getClass().getName() + " : " + ex.getLocalizedMessage() + "]", ex); envelope = null; } finally { // Prepare the builder to close the underlying stream builder.detach(); // Close the message input stream. This will ensure that the // underlying stream is advanced past the message. mis.close(); if (log.isDebugEnabled()) { log.debug(correlationIDString + ":readExternal(): end"); } } return envelope; } /** * MessageOutputStream writes DataBlock chunks to the ObjectOutput. */ private static class MessageOutputStream extends OutputStream { ObjectOutput out; boolean isDebug; MessageOutputStream(ObjectOutput out) { this.out = out; isDebug = log.isDebugEnabled(); } public void close() throws IOException { // NOOP: ObjectOutput will be closed externally } public void flush() throws IOException { out.flush(); } /** * Writes a chunk of data to the ObjectOutput */ public void write(byte[] b, int off, int len) throws IOException { if (len > 0) { if (isDebug) { log.debug("Write data chunk with len=" + len); } // Write out the length and the data chunk out.writeInt(len); out.write(b, off, len); } } /** * Writes a chunk of data to the ObjectOutput */ public void write(byte[] b) throws IOException { if (b != null && b.length > 0) { if (isDebug) { log.debug("Write data chunk with size=" + b.length); } // Write out the length and the data chunk out.writeInt(b.length); out.write(b); } } /** * Writes a single byte chunk of data to the ObjectOutput */ public void write(int b) throws IOException { if (isDebug) { log.debug("Write one byte data chunk"); } // Write out the length and the data chunk out.writeInt(1); out.write(b); } } /** * Provides a InputStream interface over ObjectInput. * MessageInputStream controls the reading of the DataBlock chunks * */ private static class MessageInputStream extends InputStream { ObjectInput in; boolean isDebug; int chunkAvail = 0; boolean isEOD = false; /** * Constructor * @param in */ MessageInputStream(ObjectInput in) { this.in = in; isDebug = log.isDebugEnabled(); } /** * Read a single logical byte */ public int read() throws IOException { if (isDebug) { log.debug("invoking read()"); } // Determine how many bytes are left in the current data chunk updateChunkAvail(); int ret = 0; if (isEOD) { ret = -1; } else { chunkAvail--; ret = in.readByte(); } log.debug("returning " + ret); return ret; } /** * Read an array of logical bytes */ public int read(byte[] b, int off, int len) throws IOException { if (isDebug) { log.debug("invoking read with off=" + off + " and len=" + len); } if (isEOD) { if (isDebug) { log.debug("EOD returning -1"); } return -1; } int bytesRead = 0; while ((len >0 && !isEOD)) { // Determine how many bytes are left in the current data chunk updateChunkAvail(); if (!isEOD) { // Read the amount of bytes requested or the number of bytes available in the current chunk int readLength = len < chunkAvail ? len : chunkAvail; int br = in.read(b, off, readLength); if (br < 0) { throw new IOException("End of File encountered"); } // Update state with the number of bytes read off += br; len -= br; chunkAvail -= br; bytesRead += br; } } if (isDebug) { log.debug("bytes read = " + bytesRead); } return bytesRead; } public int read(byte[] b) throws IOException { return read(b, 0, b.length); } public void close() throws IOException { if (isDebug) { log.debug("start close"); } // Keep reading chunks until EOD if (!isEOD) { byte[] tempBuffer = new byte[4 * 1024]; while (!isEOD) { read(tempBuffer); } } if (isDebug) { log.debug("end close"); } } /** * updateChunkAvail updates the chunkAvail field with the * amount of data in the chunk. * @throws IOException */ private void updateChunkAvail() throws IOException { // If there are no more bytes in the current chunk, // read the size of the next datablock if (chunkAvail == 0 && !isEOD) { chunkAvail = in.readInt(); if (isDebug) { log.debug("New DataBlock with size=" + chunkAvail); } if (chunkAvail <= 0) { if (isDebug) { log.debug("End of data"); } isEOD = true; chunkAvail = 0; } } } } }