blob: 99be383893f12dbd4c1ec2a439bbe96cbfcd412d [file] [log] [blame]
/*
* 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.builder;
import org.apache.axiom.attachments.Attachments;
import org.apache.axiom.attachments.lifecycle.LifecycleManager;
import org.apache.axiom.attachments.lifecycle.impl.LifecycleManagerImpl;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.OMXMLBuilderFactory;
import org.apache.axiom.om.OMXMLParserWrapper;
import org.apache.axiom.om.util.StAXParserConfiguration;
import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axiom.soap.SOAP12Constants;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPConstants;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.SOAPModelBuilder;
import org.apache.axiom.soap.SOAPProcessingException;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.deployment.DeploymentConstants;
import org.apache.axis2.description.AxisMessage;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.java.security.AccessController;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.util.JavaUtils;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.axis2.util.MultipleEntryHashMap;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.commons.schema.*;
import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackInputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Map;
public class BuilderUtil {
private static final Log log = LogFactory.getLog(BuilderUtil.class);
/**
* @deprecated
*/
public static final int BOM_SIZE = 4;
public static SOAPEnvelope buildsoapMessage(MessageContext messageContext,
MultipleEntryHashMap requestParameterMap,
SOAPFactory soapFactory) throws AxisFault {
SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();
SOAPBody body = soapEnvelope.getBody();
XmlSchemaElement xmlSchemaElement;
AxisOperation axisOperation = messageContext.getAxisOperation();
if (axisOperation != null) {
AxisMessage axisMessage =
axisOperation.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
xmlSchemaElement = axisMessage.getSchemaElement();
if (xmlSchemaElement == null) {
OMElement bodyFirstChild =
soapFactory
.createOMElement(messageContext.getAxisOperation().getName(), body);
// if there is no schema its piece of cake !! add these to the soap body in any order you like.
// Note : if there are parameters in the path of the URL, there is no way this can add them
// to the message.
createSOAPMessageWithoutSchema(soapFactory, bodyFirstChild, requestParameterMap);
} else {
// first get the target namespace from the schema and the wrapping element.
// create an OMElement out of those information. We are going to extract parameters from
// url, create OMElements and add them as children to this wrapping element.
String targetNamespace = xmlSchemaElement.getQName().getNamespaceURI();
QName bodyFirstChildQName;
if (targetNamespace != null && !"".equals(targetNamespace)) {
bodyFirstChildQName = new QName(targetNamespace, xmlSchemaElement.getName());
} else {
bodyFirstChildQName = new QName(xmlSchemaElement.getName());
}
OMElement bodyFirstChild = soapFactory.createOMElement(bodyFirstChildQName, body);
// Schema should adhere to the IRI style in this. So assume IRI style and dive in to
// schema
XmlSchemaType schemaType = xmlSchemaElement.getSchemaType();
if (schemaType instanceof XmlSchemaComplexType) {
XmlSchemaComplexType complexType = ((XmlSchemaComplexType)schemaType);
XmlSchemaParticle particle = complexType.getParticle();
if (particle instanceof XmlSchemaSequence || particle instanceof XmlSchemaAll) {
if (particle instanceof XmlSchemaSequence) {
XmlSchemaSequence sequence = (XmlSchemaSequence) particle;
// now we need to know some information from the binding operation.
for (XmlSchemaSequenceMember sequenceMember : sequence.getItems()) {
XmlSchemaElement innerElement = (XmlSchemaElement) sequenceMember;
QName qName = innerElement.getQName();
// ignoring the elements without proper type and minoccurs zero
if ((innerElement.getSchemaTypeName() == null) &&
(innerElement.getMinOccurs() == 0)) {
continue;
}
if (qName == null && innerElement.getSchemaTypeName()
.equals(org.apache.ws.commons.schema.constants.Constants.XSD_ANYTYPE)) {
createSOAPMessageWithoutSchema(soapFactory, bodyFirstChild,
requestParameterMap);
break;
}
checkMinOccurs(innerElement, qName, soapFactory, requestParameterMap,
bodyFirstChild);
}
} else {
XmlSchemaAll sequence = (XmlSchemaAll) particle;
// now we need to know some information from the binding operation.
for (XmlSchemaAllMember sequenceMember : sequence.getItems()) {
XmlSchemaElement innerElement = (XmlSchemaElement) sequenceMember;
QName qName = innerElement.getQName();
// ignoring the elements without proper type and minoccurs zero
if ((innerElement.getSchemaTypeName() == null) &&
(innerElement.getMinOccurs() == 0)) {
continue;
}
if (qName == null && innerElement.getSchemaTypeName()
.equals(org.apache.ws.commons.schema.constants.Constants.XSD_ANYTYPE)) {
createSOAPMessageWithoutSchema(soapFactory, bodyFirstChild,
requestParameterMap);
break;
}
checkMinOccurs(innerElement, qName, soapFactory, requestParameterMap,
bodyFirstChild);
}
}
}
}else {
throw new AxisFault(xmlSchemaElement.getName() + " message element in schema should follow the " +
"(Internationalized Resource Identifier)IRI ) style to process request in REST style");
}
}
}
return soapEnvelope;
}
private static void checkMinOccurs(XmlSchemaElement innerElement, QName qName,
SOAPFactory soapFactory,
MultipleEntryHashMap requestParameterMap,
OMElement bodyFirstChild)
throws AxisFault {
long minOccurs = innerElement.getMinOccurs();
boolean nillable = innerElement.isNillable();
String name =
qName != null ? qName.getLocalPart() : innerElement.getName();
Object value;
OMNamespace ns = (qName == null ||
qName.getNamespaceURI() == null
|| qName.getNamespaceURI().length() == 0) ?
null : soapFactory.createOMNamespace(
qName.getNamespaceURI(), null);
// FIXME changed
while ((value = requestParameterMap.get(name)) != null) {
addRequestParameter(soapFactory,
bodyFirstChild, ns, name, value);
minOccurs--;
}
if (minOccurs > 0) {
if (nillable) {
OMNamespace xsi = soapFactory.createOMNamespace(
Constants.URI_DEFAULT_SCHEMA_XSI,
Constants.NS_PREFIX_SCHEMA_XSI);
OMAttribute omAttribute =
soapFactory.createOMAttribute("nil", xsi, "true");
soapFactory.createOMElement(name, ns,
bodyFirstChild)
.addAttribute(omAttribute);
} else {
throw new AxisFault("Required element " + qName +
" defined in the schema can not be" +
" found in the request");
}
}
}
public static void createSOAPMessageWithoutSchema(SOAPFactory soapFactory,
OMElement bodyFirstChild,
MultipleEntryHashMap requestParameterMap) {
// first add the parameters in the URL
if (requestParameterMap != null) {
for (Object o : requestParameterMap.keySet()) {
String key = (String) o;
Object value;
while ((value = requestParameterMap.get(key)) != null) {
addRequestParameter(soapFactory, bodyFirstChild, null, key, value);
}
}
}
}
private static void addRequestParameter(SOAPFactory soapFactory,
OMElement bodyFirstChild,
OMNamespace ns,
String key,
Object parameter) {
if (parameter instanceof DataHandler) {
DataHandler dataHandler = (DataHandler)parameter;
OMText dataText = bodyFirstChild.getOMFactory().createOMText(
dataHandler, true);
soapFactory.createOMElement(key, ns, bodyFirstChild).addChild(
dataText);
} else {
String textValue = parameter.toString();
soapFactory.createOMElement(key, ns, bodyFirstChild).setText(
textValue);
}
}
/**
* Create a builder suitable for an XML message. This method uses
* {@link StAXParserConfiguration#SOAP} to disallow document type declarations (that potentially
* reference external entities).
*
* @param in
* the input stream containing the plain XML message
* @param encoding
* the charset encoding of the message or <code>null</code> if the parser should
* determine the charset encoding
* @return the builder
*/
public static OMXMLParserWrapper createPOXBuilder(InputStream in, String encoding) {
return OMXMLBuilderFactory.createOMBuilder(StAXParserConfiguration.SOAP, in, encoding);
}
/**
* @deprecated Instead of using this method, you should probably pass the {@link InputStream}
* directly to the XML parser. If the stream is not XML, you shouldn't be using this
* method anyway.
*/
public static Reader getReader(final InputStream is, final String charSetEncoding)
throws IOException {
final PushbackInputStream is2 = getPushbackInputStream(is);
final String encoding = getCharSetEncoding(is2, charSetEncoding);
InputStreamReader inputStreamReader;
try {
inputStreamReader = (InputStreamReader)AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws UnsupportedEncodingException {
return new InputStreamReader(is2, encoding);
}
}
);
} catch (PrivilegedActionException e) {
throw (UnsupportedEncodingException)e.getException();
}
return new BufferedReader(inputStreamReader);
}
/**
* @deprecated If you need a {@link PushbackInputStream} just construct one (with the
* appropriate size).
*/
public static PushbackInputStream getPushbackInputStream(InputStream is) {
return new PushbackInputStream(is, BOM_SIZE);
}
/**
* @deprecated It's the role of the XML parser to determine the charset encoding and/or byte
* order using the algorithm described in the "Autodetection of Character Encodings"
* appendix of the XML spec. If you need this method, then something is wrong:
* probably you are using a {@link Reader} where you should use an
* {@link InputStream}.
*/
public static String getCharSetEncoding(PushbackInputStream is2, String defaultEncoding)
throws IOException {
String encoding;
byte bom[] = new byte[BOM_SIZE];
int n, unread;
n = is2.read(bom, 0, bom.length);
if ((bom[0] == (byte)0xEF) && (bom[1] == (byte)0xBB) && (bom[2] == (byte)0xBF)) {
encoding = "UTF-8";
if (log.isDebugEnabled()) {
log.debug("char set encoding set from BOM =" + encoding);
}
unread = n - 3;
} else if ((bom[0] == (byte)0xFE) && (bom[1] == (byte)0xFF)) {
encoding = "UTF-16BE";
if (log.isDebugEnabled()) {
log.debug("char set encoding set from BOM =" + encoding);
}
unread = n - 2;
} else if ((bom[0] == (byte)0xFF) && (bom[1] == (byte)0xFE)) {
encoding = "UTF-16LE";
if (log.isDebugEnabled()) {
log.debug("char set encoding set from BOM =" + encoding);
}
unread = n - 2;
} else if ((bom[0] == (byte)0x00) && (bom[1] == (byte)0x00) && (bom[2] == (byte)0xFE)
&& (bom[3] == (byte)0xFF)) {
encoding = "UTF-32BE";
if (log.isDebugEnabled()) {
log.debug("char set encoding set from BOM =" + encoding);
}
unread = n - 4;
} else if ((bom[0] == (byte)0xFF) && (bom[1] == (byte)0xFE) && (bom[2] == (byte)0x00)
&& (bom[3] == (byte)0x00)) {
encoding = "UTF-32LE";
if (log.isDebugEnabled()) {
log.debug("char set encoding set from BOM =" + encoding);
}
unread = n - 4;
} else {
// Unicode BOM mark not found, unread all bytes
encoding = defaultEncoding;
if (log.isDebugEnabled()) {
log.debug("char set encoding set from default =" + encoding);
}
unread = n;
}
if (unread > 0) {
is2.unread(bom, (n - unread), unread);
}
return encoding;
}
public static String getEnvelopeNamespace(String contentType) {
String soapNS = SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI;
if (contentType != null) {
if (contentType.indexOf(SOAP12Constants.SOAP_12_CONTENT_TYPE) > -1) {
// it is SOAP 1.2
soapNS = SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI;
} else if (contentType.indexOf(SOAP11Constants.SOAP_11_CONTENT_TYPE) > -1) {
// SOAP 1.1
soapNS = SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI;
}
}
return soapNS;
}
/**
* Extracts and returns the character set encoding from the Content-type header
* <p/>
* Example: "Content-Type: text/xml; charset=utf-8" would return "utf-8"
*
* @param contentType a content-type (from HTTP or MIME, for instance)
* @return the character set encoding if found, or MessageContext.DEFAULT_CHAR_SET_ENCODING
*/
public static String getCharSetEncoding(String contentType) {
if (log.isDebugEnabled()) {
log.debug("Input contentType (" + contentType + ")");
}
if (contentType == null) {
// Using the default UTF-8
if (log.isDebugEnabled()) {
log.debug("CharSetEncoding defaulted (" + MessageContext.DEFAULT_CHAR_SET_ENCODING +
")");
}
return MessageContext.DEFAULT_CHAR_SET_ENCODING;
}
int index = contentType.indexOf(HTTPConstants.CHAR_SET_ENCODING);
if (index == -1) { // Charset encoding not found in the content-type header
// Using the default UTF-8
if (log.isDebugEnabled()) {
log.debug("CharSetEncoding defaulted (" + MessageContext.DEFAULT_CHAR_SET_ENCODING +
")");
}
return MessageContext.DEFAULT_CHAR_SET_ENCODING;
}
// If there are spaces around the '=' sign
int indexOfEq = contentType.indexOf("=", index);
// There can be situations where "charset" is not the last parameter of the Content-Type header
int indexOfSemiColon = contentType.indexOf(";", indexOfEq);
String value;
if (indexOfSemiColon > 0) {
value = (contentType.substring(indexOfEq + 1, indexOfSemiColon));
} else {
value = (contentType.substring(indexOfEq + 1, contentType.length())).trim();
}
// There might be "" around the value - if so remove them
if (value.indexOf('\"') != -1) {
value = value.replaceAll("\"", "");
}
value = value.trim();
if (log.isDebugEnabled()) {
log.debug("CharSetEncoding from content-type (" + value + ")");
}
return value;
}
public static Attachments createAttachmentsMap(MessageContext msgContext,
InputStream inStream,
String contentTypeString) {
boolean fileCacheForAttachments = isAttachmentsCacheEnabled(msgContext);
String attachmentRepoDir = null;
String attachmentSizeThreshold = null;
if (fileCacheForAttachments) {
Object attachmentRepoDirProperty = msgContext
.getProperty(Constants.Configuration.ATTACHMENT_TEMP_DIR);
if (attachmentRepoDirProperty != null) {
attachmentRepoDir = (String)attachmentRepoDirProperty;
} else {
Parameter attachmentRepoDirParameter = msgContext
.getParameter(Constants.Configuration.ATTACHMENT_TEMP_DIR);
attachmentRepoDir =
(attachmentRepoDirParameter != null) ? (String)attachmentRepoDirParameter
.getValue()
: null;
}
Object attachmentSizeThresholdProperty = msgContext
.getProperty(Constants.Configuration.FILE_SIZE_THRESHOLD);
if (attachmentSizeThresholdProperty != null
&& attachmentSizeThresholdProperty instanceof String) {
attachmentSizeThreshold = (String)attachmentSizeThresholdProperty;
} else {
Parameter attachmentSizeThresholdParameter = msgContext
.getParameter(Constants.Configuration.FILE_SIZE_THRESHOLD);
attachmentSizeThreshold = attachmentSizeThresholdParameter != null ?
attachmentSizeThresholdParameter.getValue().toString() : null;
}
}
// Get the content-length if it is available
int contentLength = 0;
Map headers = (Map)msgContext.getProperty(MessageContext.TRANSPORT_HEADERS);
if (headers != null) {
String contentLengthValue = (String)headers.get(HTTPConstants.HEADER_CONTENT_LENGTH);
if (contentLengthValue != null) {
try {
contentLength = new Integer(contentLengthValue);
} catch (NumberFormatException e) {
if (log.isDebugEnabled()) {
log.debug(
"Content-Length is not a valid number. Will assume it is not set:" +
e);
}
}
}
}
if (log.isDebugEnabled()) {
if (contentLength > 0) {
log.debug("Creating an Attachments map. The content-length is" + contentLength);
} else {
log.debug("Creating an Attachments map.");
}
}
return createAttachments(msgContext,
inStream,
contentTypeString,
fileCacheForAttachments,
attachmentRepoDir,
attachmentSizeThreshold,
contentLength);
}
public static boolean isAttachmentsCacheEnabled(MessageContext msgContext) {
Object cacheAttachmentProperty = msgContext
.getProperty(Constants.Configuration.CACHE_ATTACHMENTS);
String cacheAttachmentString;
boolean fileCacheForAttachments;
if (cacheAttachmentProperty != null && cacheAttachmentProperty instanceof String) {
cacheAttachmentString = (String)cacheAttachmentProperty;
} else {
Parameter parameter_cache_attachment =
msgContext.getParameter(Constants.Configuration.CACHE_ATTACHMENTS);
cacheAttachmentString = (parameter_cache_attachment != null) ?
(String)parameter_cache_attachment.getValue() : null;
}
fileCacheForAttachments = (Constants.VALUE_TRUE.equals(cacheAttachmentString));
return fileCacheForAttachments;
}
public static Attachments createAttachments(MessageContext msgContext,
InputStream inStream,
String contentTypeString,
boolean fileCacheForAttachments,
String attachmentRepoDir,
String attachmentSizeThreshold,
int contentLength) {
LifecycleManager manager = null;
try {
AxisConfiguration configuration = msgContext.getRootContext().getAxisConfiguration();
manager = (LifecycleManager)configuration
.getParameterValue(DeploymentConstants.ATTACHMENTS_LIFECYCLE_MANAGER);
if (manager == null) {
manager = new LifecycleManagerImpl();
configuration.addParameter(DeploymentConstants.ATTACHMENTS_LIFECYCLE_MANAGER,
manager);
}
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Exception getting Attachments LifecycleManager", e);
}
}
return new Attachments(manager,
inStream,
contentTypeString,
fileCacheForAttachments,
attachmentRepoDir,
attachmentSizeThreshold,
contentLength);
}
/**
* Create a SOAP model builder. This method delegates to
* {@link OMXMLBuilderFactory#createSOAPModelBuilder(InputStream, String)} but generates
* additional logging if an error occurs.
*
* @param in
* the input stream containing the SOAP message
* @param encoding
* the charset encoding of the SOAP message or <code>null</code> if the parser should
* determine the charset encoding
* @return the builder
*/
public static SOAPModelBuilder createSOAPModelBuilder(InputStream in, String encoding) {
try {
return OMXMLBuilderFactory.createSOAPModelBuilder(in, encoding);
} catch (OMException e) {
log.info("OMException in getSOAPBuilder", e);
try {
log.info("Remaining input stream :[" +
new String(IOUtils.toByteArray(in), encoding) + "]");
} catch (IOException e1) {
// Nothing here?
}
throw e;
}
}
/**
* @deprecated Use {@link MessageProcessorSelector#getMessageBuilder(String, MessageContext)}.
*/
public static Builder getBuilderFromSelector(String type, MessageContext msgContext)
throws AxisFault {
return MessageProcessorSelector.getMessageBuilder(type, msgContext);
}
public static void validateSOAPVersion(String soapNamespaceURIFromTransport,
SOAPEnvelope envelope) {
if (soapNamespaceURIFromTransport != null) {
OMNamespace envelopeNamespace = envelope.getNamespace();
String namespaceName = envelopeNamespace.getNamespaceURI();
if (!(soapNamespaceURIFromTransport.equals(namespaceName))) {
throw new SOAPProcessingException(
"Transport level information does not match with SOAP" +
" Message namespace URI", envelopeNamespace.getPrefix() + ":" +
SOAPConstants.FAULT_CODE_VERSION_MISMATCH);
}
}
}
public static void validateCharSetEncoding(String charsetEncodingFromTransport,
String charsetEncodingFromXML,
String soapNamespaceURI) throws AxisFault {
if ((charsetEncodingFromXML != null)
&& !"".equals(charsetEncodingFromXML)
&& (charsetEncodingFromTransport != null)
&& !charsetEncodingFromXML.equalsIgnoreCase(charsetEncodingFromTransport)
&& !compatibleEncodings(charsetEncodingFromXML, charsetEncodingFromTransport)) {
/**
* WS-BP Rule 1019 requires toleration if the character sets mismatch
* I am changing this to simply a debug statement.
String faultCode;
if (SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(soapNamespaceURI)) {
faultCode = SOAP12Constants.FAULT_CODE_SENDER;
} else {
faultCode = SOAP11Constants.FAULT_CODE_SENDER;
}
throw new AxisFault("Character Set Encoding from "
+ "transport information [" + charsetEncodingFromTransport + "] does not match with "
+ "character set encoding in the received SOAP message [" + charsetEncodingFromXML + "]", faultCode);
**/
if (log.isDebugEnabled()) {
log.debug("Character Set Encoding from "
+ "transport information [" + charsetEncodingFromTransport + "] does not match with "
+ "character set encoding in the received SOAP message [" + charsetEncodingFromXML + "]");
}
}
}
/**
* check if the pair is [UTF-16,UTF-16LE] [UTF-32, UTF-32LE],[UTF-16,UTF-16BE] [UTF-32,
* UTF-32BE] etc.
*
* @param enc1 encoding style
* @param enc2 encoding style
* @return true if the encoding styles are compatible, or false otherwise
*/
private static boolean compatibleEncodings(String enc1, String enc2) {
enc1 = enc1.toLowerCase();
enc2 = enc2.toLowerCase();
if (enc1.endsWith("be") || enc1.endsWith("le")) {
enc1 = enc1.substring(0, enc1.length() - 2);
}
if (enc2.endsWith("be") || enc2.endsWith("le")) {
enc2 = enc2.substring(0, enc2.length() - 2);
}
return enc1.equals(enc2);
}
}