/* * 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.ode.axis2.httpbinding; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.EntityEnclosingMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.params.HttpParams; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ode.axis2.Properties; import org.apache.ode.axis2.util.URLEncodedTransformer; import org.apache.ode.axis2.util.UrlReplacementTransformer; import org.apache.ode.bpel.epr.MutableEndpoint; import org.apache.ode.bpel.iapi.PartnerRoleMessageExchange; import org.apache.ode.utils.DOMUtils; import org.apache.ode.utils.Namespaces; import org.apache.ode.utils.wsdl.Messages; import org.apache.ode.utils.wsdl.WsdlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import javax.wsdl.Binding; import javax.wsdl.BindingInput; import javax.wsdl.BindingOperation; import javax.wsdl.BindingOutput; import javax.wsdl.Message; import javax.wsdl.Operation; import javax.wsdl.Part; import javax.wsdl.extensions.UnknownExtensibilityElement; import javax.wsdl.extensions.http.HTTPOperation; import javax.wsdl.extensions.mime.MIMEContent; import javax.xml.namespace.QName; import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class HttpMethodConverter { private static final String CONTENT_TYPE_TEXT_XML = "text/xml"; private static final Log log = LogFactory.getLog(HttpMethodConverter.class); protected static final Messages msgs = Messages.getMessages(Messages.class); protected Binding binding; public HttpMethodConverter(Binding binding) { this.binding = binding; } public HttpMethod createHttpRequest(PartnerRoleMessageExchange odeMex, HttpParams params) throws UnsupportedEncodingException { Operation operation = odeMex.getOperation(); BindingOperation bindingOperation = binding.getBindingOperation(operation.getName(), operation.getInput().getName(), operation.getOutput().getName()); // message to be sent Element message = odeMex.getRequest().getMessage(); Message msgDef = operation.getInput().getMessage(); // base url String url = ((MutableEndpoint) odeMex.getEndpointReference()).getUrl(); // extract part values into a map and check that all parts are assigned a value Map<String, Element> partElements = extractPartElements(msgDef, message); // http method type // the operation may override the verb, this is an extension for RESTful BPEL String verb = WsdlUtils.resolveVerb(binding, bindingOperation); // build the http method itself HttpMethod method = prepareHttpMethod(bindingOperation, verb, partElements, url, params); return method; } /** * create and initialize the http method. * Http Headers that may been passed in the params are not set in this method. * Headers will be automatically set by HttpClient. * See usages of HostParams.DEFAULT_HEADERS * See org.apache.commons.httpclient.HttpMethodDirector#executeMethod(org.apache.commons.httpclient.HttpMethod) */ protected HttpMethod prepareHttpMethod(BindingOperation opBinding, String verb, Map<String, Element> partValues, final String rootUri, HttpParams params) throws UnsupportedEncodingException { if (log.isDebugEnabled()) log.debug("Preparing http request..."); // convenience variables... BindingInput bindingInput = opBinding.getBindingInput(); HTTPOperation httpOperation = (HTTPOperation) WsdlUtils.getOperationExtension(opBinding); MIMEContent content = WsdlUtils.getMimeContent(bindingInput.getExtensibilityElements()); String contentType = content == null ? "" : content.getType(); boolean useUrlEncoded = WsdlUtils.useUrlEncoded(bindingInput) || PostMethod.FORM_URL_ENCODED_CONTENT_TYPE.equalsIgnoreCase(contentType); boolean useUrlReplacement = WsdlUtils.useUrlReplacement(bindingInput); // the http method to be built and returned HttpMethod method = null; // the 4 elements the http method may be made of String relativeUri = httpOperation.getLocationURI(); String queryPath = null; RequestEntity requestEntity; String encodedParams = null; // ODE supports uri template in both port and operation location. // so assemble the final url *before* replacement String completeUri = rootUri; if (StringUtils.isNotEmpty(relativeUri)) { completeUri = completeUri + (completeUri.endsWith("/") || relativeUri.startsWith("/") ? "" : "/") + relativeUri; } if (useUrlReplacement) { // insert part values in the url completeUri = new UrlReplacementTransformer().transform(completeUri, partValues); } else if (useUrlEncoded) { // encode part values encodedParams = new URLEncodedTransformer().transform(partValues); } // http-client api is not really neat // something similar to the following would save some if/else manipulations. // But we have to deal with it as-is. // // method = new Method(verb); // method.setRequestEnity(..) // etc... if ("GET".equalsIgnoreCase(verb) || "DELETE".equalsIgnoreCase(verb)) { if ("GET".equalsIgnoreCase(verb)) { method = new GetMethod(); } else if ("DELETE".equalsIgnoreCase(verb)) { method = new DeleteMethod(); } if (useUrlEncoded) { queryPath = encodedParams; } // Let http-client manage the redirection // see org.apache.commons.httpclient.params.HttpClientParams.MAX_REDIRECTS // default is 100 method.setFollowRedirects(true); } else if ("POST".equalsIgnoreCase(verb) || "PUT".equalsIgnoreCase(verb)) { if ("POST".equalsIgnoreCase(verb)) { method = new PostMethod(); } else if ("PUT".equalsIgnoreCase(verb)) { method = new PutMethod(); } // some body-building... if (useUrlEncoded) { requestEntity = new StringRequestEntity(encodedParams, PostMethod.FORM_URL_ENCODED_CONTENT_TYPE, method.getParams().getContentCharset()); } else if (contentType.endsWith(CONTENT_TYPE_TEXT_XML)) { // get the part to be put in the body Part part = opBinding.getOperation().getInput().getMessage().getPart(content.getPart()); Element partValue = partValues.get(part.getName()); // if the part has an element name, we must take the first element if (part.getElementName() != null) { partValue = DOMUtils.getFirstChildElement(partValue); } String xmlString = DOMUtils.domToString(partValue); requestEntity = new ByteArrayRequestEntity(xmlString.getBytes(), contentType); } else { // should not happen because of HttpBindingValidator, but never say never throw new IllegalArgumentException("Unsupported content-type!"); } // cast safely, PUT and POST are subclasses of EntityEnclosingMethod final EntityEnclosingMethod enclosingMethod = (EntityEnclosingMethod) method; enclosingMethod.setRequestEntity(requestEntity); enclosingMethod.setContentChunked(params.getBooleanParameter(Properties.PROP_HTTP_REQUEST_CHUNK, false)); } else { // should not happen because of HttpBindingValidator, but never say never throw new IllegalArgumentException("Unsupported HTTP method: " + verb); } // link params together method.getParams().setDefaults(params); method.setPath(completeUri); // assumes that the path is properly encoded (URL safe). method.setQueryString(queryPath); // headers setHttpRequestHeaders(method, partValues, opBinding.getOperation().getInput().getMessage(), opBinding.getBindingInput()); return method; } /** * Go through the list of {@linkplain Namespaces.ODE_HTTP_EXTENSION_NS}{@code :header} elements included in the input binding. For each of them, set the HTTP Request Header with the static value defined by the attribute {@linkplain Namespaces.ODE_HTTP_EXTENSION_NS}{@code :value}, * or the part value mentionned in the attribute {@linkplain Namespaces.ODE_HTTP_EXTENSION_NS}{@code :part}. */ public void setHttpRequestHeaders(HttpMethod method, Map<String, Element> partValues, Message inputMessage, BindingInput bindingInput) { Collection<UnknownExtensibilityElement> headerBindings = WsdlUtils.getHttpHeaders(bindingInput.getExtensibilityElements()); for (Iterator<UnknownExtensibilityElement> iterator = headerBindings.iterator(); iterator.hasNext();) { Element binding = iterator.next().getElement(); String headerName = binding.getAttribute("name"); String partName = binding.getAttribute("part"); String value = binding.getAttribute("value"); String headerValue; if (StringUtils.isNotEmpty(partName)) { // get the part to be put in the body Part part = inputMessage.getPart(partName); Element partValue = partValues.get(part.getName()); // if the part has an element name, we must take the first element if (part.getElementName() != null) partValue = DOMUtils.getFirstChildElement(partValue); headerValue = DOMUtils.domToString(partValue); } else if (StringUtils.isNotEmpty(value)) { headerValue = value; } else { String errMsg = "Invalid binding: missing attribute! Expecting " + new QName(Namespaces.ODE_HTTP_EXTENSION_NS, "part") + " or " + new QName(Namespaces.ODE_HTTP_EXTENSION_NS, "value"); if (log.isErrorEnabled()) { log.error(errMsg); } throw new RuntimeException(errMsg); } method.setRequestHeader(headerName, HttpClientHelper.replaceCRLFwithLWS(headerValue)); } } protected Map<String, Element> extractPartElements(Message msgDef, Element message) { Map<String, Element> partValues = new HashMap<String, Element>(); for (Iterator iterator = msgDef.getParts().values().iterator(); iterator.hasNext();) { Part part = (Part) iterator.next(); Element partEl = DOMUtils.findChildByName(message, new QName(null, part.getName())); if (partEl == null) throw new IllegalArgumentException(msgs.msgOdeMessageMissingRequiredPart(part.getName())); partValues.put(part.getName(), partEl); } return partValues; } /** * Create the element to be associated with this part into the {@link org.apache.ode.bpel.iapi.Message}. * <br/>An element named with the part name will be returned. the content of this element depends on the part. * <p/>If the part has a non-null element name, a new element will be created and named accordingly then the text value is inserted in this new element. * <br/>else the given text content is simply set on the part element. * * @param part * @param textContent * @return an element named with the part name will be returned */ public Element createPartElement(Part part, String textContent) { Document doc = DOMUtils.newDocument(); Element partElement = doc.createElementNS(null, part.getName()); if (part.getElementName() != null) { Element element = doc.createElementNS(part.getElementName().getNamespaceURI(), part.getElementName().getLocalPart()); element.setTextContent(textContent); partElement.appendChild(element); } else { partElement.setTextContent(textContent); } return partElement; } /** * Create the element to be associated with this part into the {@link org.apache.ode.bpel.iapi.Message}. * <p/>If the part has a non-null element name, the bodyElement is simply appended. * Else if the bodyElement has a text content, the value is set to the message. * Else append all nodes of bodyElement to the returned element. Attributes are ignored. * <p/> * The name of the returned element is the part name. * * @param part * @param receivedElement * @return the element to insert "as is" to ODE message */ public Element createPartElement(Part part, Element receivedElement) { Document doc = DOMUtils.newDocument(); Element partElement = doc.createElementNS(null, part.getName()); if (part.getElementName() != null) { partElement.appendChild(doc.importNode(receivedElement, true)); } else { if (DOMUtils.isEmptyElement(receivedElement)) { // Append an empty text node. // Warning! setting an empty string with setTextContent has not effect. See javadoc. partElement.appendChild(doc.createTextNode("")); } else { // No need to make the distinction between simple and complex types, importNode will handle it // !!! Attributes are ignored for (int m = 0; m < receivedElement.getChildNodes().getLength(); m++) { Node child = receivedElement.getChildNodes().item(m); partElement.appendChild(doc.importNode(child, true)); } } } return partElement; } /** * Process the HTTP Response Headers. * <p/> * First go through the list of {@linkplain Namespaces.ODE_HTTP_EXTENSION_NS}{@code :header} elements included in the output binding. For each of them, set the header value as the value of the message part. * <br/>Then add all HTTP headers as header part in the message. The name of the header would be the part name. * * @param odeMessage * @param method * @param messageDef * @param bindingOutput */ public void extractHttpResponseHeaders(org.apache.ode.bpel.iapi.Message odeMessage, HttpMethod method, Message messageDef, BindingOutput bindingOutput) { Collection<UnknownExtensibilityElement> headerBindings = WsdlUtils.getHttpHeaders(bindingOutput.getExtensibilityElements()); // iterate through the list of header bindings // and set the message parts accordingly for (Iterator<UnknownExtensibilityElement> iterator = headerBindings.iterator(); iterator.hasNext();) { Element binding = iterator.next().getElement(); String partName = binding.getAttribute("part"); String headerName = binding.getAttribute("name"); Part part = messageDef.getPart(partName); if (StringUtils.isNotEmpty(partName)) { odeMessage.setPart(partName, createPartElement(part, method.getRequestHeader(headerName).getValue())); } else { String errMsg = "Invalid binding: missing required attribute! Part name: " + new QName(Namespaces.ODE_HTTP_EXTENSION_NS, "part"); if (log.isErrorEnabled()) log.error(errMsg); throw new RuntimeException(errMsg); } } // also add all HTTP headers into the messade as header parts Header[] reqHeaders = method.getResponseHeaders(); for (int i = 0; i < reqHeaders.length; i++) { Header h = reqHeaders[i]; odeMessage.setHeaderPart(h.getName(), h.getValue()); } } }