blob: a233f741301b849ce86fe5b6b546de8f6d5da73c [file] [log] [blame]
/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.xml.internal.ws.client.sei;
import com.sun.xml.internal.ws.api.SOAPVersion;
import com.sun.xml.internal.ws.api.message.Attachment;
import com.sun.xml.internal.ws.api.message.AttachmentSet;
import com.sun.xml.internal.ws.api.message.Message;
import com.sun.xml.internal.ws.api.model.ParameterBinding;
import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
import com.sun.xml.internal.ws.model.ParameterImpl;
import com.sun.xml.internal.ws.model.WrapperParameter;
import com.sun.xml.internal.ws.resources.ServerMessages;
import com.sun.xml.internal.ws.spi.db.RepeatedElementBridge;
import com.sun.xml.internal.ws.spi.db.XMLBridge;
import com.sun.xml.internal.ws.spi.db.DatabindingException;
import com.sun.xml.internal.ws.spi.db.PropertyAccessor;
import com.sun.xml.internal.ws.spi.db.WrapperComposite;
import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
import com.sun.xml.internal.ws.encoding.StringDataContentHandler;
import com.sun.xml.internal.ws.encoding.DataHandlerDataSource;
import javax.activation.DataHandler;
import javax.imageio.ImageIO;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.transform.Source;
import javax.xml.ws.Holder;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.soap.SOAPFaultException;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Reads a response {@link Message}, disassembles it, and moves obtained Java values
* to the expected places.
*
* @author Kohsuke Kawaguchi
* @author Jitendra Kotamraju
*/
public abstract class ResponseBuilder {
/**
* Reads a response {@link Message}, disassembles it, and moves obtained Java values
* to the expected places.
*
* @param reply
* The reply {@link Message} to be de-composed.
* @param args
* The Java arguments given to the SEI method invocation.
* Some parts of the reply message may be set to {@link Holder}s in the arguments.
* @return
* If a part of the reply message is returned as a return value from
* the SEI method, this method returns that value. Otherwise null.
* @throws JAXBException
* if there's an error during unmarshalling the reply message.
* @throws XMLStreamException
* if there's an error during unmarshalling the reply message.
*/
public abstract Object readResponse(Message reply, Object[] args) throws JAXBException, XMLStreamException;
static final class WrappedPartBuilder {
private final XMLBridge bridge;
private final ValueSetter setter;
public WrappedPartBuilder(XMLBridge bridge, ValueSetter setter) {
this.bridge = bridge;
this.setter = setter;
}
final Object readResponse(Object[] args, XMLStreamReader r, AttachmentSet att) throws JAXBException {
Object obj;
AttachmentUnmarshallerImpl au = (att != null)?new AttachmentUnmarshallerImpl(att):null;
if (bridge instanceof RepeatedElementBridge) {
RepeatedElementBridge rbridge = (RepeatedElementBridge)bridge;
ArrayList list = new ArrayList();
QName name = r.getName();
while (r.getEventType()==XMLStreamReader.START_ELEMENT && name.equals(r.getName())) {
list.add(rbridge.unmarshal(r, au));
XMLStreamReaderUtil.toNextTag(r, name);
}
obj = rbridge.collectionHandler().convert(list);
} else {
obj = bridge.unmarshal(r, au);
}
return setter.put(obj,args);
}
}
/**
* {@link ResponseBuilder.PartBuilder} keyed by the element name (inside the wrapper element.)
*/
protected Map<QName,WrappedPartBuilder> wrappedParts = null;
protected QName wrapperName;
protected Object readWrappedResponse(Message msg, Object[] args) throws JAXBException, XMLStreamException {
Object retVal = null;
if (!msg.hasPayload()) {
throw new WebServiceException("No payload. Expecting payload with "+wrapperName+" element");
}
XMLStreamReader reader = msg.readPayload();
XMLStreamReaderUtil.verifyTag(reader,wrapperName);
reader.nextTag();
while(reader.getEventType()==XMLStreamReader.START_ELEMENT) {
// TODO: QName has a performance issue
WrappedPartBuilder part = wrappedParts.get(reader.getName());
if(part==null) {
// no corresponding part found. ignore
XMLStreamReaderUtil.skipElement(reader);
reader.nextTag();
} else {
Object o = part.readResponse(args,reader, msg.getAttachments());
// there's only at most one ResponseBuilder that returns a value.
if(o!=null) {
assert retVal==null;
retVal = o;
}
}
// skip any whitespace
if (reader.getEventType() != XMLStreamConstants.START_ELEMENT &&
reader.getEventType() != XMLStreamConstants.END_ELEMENT) {
XMLStreamReaderUtil.nextElementContent(reader);
}
}
// we are done with the body
reader.close();
XMLStreamReaderFactory.recycle(reader);
return retVal;
}
static final class None extends ResponseBuilder {
private None(){
}
@Override
public Object readResponse(Message msg, Object[] args) {
msg.consume();
return null;
}
}
/**
* The singleton instance that produces null return value.
* Used for operations that doesn't have any output.
*/
public final static ResponseBuilder NONE = new None();
/**
* Returns the 'uninitialized' value for the given type.
*
* <p>
* For primitive types, it's '0', and for reference types, it's null.
*/
@SuppressWarnings("element-type-mismatch")
public static Object getVMUninitializedValue(Type type) {
// if this map returns null, that means the 'type' is a reference type,
// in which case 'null' is the correct null value, so this code is correct.
return primitiveUninitializedValues.get(type);
}
private static final Map<Class,Object> primitiveUninitializedValues = new HashMap<Class, Object>();
static {
Map<Class, Object> m = primitiveUninitializedValues;
m.put(int.class,(int)0);
m.put(char.class,(char)0);
m.put(byte.class,(byte)0);
m.put(short.class,(short)0);
m.put(long.class,(long)0);
m.put(float.class,(float)0);
m.put(double.class,(double)0);
}
/**
* {@link ResponseBuilder} that sets the VM uninitialized value to the type.
*/
public static final class NullSetter extends ResponseBuilder {
private final ValueSetter setter;
private final Object nullValue;
public NullSetter(ValueSetter setter, Object nullValue){
assert setter!=null;
this.nullValue = nullValue;
this.setter = setter;
}
@Override
public Object readResponse(Message msg, Object[] args) {
return setter.put(nullValue, args);
}
}
/**
* {@link ResponseBuilder} that is a composition of multiple
* {@link ResponseBuilder}s.
*
* <p>
* Sometimes we need to look at multiple parts of the reply message
* (say, two header params, one body param, and three attachments, etc.)
* and that's when this object is used to combine multiple {@link ResponseBuilder}s
* (that each responsible for handling one part).
*
* <p>
* The model guarantees that only at most one {@link ResponseBuilder} will
* return a value as a return value (and everything else has to go to
* {@link Holder}s.)
*/
public static final class Composite extends ResponseBuilder {
private final ResponseBuilder[] builders;
public Composite(ResponseBuilder... builders) {
this.builders = builders;
}
public Composite(Collection<? extends ResponseBuilder> builders) {
this(builders.toArray(new ResponseBuilder[builders.size()]));
}
@Override
public Object readResponse(Message msg, Object[] args) throws JAXBException, XMLStreamException {
Object retVal = null;
for (ResponseBuilder builder : builders) {
Object r = builder.readResponse(msg,args);
// there's only at most one ResponseBuilder that returns a value.
if(r!=null) {
assert retVal==null;
retVal = r;
}
}
return retVal;
}
}
/**
* Reads an Attachment into a Java parameter.
*/
public static abstract class AttachmentBuilder extends ResponseBuilder {
protected final ValueSetter setter;
protected final ParameterImpl param;
private final String pname;
private final String pname1;
AttachmentBuilder(ParameterImpl param, ValueSetter setter) {
this.setter = setter;
this.param = param;
this.pname = param.getPartName();
this.pname1 = "<"+pname;
}
/**
* Creates an AttachmentBuilder based on the parameter type
*
* @param param
* runtime Parameter that abstracts the annotated java parameter
* @param setter
* specifies how the obtained value is set into the argument. Takes
* care of Holder arguments.
*/
public static ResponseBuilder createAttachmentBuilder(ParameterImpl param, ValueSetter setter) {
Class type = (Class)param.getTypeInfo().type;
if (DataHandler.class.isAssignableFrom(type)) {
return new DataHandlerBuilder(param, setter);
} else if (byte[].class==type) {
return new ByteArrayBuilder(param, setter);
} else if(Source.class.isAssignableFrom(type)) {
return new SourceBuilder(param, setter);
} else if(Image.class.isAssignableFrom(type)) {
return new ImageBuilder(param, setter);
} else if(InputStream.class==type) {
return new InputStreamBuilder(param, setter);
} else if(isXMLMimeType(param.getBinding().getMimeType())) {
return new JAXBBuilder(param, setter);
} else if(String.class.isAssignableFrom(type)) {
return new StringBuilder(param, setter);
} else {
throw new UnsupportedOperationException("Unexpected Attachment type ="+type);
}
}
@Override
public Object readResponse(Message msg, Object[] args) throws JAXBException, XMLStreamException {
// TODO not to loop
for (Attachment att : msg.getAttachments()) {
String part = getWSDLPartName(att);
if (part == null) {
continue;
}
if(part.equals(pname) || part.equals(pname1)){
return mapAttachment(att, args);
}
}
return null;
}
abstract Object mapAttachment(Attachment att, Object[] args) throws JAXBException;
}
private static final class DataHandlerBuilder extends AttachmentBuilder {
DataHandlerBuilder(ParameterImpl param, ValueSetter setter) {
super(param, setter);
}
@Override
Object mapAttachment(Attachment att, Object[] args) {
return setter.put(att.asDataHandler(), args);
}
}
private static final class StringBuilder extends AttachmentBuilder {
StringBuilder(ParameterImpl param, ValueSetter setter) {
super(param, setter);
}
@Override
Object mapAttachment(Attachment att, Object[] args) {
att.getContentType();
StringDataContentHandler sdh = new StringDataContentHandler();
try {
String str = (String)sdh.getContent(new DataHandlerDataSource(att.asDataHandler()));
return setter.put(str, args);
} catch(Exception e) {
throw new WebServiceException(e);
}
}
}
private static final class ByteArrayBuilder extends AttachmentBuilder {
ByteArrayBuilder(ParameterImpl param, ValueSetter setter) {
super(param, setter);
}
@Override
Object mapAttachment(Attachment att, Object[] args) {
return setter.put(att.asByteArray(), args);
}
}
private static final class SourceBuilder extends AttachmentBuilder {
SourceBuilder(ParameterImpl param, ValueSetter setter) {
super(param, setter);
}
@Override
Object mapAttachment(Attachment att, Object[] args) {
return setter.put(att.asSource(), args);
}
}
private static final class ImageBuilder extends AttachmentBuilder {
ImageBuilder(ParameterImpl param, ValueSetter setter) {
super(param, setter);
}
@Override
Object mapAttachment(Attachment att, Object[] args) {
Image image;
InputStream is = null;
try {
is = att.asInputStream();
image = ImageIO.read(is);
} catch(IOException ioe) {
throw new WebServiceException(ioe);
} finally {
if (is != null) {
try {
is.close();
} catch(IOException ioe) {
throw new WebServiceException(ioe);
}
}
}
return setter.put(image, args);
}
}
private static final class InputStreamBuilder extends AttachmentBuilder {
InputStreamBuilder(ParameterImpl param, ValueSetter setter) {
super(param, setter);
}
@Override
Object mapAttachment(Attachment att, Object[] args) {
return setter.put(att.asInputStream(), args);
}
}
private static final class JAXBBuilder extends AttachmentBuilder {
JAXBBuilder(ParameterImpl param, ValueSetter setter) {
super(param, setter);
}
@Override
Object mapAttachment(Attachment att, Object[] args) throws JAXBException {
Object obj = param.getXMLBridge().unmarshal(att.asInputStream());
return setter.put(obj, args);
}
}
/**
* 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.
*/
@SuppressWarnings("FinalStaticMethod")
public static final String getWSDLPartName(com.sun.xml.internal.ws.api.message.Attachment att){
String cId = att.getContentId();
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 WebServiceException(e);
}
}
/**
* Reads a header into a JAXB object.
*/
public static final class Header extends ResponseBuilder {
private final XMLBridge<?> bridge;
private final ValueSetter setter;
private final QName headerName;
private final SOAPVersion soapVersion;
/**
* @param soapVersion
* SOAP1.1 or 1.2
* @param name
* The name of the header element.
* @param bridge
* specifies how to unmarshal a header into a JAXB object.
* @param setter
* specifies how the obtained value is returned to the client.
*/
public Header(SOAPVersion soapVersion, QName name, XMLBridge<?> bridge, ValueSetter setter) {
this.soapVersion = soapVersion;
this.headerName = name;
this.bridge = bridge;
this.setter = setter;
}
public Header(SOAPVersion soapVersion, ParameterImpl param, ValueSetter setter) {
this(soapVersion,
param.getTypeInfo().tagName,
param.getXMLBridge(),
setter);
assert param.getOutBinding()== ParameterBinding.HEADER;
}
private SOAPFaultException createDuplicateHeaderException() {
try {
SOAPFault fault = soapVersion.getSOAPFactory().createFault();
fault.setFaultCode(soapVersion.faultCodeServer);
fault.setFaultString(ServerMessages.DUPLICATE_PORT_KNOWN_HEADER(headerName));
return new SOAPFaultException(fault);
} catch(SOAPException e) {
throw new WebServiceException(e);
}
}
@Override
public Object readResponse(Message msg, Object[] args) throws JAXBException {
com.sun.xml.internal.ws.api.message.Header header = null;
Iterator<com.sun.xml.internal.ws.api.message.Header> it =
msg.getHeaders().getHeaders(headerName,true);
if (it.hasNext()) {
header = it.next();
if (it.hasNext()) {
throw createDuplicateHeaderException();
}
}
if(header!=null)
return setter.put( header.readAsJAXB(bridge), args );
else
// header not found.
return null;
}
}
/**
* Reads the whole payload into a single JAXB bean.
*/
public static final class Body extends ResponseBuilder {
private final XMLBridge<?> bridge;
private final ValueSetter setter;
/**
* @param bridge
* specifies how to unmarshal the payload into a JAXB object.
* @param setter
* specifies how the obtained value is returned to the client.
*/
public Body(XMLBridge<?> bridge, ValueSetter setter) {
this.bridge = bridge;
this.setter = setter;
}
@Override
public Object readResponse(Message msg, Object[] args) throws JAXBException {
return setter.put( msg.readPayloadAsJAXB(bridge), args );
}
}
/**
* Treats a payload as multiple parts wrapped into one element,
* and processes all such wrapped parts.
*/
public static final class DocLit extends ResponseBuilder {
/**
* {@link PartBuilder} keyed by the element name (inside the wrapper element.)
*/
private final PartBuilder[] parts;
private final XMLBridge wrapper;
private boolean dynamicWrapper;
public DocLit(WrapperParameter wp, ValueSetterFactory setterFactory) {
wrapperName = wp.getName();
wrapper = wp.getXMLBridge();
Class wrapperType = (Class) wrapper.getTypeInfo().type;
dynamicWrapper = WrapperComposite.class.equals(wrapperType);
List<PartBuilder> tempParts = new ArrayList<PartBuilder>();
List<ParameterImpl> children = wp.getWrapperChildren();
for (ParameterImpl p : children) {
if(p.isIN())
continue;
QName name = p.getName();
if (dynamicWrapper) {
if (wrappedParts == null) wrappedParts = new HashMap<QName,WrappedPartBuilder>();
XMLBridge xmlBridge = p.getInlinedRepeatedElementBridge();
if (xmlBridge == null) xmlBridge = p.getXMLBridge();
wrappedParts.put( p.getName(), new WrappedPartBuilder(xmlBridge, setterFactory.get(p)));
} else {
try {
tempParts.add(new PartBuilder(
wp.getOwner().getBindingContext().getElementPropertyAccessor(
wrapperType,
name.getNamespaceURI(),
p.getName().getLocalPart()),
setterFactory.get(p)
));
// wrapper parameter itself always bind to body, and
// so do all its children
assert p.getBinding()== ParameterBinding.BODY;
} catch (JAXBException e) {
throw new WebServiceException( // TODO: i18n
wrapperType+" do not have a property of the name "+name,e);
}
}
}
this.parts = tempParts.toArray(new PartBuilder[tempParts.size()]);
}
@Override
public Object readResponse(Message msg, Object[] args) throws JAXBException, XMLStreamException {
if (dynamicWrapper) return readWrappedResponse(msg, args);
Object retVal = null;
if (parts.length>0) {
if (!msg.hasPayload()) {
throw new WebServiceException("No payload. Expecting payload with "+wrapperName+" element");
}
XMLStreamReader reader = msg.readPayload();
XMLStreamReaderUtil.verifyTag(reader,wrapperName);
Object wrapperBean = wrapper.unmarshal(reader, (msg.getAttachments() != null) ?
new AttachmentUnmarshallerImpl(msg.getAttachments()): null);
try {
for (PartBuilder part : parts) {
Object o = part.readResponse(args,wrapperBean);
// there's only at most one ResponseBuilder that returns a value.
// TODO: reorder parts so that the return value comes at the end.
if(o!=null) {
assert retVal==null;
retVal = o;
}
}
} catch (DatabindingException e) {
// this can happen when the set method throw a checked exception or something like that
throw new WebServiceException(e); // TODO:i18n
}
// we are done with the body
reader.close();
XMLStreamReaderFactory.recycle(reader);
} else {
msg.consume();
}
return retVal;
}
/**
* Unmarshals each wrapped part into a JAXB object and moves it
* to the expected place.
*/
static final class PartBuilder {
private final PropertyAccessor accessor;
private final ValueSetter setter;
/**
* @param accessor
* specifies which portion of the wrapper bean to obtain the value from.
* @param setter
* specifies how the obtained value is returned to the client.
*/
public PartBuilder(PropertyAccessor accessor, ValueSetter setter) {
this.accessor = accessor;
this.setter = setter;
assert accessor!=null && setter!=null;
}
final Object readResponse( Object[] args, Object wrapperBean ) {
Object obj = accessor.get(wrapperBean);
return setter.put(obj,args);
}
}
}
/**
* Treats a payload as multiple parts wrapped into one element,
* and processes all such wrapped parts.
*/
public static final class RpcLit extends ResponseBuilder {
public RpcLit(WrapperParameter wp, ValueSetterFactory setterFactory) {
assert wp.getTypeInfo().type== WrapperComposite.class;
wrapperName = wp.getName();
wrappedParts = new HashMap<QName,WrappedPartBuilder>();
List<ParameterImpl> children = wp.getWrapperChildren();
for (ParameterImpl p : children) {
wrappedParts.put( p.getName(), new WrappedPartBuilder(
p.getXMLBridge(), setterFactory.get(p)
));
// wrapper parameter itself always bind to body, and
// so do all its children
assert p.getBinding()== ParameterBinding.BODY;
}
}
@Override
public Object readResponse(Message msg, Object[] args) throws JAXBException, XMLStreamException {
return readWrappedResponse(msg, args);
}
}
private static boolean isXMLMimeType(String mimeType){
return mimeType.equals("text/xml") || mimeType.equals("application/xml");
}
}