blob: 951f2cae6c5f37eedeac47b490c576d373e78a80 [file] [log] [blame]
/*
* Portions Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.xml.internal.ws.encoding.soap.internal;
import com.sun.xml.internal.bind.api.BridgeContext;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import com.sun.xml.internal.ws.encoding.jaxb.JAXBBridgeInfo;
import com.sun.xml.internal.ws.encoding.soap.SerializationException;
import com.sun.xml.internal.ws.server.RuntimeContext;
import com.sun.xml.internal.ws.util.ASCIIUtility;
import com.sun.xml.internal.ws.util.ByteArrayDataSource;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.imageio.ImageIO;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.WebServiceException;
import java.awt.Image;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
/**
* Attachment of {@link InternalMessage}.
*
* <p>
* The key idea behind this class is to hide the actual data representation.
* The producer of the object may choose the best format it wants, and then
* various accessor methods provide the rest of the stack to chose the format
* it wants.
*
* <p>
* When receiving from a network, this allows an {@link AttachmentBlock} object
* to be constructed without actually even parsing the attachment. When
* sending to a network, this allows the conversion to the byte image to happen
* lazily, and directly to the network.
*
* <p>
* Even though most of the data access methods have default implementation,
* Implementation classes of this class should override them
* so that they can run faster, whenever possible. In particular, the default
* implementation of {@link #asInputStream()} and {@link #writeTo(OutputStream)}
* has a circular dependency, so at least one must be overridden.
*
* TODO:
* in the performance critical path of mapping an attachment to a Java type,
* most of the type the Java type to which it binds to is known in advance.
* for this reason, it's better to prepare an 'accessor' object for each
* kind of conversion. In that way we can avoid the computation of this
* in the pritical path, which is redundant.
*
* @author Kohsuke Kawaguchi
*/
public abstract class AttachmentBlock {
public static AttachmentBlock fromDataHandler(String cid,DataHandler dh) {
return new DataHandlerImpl(cid,dh);
}
public static AttachmentBlock fromSAAJ(AttachmentPart part) {
return new SAAJImpl(part);
}
public static AttachmentBlock fromByteArray(String cid,byte[] data, int start, int len, String mimeType ) {
return new ByteArrayImpl(cid,data,start,len,mimeType);
}
public static AttachmentBlock fromByteArray(String cid,byte[] data, String mimeType) {
return new ByteArrayImpl(cid,data,0,data.length,mimeType);
}
public static AttachmentBlock fromJAXB(String cid, JAXBBridgeInfo bridgeInfo, RuntimeContext rtContext, String mimeType) {
return new JAXBImpl(cid,bridgeInfo,rtContext,mimeType);
}
/**
* No derived class outside this class.
*/
private AttachmentBlock() {}
/**
* Content ID of the attachment. Uniquely identifies an attachment.
*/
public abstract String getId();
/**
* Gets the WSDL part name of this attachment.
*
* <p>
* According to WSI AP 1.0
* <PRE>
* 3.8 Value-space of Content-Id Header
* Definition: content-id part encoding
* The "content-id part encoding" consists of the concatenation of:
* The value of the name attribute of the wsdl:part element referenced by the mime:content, in which characters disallowed in content-id headers (non-ASCII characters as represented by code points above 0x7F) are escaped as follows:
* o Each disallowed character is converted to UTF-8 as one or more bytes.
* o Any bytes corresponding to a disallowed character are escaped with the URI escaping mechanism (that is, converted to %HH, where HH is the hexadecimal notation of the byte value).
* o The original character is replaced by the resulting character sequence.
* The character '=' (0x3D).
* A globally unique value such as a UUID.
* The character '@' (0x40).
* A valid domain name under the authority of the entity constructing the message.
* </PRE>
*
* So a wsdl:part fooPart will be encoded as:
* <fooPart=somereallybignumberlikeauuid@example.com>
*
* @return null
* if the parsing fails.
*/
public final String getWSDLPartName(){
String cId = getId();
int index = cId.lastIndexOf('@', cId.length());
if(index == -1){
return null;
}
String localPart = cId.substring(0, index);
index = localPart.lastIndexOf('=', localPart.length());
if(index == -1){
return null;
}
try {
return java.net.URLDecoder.decode(localPart.substring(0, index), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new SerializationException(e);
}
}
/**
* Gets the MIME content-type of this attachment.
*/
public abstract String getContentType();
/**
* Gets the attachment as an exact-length byte array.
*/
// not so fast but useful default implementation
public byte[] asByteArray() {
try {
return ASCIIUtility.getBytes(asInputStream());
} catch (IOException e) {
throw new WebServiceException(e);
}
}
/**
* Gets the attachment as a {@link DataHandler}.
*/
public abstract DataHandler asDataHandler();
/**
* Gets the attachment as a {@link Source}.
* Note that there's no guarantee that the attachment is actually an XML.
*/
public Source asSource() {
return new StreamSource(asInputStream());
}
/**
* Obtains this attachment as an {@link InputStream}.
*/
// not-so-efficient but useful default implementation.
public InputStream asInputStream() {
ByteOutputStream bos = new ByteOutputStream();
try {
writeTo(bos);
} catch (IOException e) {
throw new WebServiceException(e);
}
return bos.newInputStream();
}
/**
* Deserializes this attachment by using JAXB into {@link JAXBBridgeInfo}.
*
* TODO: this abstraction is wrong.
*/
public final void deserialize(BridgeContext bc, JAXBBridgeInfo bi) {
bi.deserialize(asInputStream(),bc);
}
/**
* Adds this attachment as an {@link AttachmentPart} into the given {@link SOAPMessage}.
*/
// not so fast but useful default
public void addTo(SOAPMessage msg) throws SOAPException {
AttachmentPart part = msg.createAttachmentPart(asDataHandler());
part.setContentId(getId());
//it may be safe to say the encoding is binary meaning the bytes are not subjected any
//specific encoding.
part.setMimeHeader("Content-transfer-encoding", "binary");
msg.addAttachmentPart(part);
}
/**
* Writes the contents of the attachment into the given stream.
*/
// not so fast but useful default
public void writeTo(OutputStream os) throws IOException {
ASCIIUtility.copyStream(asInputStream(),os);
}
/**
* Deserializes this attachment into an {@link Image}.
*
* @return null if the decoding fails.
*/
public final Image asImage() throws IOException {
// technically we should check the MIME type here, but
// normally images can be content-sniffed.
// so the MIME type check will only make us slower and draconian, both of which
// JAXB 2.0 isn't interested.
return ImageIO.read(asInputStream());
}
/**
* {@link AttachmentBlock} stored as SAAJ {@link AttachmentPart}.
*/
private static final class SAAJImpl extends AttachmentBlock {
private final AttachmentPart ap;
public SAAJImpl(AttachmentPart part) {
this.ap = part;
}
public String getId() {
return ap.getContentId();
}
public String getContentType() {
return ap.getContentType();
}
public byte[] asByteArray() {
try {
return ap.getRawContentBytes();
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
public DataHandler asDataHandler() {
try {
return ap.getDataHandler();
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
public Source asSource() {
try {
return new StreamSource(ap.getRawContent());
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
public InputStream asInputStream() {
try {
return ap.getRawContent();
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
public void addTo(SOAPMessage msg) {
msg.addAttachmentPart(ap);
}
}
/**
* {@link AttachmentBlock} stored as a {@link DataHandler}.
*/
private static final class DataHandlerImpl extends AttachmentBlock {
private final String cid;
private final DataHandler dh;
public DataHandlerImpl(String cid, DataHandler dh) {
this.cid = cid;
this.dh = dh;
}
public String getId() {
return cid;
}
public String getContentType() {
return dh.getContentType();
}
public DataHandler asDataHandler() {
return dh;
}
public InputStream asInputStream() {
try {
return dh.getInputStream();
} catch (IOException e) {
throw new WebServiceException(e);
}
}
}
private static final class ByteArrayImpl extends AttachmentBlock {
private final String cid;
private byte[] data;
private int start;
private int len;
private final String mimeType;
public ByteArrayImpl(String cid, byte[] data, int start, int len, String mimeType) {
this.cid = cid;
this.data = data;
this.start = start;
this.len = len;
this.mimeType = mimeType;
}
public String getId() {
return cid;
}
public String getContentType() {
return mimeType;
}
public byte[] asByteArray() {
if(start!=0 || len!=data.length) {
// if our buffer isn't exact, switch to the exact one
byte[] exact = new byte[len];
System.arraycopy(data,start,exact,0,len);
start = 0;
data = exact;
}
return data;
}
public DataHandler asDataHandler() {
return new DataHandler(new ByteArrayDataSource(data,start,len,getContentType()));
}
public InputStream asInputStream() {
return new ByteArrayInputStream(data,start,len);
}
public void addTo(SOAPMessage msg) throws SOAPException {
AttachmentPart part = msg.createAttachmentPart();
part.setRawContentBytes(data,start,len,getContentType());
part.setContentId(getId());
msg.addAttachmentPart(part);
}
public void writeTo(OutputStream os) throws IOException {
os.write(data,start,len);
}
}
/**
* {@link AttachmentPart} that stores the value as a JAXB object.
*
* TODO: if it's common for an attahchment to be written more than once,
* it's better to cache the marshalled result, as it is expensive operation.
*/
private static final class JAXBImpl extends AttachmentBlock implements DataSource {
private final String id;
private final JAXBBridgeInfo bridgeInfo;
private final RuntimeContext rtContext;
private final String type;
public JAXBImpl(String id, JAXBBridgeInfo bridgeInfo, RuntimeContext rtContext, String type) {
this.id = id;
this.bridgeInfo = bridgeInfo;
this.rtContext = rtContext;
this.type = type;
}
public String getId() {
return id;
}
public String getContentType() {
return type;
}
public DataHandler asDataHandler() {
return new DataHandler(this);
}
public InputStream getInputStream() {
return asInputStream();
}
public String getName() {
return null;
}
public OutputStream getOutputStream() {
throw new UnsupportedOperationException();
}
public void writeTo(OutputStream os) {
bridgeInfo.serialize(rtContext.getBridgeContext(),os,null);
}
}
}