/* Copyright 2012 predic8 GmbH, www.predic8.com
Licensed 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 com.predic8.membrane.core.rules;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import com.google.common.collect.Lists;
import com.predic8.membrane.annot.MCAttribute;
import com.predic8.membrane.annot.MCElement;
import com.predic8.membrane.core.Constants;
import com.predic8.membrane.core.Router;
import com.predic8.membrane.core.config.security.SSLParser;
import com.predic8.membrane.core.interceptor.Interceptor;
import com.predic8.membrane.core.interceptor.WSDLInterceptor;
import com.predic8.membrane.core.interceptor.rewrite.RewriteInterceptor;
import com.predic8.membrane.core.interceptor.server.WSDLPublisherInterceptor;
import com.predic8.membrane.core.interceptor.soap.WebServiceExplorerInterceptor;
import com.predic8.membrane.core.resolver.ResourceRetrievalException;
import com.predic8.membrane.core.resolver.HTTPSchemaResolver;
import com.predic8.membrane.core.resolver.ResolverMap;
import com.predic8.membrane.core.transport.http.client.HttpClientConfiguration;
import com.predic8.membrane.core.util.URLUtil;
import com.predic8.membrane.core.ws.relocator.Relocator.PathRewriter;
import com.predic8.wsdl.AbstractBinding;
import com.predic8.wsdl.Definitions;
import com.predic8.wsdl.Port;
import com.predic8.wsdl.Service;
import com.predic8.wsdl.WSDLParser;
import com.predic8.wsdl.WSDLParserContext;
/**
* @description <p>
* A SOAP proxy can be deployed on front of a SOAP Web Service. It conceals the server and offers the same
* interface as the target server to its clients.
* </p>
* @explanation If the WSDL referenced by the <i>wsdl</i> attribute is not available at startup, the <soapProxy>
* will become inactive. Through the admin console, reinitialization attempts can be triggered and, by
* default, the {@link Router} also periodically triggers such attempts.
* @topic 2. Proxies
*/
@MCElement(name="soapProxy")
public class SOAPProxy extends AbstractServiceProxy {
private static final Logger log = LoggerFactory.getLogger(SOAPProxy.class.getName());
private static final Pattern relativePathPattern = Pattern.compile("^./[^/?]*\\?");
// configuration attributes
protected String wsdl;
protected String portName;
protected String targetPath;
protected HttpClientConfiguration httpClientConfig;
// set during initialization
protected ResolverMap resolverMap;
public SOAPProxy() {
this.key = new ServiceProxyKey(80);
}
@Override
protected AbstractProxy getNewInstance() {
return new SOAPProxy();
}
/**
* @return error or null for success
*/
private void parseWSDL() throws Exception {
WSDLParserContext ctx = new WSDLParserContext();
ctx.setInput(ResolverMap.combine(router.getBaseLocation(), wsdl));
try {
WSDLParser wsdlParser = new WSDLParser();
wsdlParser.setResourceResolver(resolverMap.toExternalResolver().toExternalResolver());
Definitions definitions = wsdlParser.parse(ctx);
List<Service> services = definitions.getServices();
if (services.size() != 1)
throw new IllegalArgumentException("There are " + services.size() + " services defined in the WSDL, but exactly 1 is required for soapProxy.");
Service service = services.get(0);
if (StringUtils.isEmpty(name))
name = StringUtils.isEmpty(service.getName()) ? definitions.getName() : service.getName();
List<Port> ports = service.getPorts();
Port port = selectPort(ports, portName);
String location = port.getAddress().getLocation();
if (location == null)
throw new IllegalArgumentException("In the WSDL, there is no @location defined on the port.");
try {
URL url = new URL(location);
target.setHost(url.getHost());
if (url.getPort() != -1)
target.setPort(url.getPort());
else
target.setPort(url.getDefaultPort());
if (key.getPath() == null) {
key.setUsePathPattern(true);
key.setPathRegExp(false);
key.setPath(url.getPath());
} else {
String query = "";
if(url.getQuery() != null){
query = "?" + url.getQuery();
}
targetPath = url.getPath()+ query;
}
if(location.startsWith("https")){
SSLParser sslOutboundParser = new SSLParser();
target.setSslParser(sslOutboundParser);
}
((ServiceProxyKey)key).setMethod("*");
} catch (MalformedURLException e) {
throw new IllegalArgumentException("WSDL endpoint location '"+location+"' is not an URL.", e);
}
return;
} catch (Exception e) {
Throwable f = e;
while (f.getCause() != null && ! (f instanceof ResourceRetrievalException))
f = f.getCause();
if (f instanceof ResourceRetrievalException) {
ResourceRetrievalException rre = (ResourceRetrievalException) f;
if (rre.getStatus() >= 400)
throw rre;
Throwable cause = rre.getCause();
if (cause != null) {
if (cause instanceof UnknownHostException)
throw (UnknownHostException) cause;
else if (cause instanceof ConnectException)
throw (ConnectException) cause;
}
}
throw new IllegalArgumentException("Could not download the WSDL '" + wsdl + "'.", e);
}
}
public static Port selectPort(List<Port> ports, String portName) {
if (portName != null) {
for (Port port : ports)
if (portName.equals(port.getName()))
return port;
throw new IllegalArgumentException("No port with name '" + portName + "' found.");
}
Port port = getPortByNamespace(ports, Constants.WSDL_SOAP11_NS);
if (port == null)
port = getPortByNamespace(ports, Constants.WSDL_SOAP12_NS);
if (port == null)
throw new IllegalArgumentException("No SOAP/1.1 or SOAP/1.2 ports found in WSDL.");
return port;
}
private static Port getPortByNamespace(List<Port> ports, String namespace) {
for (Port port : ports) {
try {
if (port.getBinding() == null)
continue;
if (port.getBinding().getBinding() == null)
continue;
AbstractBinding binding = port.getBinding().getBinding();
if (!"http://schemas.xmlsoap.org/soap/http".equals(binding.getProperty("transport")))
continue;
if (!namespace.equals(binding.getElementName().getNamespaceURI()))
continue;
return port;
} catch (Exception e) {
log.warn("Error inspecting WSDL port binding.", e);
}
}
return null;
}
private int automaticallyAddedInterceptorCount;
public void configure() throws Exception {
parseWSDL();
// remove previously added interceptors
for(; automaticallyAddedInterceptorCount > 0; automaticallyAddedInterceptorCount--)
interceptors.remove(0);
// add interceptors (in reverse order) to position 0.
WebServiceExplorerInterceptor sui = new WebServiceExplorerInterceptor();
sui.setWsdl(wsdl);
sui.setPortName(portName);
interceptors.add(0, sui);
automaticallyAddedInterceptorCount++;
boolean hasPublisher = getInterceptorOfType(WSDLPublisherInterceptor.class) != null;
if (!hasPublisher) {
WSDLPublisherInterceptor wp = new WSDLPublisherInterceptor();
wp.setWsdl(wsdl);
interceptors.add(0, wp);
automaticallyAddedInterceptorCount++;
}
WSDLInterceptor wsdlInterceptor = getInterceptorOfType(WSDLInterceptor.class);
boolean hasRewriter = wsdlInterceptor != null;
if (!hasRewriter) {
wsdlInterceptor = new WSDLInterceptor();
interceptors.add(0, wsdlInterceptor);
automaticallyAddedInterceptorCount++;
}
if (key.getPath() != null) {
final String keyPath = key.getPath();
final String name = URLUtil.getName(router.getUriFactory(), keyPath);
wsdlInterceptor.setPathRewriter(new PathRewriter() {
@Override
public String rewrite(String path2) {
try {
if (path2.contains("://")) {
path2 = new URL(new URL(path2), keyPath).toString();
} else {
Matcher m = relativePathPattern.matcher(path2);
path2 = m.replaceAll("./" + name + "?");
}
} catch (MalformedURLException e) {
}
return path2;
}
});
}
if (hasRewriter && !hasPublisher)
log.warn("A <soapProxy> contains a <wsdlRewriter>, but no <wsdlPublisher>. Probably you want to insert a <wsdlPublisher> just after the <wsdlRewriter>. (Or, if this is a valid use case, please notify us at " + Constants.PRODUCT_CONTACT_EMAIL + ".)");
if (targetPath != null) {
RewriteInterceptor ri = new RewriteInterceptor();
ri.setMappings(Lists.newArrayList(new RewriteInterceptor.Mapping("^" + Pattern.quote(key.getPath()), Matcher.quoteReplacement(targetPath), "rewrite")));
interceptors.add(0, ri);
automaticallyAddedInterceptorCount++;
}
}
@Override
public void init() throws Exception {
super.init();
if (wsdl == null)
return;
resolverMap = router.getResolverMap();
if (httpClientConfig != null) {
HTTPSchemaResolver httpSR = new HTTPSchemaResolver();
httpSR.setHttpClientConfig(httpClientConfig);
resolverMap = resolverMap.clone();
resolverMap.addSchemaResolver(httpSR);
}
configure();
}
@SuppressWarnings("unchecked")
private <T extends Interceptor> T getInterceptorOfType(Class<T> class1) {
for (Interceptor i : interceptors)
if (class1.isInstance(i))
return (T) i;
return null;
}
public String getWsdl() {
return wsdl;
}
/**
* @description The WSDL of the SOAP service.
* @example http://predic8.de/my.wsdl <i>or</i> file:my.wsdl
*/
@Required
@MCAttribute
public void setWsdl(String wsdl) {
this.wsdl = wsdl;
}
public String getPortName() {
return portName;
}
@MCAttribute
public void setPortName(String portName) {
this.portName = portName;
}
public HttpClientConfiguration getWsdlHttpClientConfig() {
return httpClientConfig;
}
@MCAttribute
public void setWsdlHttpClientConfig(HttpClientConfiguration httpClientConfig) {
this.httpClientConfig = httpClientConfig;
}
}