/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.xml.ws.server;
import gw.config.CommonServices;
import gw.fs.IFile;
import gw.internal.schema.gw.xsd.w3c.soap12_envelope.anonymous.elements.Fault_Code;
import gw.internal.schema.gw.xsd.w3c.soap12_envelope.anonymous.elements.Fault_Reason;
import gw.internal.schema.gw.xsd.w3c.soap12_envelope.enums.FaultcodeEnum;
import gw.internal.xml.IXmlLoggerFactory;
import gw.internal.xml.XmlElementInternals;
import gw.internal.xml.XmlSchemaAccessImpl;
import gw.internal.xml.XmlTypeResolver;
import gw.internal.xml.config.XmlServices;
import gw.internal.xml.ws.GosuWebservicesServlet;
import gw.internal.xml.ws.WsiInvocationContextImpl;
import gw.internal.xml.ws.http.HttpParseContext;
import gw.internal.xml.ws.http.fragment.HttpMediaType;
import gw.internal.xml.ws.http.fragment.HttpMultipartRelatedContent;
import gw.internal.xml.ws.server.marshal.ArrayMarshalInfo;
import gw.internal.xml.ws.server.marshal.ListMarshalInfo;
import gw.internal.xml.ws.server.marshal.MarshalInfo;
import gw.internal.xml.ws.server.marshal.XmlElementMarshalInfo;
import gw.internal.xml.ws.server.marshal.XmlTypeInstanceMarshalInfo;
import gw.internal.xml.xsd.typeprovider.NotFoundException;
import gw.internal.xml.xsd.typeprovider.XmlSchemaIndex;
import gw.internal.xml.xsd.typeprovider.XmlSchemaResourceTypeLoaderBase;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaElement;
import gw.internal.xml.xsd.typeprovider.schemaparser.SoapVersion;
import gw.lang.function.Function1;
import gw.lang.parser.ISource;
import gw.lang.reflect.IAnnotationInfo;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeInfo;
import gw.lang.reflect.ITypeLoaderListener;
import gw.lang.reflect.RefreshRequest;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.GosuClassTypeLoader;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuClassRepository;
import gw.lang.reflect.gs.ISourceFileHandle;
import gw.lang.reflect.module.IModule;
import gw.util.GosuClassUtil;
import gw.util.GosuExceptionUtil;
import gw.util.ILogger;
import gw.util.Pair;
import gw.util.StreamUtil;
import gw.util.concurrent.LockingLazyVar;
import gw.xml.BinaryData;
import gw.xml.XmlElement;
import gw.xml.XmlNamespace;
import gw.xml.XmlParseOptions;
import gw.xml.XmlSchemaAccess;
import gw.xml.XmlSerializationOptions;
import gw.xml.ws.DefaultWsiInvocationHandler;
import gw.xml.ws.WebServiceException;
import gw.xml.ws.WsdlFault;
import gw.xml.ws.WsiRequestLocal;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import javax.xml.validation.Schema;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
/**
* This class is the base class for exposing web services via an app server
*/
public abstract class WebservicesServletBase extends HttpServlet implements ITypeLoaderListener {
public static final String CONFIG_PARAM_AVAILABLE_SERVICES = "AvailableServices";
public static final String CONFIG_PARAM_HIDE_SERVICE_LIST_PAGE = "HideListPage";
private Set<String> _availableServices = new HashSet<String>();
private boolean _hideServiceListPage;
private boolean _initializedFromConfig;
private static final String XSD_SUFFIX = ".xsd";
private static final String WSDL_SUFFIX = ".wsdl";
private static final String GX_SUFFIX = ".gx";
private static final LockingLazyVar<IType> _wsiWebServiceAnnotationType = new LockingLazyVar<IType>( TypeSystem.getGlobalLock() ) {
@Override
protected IType init() {
return TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiWebService" );
}
};
private static final LockingLazyVar<IType> _doNotVerifyResourceAnnotationType = new LockingLazyVar<IType>( TypeSystem.getGlobalLock() ) {
@Override
protected IType init() {
return TypeSystem.getByFullNameIfValid("gw.testharness.DoNotVerifyResource");
}
};
private static ILogger _logger;
private static ILogger _requestLogger;
private Map<String, ILogger> _loggers = new HashMap<String, ILogger>();
private static final Map<String, Pair<String, String>> RESOURCES = new HashMap<String, Pair<String, String>>();
private static final Map<IGosuClass, WsiInvocationContextImpl.WebService> _webservices = new HashMap<IGosuClass, WsiInvocationContextImpl.WebService>();
private static LockingLazyVar<WebservicesServletBase> _defaultLocalWebservicesServlet = new LockingLazyVar<WebservicesServletBase>() {
@Override
protected WebservicesServletBase init() {
try {
return _defaultLocalWebservicesServletClass.getConstructor( boolean.class ).newInstance( true );
} catch ( Exception ex ) {
throw GosuExceptionUtil.forceThrow( ex );
}
}
};
private static Class<? extends WebservicesServletBase> _defaultLocalWebservicesServletClass = GosuWebservicesServlet.class;
private final boolean _wsiLocal;
private boolean _initialized;
private IType _requestTransformType;
private IType _responseTransformType;
private IType _requestXmlTransformType;
private IType _responseXmlTransformType;
private IType _wsiInvocationHandlerType;
private IType _wsiParseOptionsAnnotationType;
private IType _wsiSerializationOptionsAnnotationType;
private static final ThreadLocal<WeakHashMap<WsiRequestLocal, ?>> _requestLocals = new ThreadLocal<WeakHashMap<WsiRequestLocal, ?>>();
private static Set<WebservicesServletBaseListenerForTesting> _listenersForTesting = new HashSet<WebservicesServletBaseListenerForTesting>();
public static final Map<QName, WsdlFault.FaultCode> SOAP11_FAULT_TO_GENERIC_FAULT = new HashMap<QName, WsdlFault.FaultCode>();
public static final Map<WsdlFault.FaultCode, QName> GENERIC_FAULT_TO_SOAP11_FAULT = new HashMap<WsdlFault.FaultCode, QName>();
private static void mapSoap11Fault( String soap11FaultName, WsdlFault.FaultCode genericFaultCode ) {
String nsUri = gw.internal.schema.gw.xsd.w3c.soap11_envelope.Fault.$QNAME.getNamespaceURI();
QName soap11FaultQName = new QName( nsUri, soap11FaultName );
SOAP11_FAULT_TO_GENERIC_FAULT.put( soap11FaultQName, genericFaultCode );
GENERIC_FAULT_TO_SOAP11_FAULT.put( genericFaultCode, soap11FaultQName );
}
static {
mapSoap11Fault( "VersionMismatch", WsdlFault.FaultCode.VersionMismatch );
mapSoap11Fault( "MustUnderstand", WsdlFault.FaultCode.MustUnderstand );
mapSoap11Fault( "Client", WsdlFault.FaultCode.Sender );
mapSoap11Fault( "Server", WsdlFault.FaultCode.Receiver );
// specifically list out resources to avoid possible security holes
addResource( "/resources.dftree/dftree.css", "dftree/dftree.css", "text/css" );
addResource( "/resources.dftree/dftree.js", "dftree/dftree.js", "application/x-javascript" );
addResource( "/resources.dftree/img/foldertree_folder.gif", "dftree/img/foldertree_folder.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_folderopen.gif", "dftree/img/foldertree_folderopen.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_minusbottom.gif", "dftree/img/foldertree_minusbottom.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_plusbottom.gif", "dftree/img/foldertree_plusbottom.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_plus.gif", "dftree/img/foldertree_plus.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_minus.gif", "dftree/img/foldertree_minus.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_joinbottom.gif", "dftree/img/foldertree_joinbottom.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_join.gif", "dftree/img/foldertree_join.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_page.gif", "dftree/img/foldertree_page.gif", "image/gif" );
addResource( "/resources.dftree/img/foldertree_line.gif", "dftree/img/foldertree_line.gif", "image/gif" );
}
public WebservicesServletBase() {
this( false );
}
public WebservicesServletBase( boolean wsiLocal ) {
_wsiLocal = wsiLocal;
}
private static void addResource( String publicName, String resourceName, String contentType ) {
RESOURCES.put( publicName, new Pair<String, String>( resourceName, contentType ) );
}
/**
* This will return a logger for use by the customer's logger.
*
* @return the ILogger
*/
public static ILogger getILogger() {
if ( _logger == null ) {
_logger = XmlServices.getLogger( IXmlLoggerFactory.Category.Runtime );
}
return _logger;
}
/**
* This will return a logger for use by the customer's logger.
*
* @return the ILogger
*/
public static ILogger getRequestILogger() {
if ( _requestLogger == null ) {
_requestLogger = XmlServices.getLogger( IXmlLoggerFactory.Category.Request );
}
return _requestLogger;
}
public static Set<String> getAllWebserviceTypesStatic() {
Set<String> allTypeNames = new HashSet<String>();
IGosuClassRepository repository = GosuClassTypeLoader.getDefaultClassLoader().getRepository();
for ( String typeName : repository.getAllTypeNames( ".gs" ) ) {
// dvl: added temporary variables to help debug NPE (PL-18521):
final ISourceFileHandle clazz = repository.findClass( typeName, new String[]{ GosuClassTypeLoader.GOSU_CLASS_FILE_EXT } );
if ( clazz == null ) {
continue; // avoid NPE - probably not the right thing to do, but this issue is nearly impossible to reproduce to figure out what else should be done
}
final ISource parserSource = clazz.getSource();
final String sourceString = parserSource.getSource();
final String lowercaseSourceString = sourceString.toLowerCase();
int idx = lowercaseSourceString.indexOf( "wsiwebservice" ); // optimization
if ( idx < 0 ) {
continue;
}
if ( testTypeName( typeName ) ) {
allTypeNames.add( typeName );
}
}
return allTypeNames;
// return TypeSystem.getAllTypeNames(); // http://jira/jira/browse/PL-11741
}
protected void initAvailable() {
}
protected boolean initAvailable( String path ) {
final URL resource = getClass().getClassLoader().getResource( path );
if ( resource != null ) {
try {
// try {
// return CommonServices.getFileSystem().getIFile(new File(url.toURI()));
// } catch (URISyntaxException e) {
// throw new RuntimeException(e);
// }
InputStream stream = CommonServices.getFileSystem().getIFile(resource).openInputStream();
readAvailable( stream );
return true;
} catch ( Throwable e ) {
throw new RuntimeException( "initAvailable from resource " + path, e );
}
}
File file = new File( path );
if ( file.exists() ) {
try {
readAvailable( new FileInputStream( file ) );
} catch ( Throwable e ) {
throw new RuntimeException( "initAvailable from file system " + path, e );
}
}
return false;
}
protected String getStringValue( ServletConfig config, final String name, final String defaultValue ) {
if ( config == null ) {
return defaultValue;
}
final String value = config.getInitParameter( name );
return value == null || value.length() == 0 ? defaultValue : value;
}
protected boolean getBooleanValue( ServletConfig config, final String name, final boolean defaultValue ) {
if ( config == null ) {
return defaultValue;
}
final String value = config.getInitParameter( name );
return value == null || value.length() == 0 ? defaultValue : Boolean.parseBoolean( value );
}
protected void onRefreshTypeSystem() {
TypeSystem.lock();
try {
if ( !_initializedFromConfig ) {
_availableServices.clear();
for ( String typeName : getAllWebserviceTypesStatic() ) {
_availableServices.add( typeName );
}
}
} finally {
TypeSystem.unlock();
}
}
protected void addWebService( String typeName ) {
TypeSystem.lock();
try {
if ( testTypeName( typeName ) ) {
_availableServices.add( typeName );
}
} finally {
TypeSystem.unlock();
}
}
protected void removeWebService( String typeName ) {
TypeSystem.lock();
try {
_availableServices.remove( typeName );
} finally {
TypeSystem.unlock();
}
}
private static boolean testTypeName( String typeName ) {
try {
IGosuClass clazz = (IGosuClass) TypeSystem.getByFullNameIfValid( typeName );
if ( clazz == null ) {
if ( getILogger().isDebugEnabled() ) {
getILogger().debug( "Could not find typeName " + typeName );
}
return false;
}
if ( ! clazz.getTypeInfo().hasAnnotation( _wsiWebServiceAnnotationType.get() ) ) {
return false;
}
IType annotationType = _doNotVerifyResourceAnnotationType.get();
if ( annotationType != null && clazz.getTypeInfo().hasAnnotation( annotationType ) ) {
return false;
}
} catch ( Throwable ex ) {
if ( getILogger().isDebugEnabled() ) {
getILogger().debug( "On typeName " + typeName, ex );
}
return false;
}
return true;
}
protected void readAvailable( InputStream is ) throws IOException {
try {
final BufferedReader reader = new BufferedReader( StreamUtil.getInputStreamReader( is ) );
while ( true ) {
String line = reader.readLine();
if ( line == null ) {
break;
}
addWebService( line.trim() );
}
} finally {
is.close();
}
}
/**
* This will be called before the invoke method is called, it is used to ensure the server is in the correct
* state.
*/
protected void beforeInvoke( WebservicesRequest request, WebservicesResponseAdapter responseAdapter ) {
_requestLocals.set(new WeakHashMap<WsiRequestLocal, Object>());
}
/**
* This will be called after the invoke method is called, it is used to ensure the server gets back into the
* correct state. It is called in a finally block.
*/
protected void afterInvoke( WebservicesRequest request, WebservicesResponseAdapter responseAdapter ) {
_requestLocals.remove();
}
/**
* This will be called after the requestElement is determined and before it is executed.
*
* @param request the httpRequest for this request.
* @param requestElement the requestElement (QName is effectively the operation name)
*/
protected void logRequest( WebservicesRequest request, XmlElement requestElement ) {
if ( getRequestILogger().isDebugEnabled() ) {
String userName = null;
String userAddr = null;
if ( request != null ) {
HttpServletRequest httpRequest = request.getHttpServletRequest();
if ( httpRequest != null ) {
userName = httpRequest.getRemoteUser();
userAddr = httpRequest.getRemoteAddr();
}
}
getRequestILogger().debug( "Do " + ( userName == null ? "<unknown>" : userName ) + "@" + ( userAddr == null ? "<unknown>" : userAddr ) + ": " + requestElement.getQName() );
}
}
@Override
protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException {
doPost( new ServletWebservicesRequest( request ), new ServletWebservicesResponseAdapter( response, getILogger() ) );
}
public void doPost( WebservicesRequest request, WebservicesResponseAdapter responseAdapter ) throws ServletException {
Map<String, String> requestPairs = parseRequestPairs( request.getQueryString() );
boolean createSession = false;
for (String key : requestPairs.keySet()) {
if ("stateful".equalsIgnoreCase( key )) {
createSession = true;
break;
}
}
if ( createSession ) {
request.createSession();
}
beforeInvoke( request, responseAdapter );
final WsiInvocationContextImpl context = new WsiInvocationContextImpl();
try {
maybeInit();
Pair<IType, SoapVersion> pair = getWebServiceInfo(request);
if ( pair == null ) {
getILogger().info( "Webservices servlet: 404 Not Found (web service type): " + request.getPathInfo() );
send404NotFound( responseAdapter );
return;
}
IType type = pair.getFirst();
SoapVersion soapVersion = pair.getSecond();
List<Callable> finallyList = new ArrayList<Callable>();
WsiInvocationContextImpl.WebService webservice = getWebServiceForType( type );
context.setWebserviceType( type );
context.setWebService( webservice );
context.setWebservicesServletBase( this );
context.setSoapVersion( soapVersion );
context.setWebservicesRequest( request );
context.setFinallyList( finallyList );
context.setRequestHttpHeaders( request.getHttpHeaders() );
context.setHttpServletRequest( request.getHttpServletRequest() );
OutputStream os;
if ( webservice._responseTransform != null ) {
os = new ByteArrayOutputStream();
} else {
os = responseAdapter.getOutputStream();
}
// Once we have the soap version, we can now return a fault for anything erroneous, so we start the try/catch here
try {
XmlElement envelope;
InputStream is = request.getInputStream();
// get character set
String contentTypeString = request.getHttpHeaders().getHeader( "Content-Type" );
String charset = null;
if ( contentTypeString != null ) {
HttpMediaType httpMediaType = new HttpMediaType( new HttpParseContext( contentTypeString.getBytes( "US-ASCII" ) ) );
if ( httpMediaType.getMediaType().equals( "multipart/related" ) ) {
is = XopUtil.getInputStream( new HttpMultipartRelatedContent( new HttpParseContext( StreamUtil.getContent( is ) ), httpMediaType ) );
}
charset = httpMediaType.getFirstParameter( "charset" );
}
// log request if desired
ILogger wsLogger = getLogger(webservice._serviceInfo.getWebserviceType().getName());
if (wsLogger.isDebugEnabled()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamUtil.copy(is,baos);
wsLogger.debug(">>>\n" + (charset == null ? baos.toString("UTF-8") : baos.toString(charset)));
is = new ByteArrayInputStream(baos.toByteArray());
}
if ( webservice._requestTransform != null ) {
is = (InputStream) webservice._requestTransform.invoke( is );
}
// if charset is specified in content-type http header, use that, otherwise use charset embedded in XML
if ( charset == null ) {
envelope = XmlElementInternals.instance().parse( is, webservice._parseOptions, webservice._typeResolver );
}
else {
byte[] content = StreamUtil.getContent( is );
String xmlString = new String( content, charset );
envelope = XmlElementInternals.instance().parse( new StringReader( xmlString ), webservice._parseOptions, webservice._typeResolver );
}
context.setRequestEnvelope( envelope );
context.setRequestSoapHeaders( getHeadersFromEnvelope( envelope, soapVersion ) );
if ( webservice._requestXmlTransform != null ) {
webservice._requestXmlTransform.invoke( envelope );
}
XmlElement body = getBodyFromEnv( envelope, soapVersion );
List<XmlElement> bodyChildren = body.getChildren();
if ( bodyChildren.size() != 1 ) {
throw new WebServiceException( "Expected SOAP body to contain 1 child, but found " + bodyChildren.size(), true );
}
XmlElement requestElement = bodyChildren.get( 0 );
logRequest( request, requestElement );
XmlElement resp = webservice._invocationHandler.invoke( requestElement, context );
XmlElement responseEnvelopeXml;
if ( soapVersion == SoapVersion.SOAP_12 ) {
gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope responseEnvelope = new gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope();
responseEnvelope.setBody$( new gw.internal.schema.gw.xsd.w3c.soap12_envelope.Body() );
responseEnvelope.Body().addChild( resp );
if ( context != null && context.getResponseSoapHeadersDirect() != null && !context.getResponseSoapHeadersDirect().isEmpty() ) {
responseEnvelope.setHeader$( new gw.internal.schema.gw.xsd.w3c.soap12_envelope.Header() );
responseEnvelope.Header().getChildren().addAll( context.getResponseSoapHeadersDirect() );
}
responseEnvelopeXml = responseEnvelope;
} else {
gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope responseEnvelope = new gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope();
responseEnvelope.setBody$( new gw.internal.schema.gw.xsd.w3c.soap11_envelope.Body() );
responseEnvelope.Body().addChild( resp );
if ( context != null && context.getResponseSoapHeadersDirect() != null && !context.getResponseSoapHeadersDirect().isEmpty() ) {
responseEnvelope.setHeader$( new gw.internal.schema.gw.xsd.w3c.soap11_envelope.Header() );
responseEnvelope.Header().getChildren().addAll( context.getResponseSoapHeadersDirect() );
}
responseEnvelopeXml = responseEnvelope;
}
if ( context != null && context.getResponseHttpHeadersDirect() != null ) {
for ( String headerName : context.getResponseHttpHeadersDirect().getHeaderNames() ) {
String headerValue = context.getResponseHttpHeadersDirect().getHeader( headerName );
responseAdapter.getHttpHeaders().setHeader( headerName, headerValue );
}
}
if ( webservice._responseXmlTransform != null ) {
webservice._responseXmlTransform.invoke( responseEnvelopeXml );
}
if (wsLogger.isDebugEnabled()) {
wsLogger.debug("<<<\n" + responseEnvelopeXml.asUTFString());
}
writeResponseEnvelope( responseEnvelopeXml, os, context, webservice, responseAdapter, soapVersion );
} catch ( Throwable throwable ) {
final WsdlFault wsdlFault = getWsdlFault( throwable, webservice );
handleWsdlFault( wsdlFault, soapVersion, webservice, os, context, responseAdapter );
logWsdlFault( wsdlFault.getDetail() != null, throwable );
} finally {
try {
if ( webservice._responseTransform != null ) {
ByteArrayOutputStream baos = (ByteArrayOutputStream) os;
byte[] bytes = baos.toByteArray();
InputStream in = (InputStream) webservice._responseTransform.invoke( new ByteArrayInputStream( bytes ) );
StreamUtil.copy( in, responseAdapter.getOutputStream() );
}
} finally {
for ( Callable callable : finallyList ) {
try {
callable.call();
} catch ( Throwable t ) {
t.printStackTrace();
}
}
}
}
} catch ( IOException ex ) {
throw new ServletException( ex );
} finally {
afterInvoke( request, responseAdapter );
}
}
private ILogger getLogger(String name) {
ILogger logger = _loggers.get(name);
if (logger == null) {
synchronized (_loggers) {
logger = _loggers.get(name);
if (logger == null) {
logger = XmlServices.getLogger(name);
_loggers.put(name, logger);
}
}
}
return logger;
}
private static void writeString( OutputStream os, String s ) throws IOException {
os.write( s.getBytes( "UTF-8" ) );
}
private void writeResponseEnvelope( XmlElement responseEnvelopeXml, OutputStream os, WsiInvocationContextImpl context, WsiInvocationContextImpl.WebService webservice, WebservicesResponseAdapter responseAdapter, SoapVersion soapVersion ) throws IOException {
boolean mtomEnabled = context != null && context.isMtomEnabled();
XmlSerializationOptions options = context == null ? webservice._serializationOptions : context.getXmlSerializationOptions();
HashMap<XmlElement, Pair<String, BinaryData>> xopIncludes = new HashMap<XmlElement, Pair<String, BinaryData>>();
if ( mtomEnabled ) {
// find base64Binarys in XML tree, remove them for use as attachments
removeBinaryData( responseEnvelopeXml, xopIncludes );
}
// optimization - if sorting or validating, pre-serialize the XML to catch any exceptions
if ( options == null || options.getSort() || options.getValidate() ) {
responseEnvelopeXml.writeTo( new DevNullOutputStream(), options );
if ( options == null ) {
options = new XmlSerializationOptions();
} else {
options = options.copy();
}
options.setValidate( false ); // no need to validate a second time
}
String contentType = getContentType( soapVersion );
String multipartBoundary = null;
if ( mtomEnabled ) {
responseEnvelopeXml.declareNamespace( new XmlNamespace( gw.internal.schema.gw.xsd.w3c.xop_include.Include.$QNAME.getNamespaceURI(), "xop" ) );
// place xop includes
for ( Map.Entry<XmlElement, Pair<String, BinaryData>> entry : xopIncludes.entrySet() ) {
XmlElement xml = entry.getKey();
Pair<String, BinaryData> pair = entry.getValue();
String uuid = pair.getFirst();
BinaryData binaryData = pair.getSecond();
gw.internal.schema.gw.xsd.w3c.xop_include.Include include = new gw.internal.schema.gw.xsd.w3c.xop_include.Include();
try {
include.setHref$( new URI( "cid:" + uuid ) );
}
catch ( URISyntaxException ex ) {
throw new RuntimeException( ex ); // shouldn't happen unless we did something wrong
}
xml.addChild( include );
}
multipartBoundary = "uuid:" + UUID.randomUUID().toString();
String multipartStart = "<root.message@ws.guidewire.com>";
responseAdapter.setContentType( "multipart/related; type=\"" + contentType + "\"; boundary=\"" + multipartBoundary + "\"; start=\"" + multipartStart + "\"; start-info=\"text/xml\"" );
writeString( os, "\r\n" );
writeString( os, "--" );
writeString( os, multipartBoundary );
writeString( os, "\r\n" );
writeString( os, "Content-Type: " );
writeString( os, contentType );
writeString( os, "\r\n" );
writeString( os, "Content-Transfer-Encoding: binary\r\n" );
writeString( os, "Content-ID: " );
writeString( os, multipartStart );
writeString( os, "\r\n" );
writeString( os, "\r\n" );
}
else {
responseAdapter.setContentType( contentType );
}
responseAdapter.commitHttpHeaders();
responseEnvelopeXml.writeTo( os, options );
if ( mtomEnabled ) {
for ( Pair<String, BinaryData> pair : xopIncludes.values() ) {
String uuid = pair.getFirst();
BinaryData binaryData = pair.getSecond();
writeString( os, "\r\n" );
writeString( os, "--" );
writeString( os, multipartBoundary );
writeString( os, "\r\n" );
writeString( os, "Content-Type: application/octet-stream\r\n" ); // TODO dlank - what should this be set to?
writeString( os, "Content-Transfer-Encoding: binary\r\n" );
writeString( os, "Content-ID: <" + uuid + ">\r\n" );
writeString( os, "\r\n" );
StreamUtil.copy( binaryData.getInputStream(), os );
}
writeString( os, "\r\n" );
writeString( os, "--" );
writeString( os, multipartBoundary );
writeString( os, "--" );
writeString( os, "\r\n" );
}
}
private void removeBinaryData( XmlElement xml, Map<XmlElement, Pair<String, BinaryData>> xopIncludes ) {
if ( xml.getSimpleValue() != null && xml.getSimpleValue().getGosuValueType().equals( TypeSystem.get( BinaryData.class ) ) ) {
String uuid = UUID.randomUUID() + "@ws.guidewire.com";
xopIncludes.put( xml, new Pair<String, BinaryData>( uuid, (BinaryData) xml.getSimpleValue().getGosuValue() ) );
xml.setSimpleValue( null );
}
for ( XmlElement child : xml.getChildren() ) {
removeBinaryData( child, xopIncludes );
}
}
private static Map<String, String> parseRequestPairs( String queryString ) {
if ( queryString == null || queryString.length() == 0 ) {
return Collections.emptyMap();
}
Map<String, String> ret = new HashMap<String, String>();
while ( true ) {
int idx = queryString.indexOf( '&' );
String keyValue;
if ( idx < 0 ) {
keyValue = queryString;
queryString = "";
} else {
keyValue = queryString.substring( 0, idx );
queryString = queryString.substring( idx + 1 );
}
idx = keyValue.indexOf( '=' );
String key, value;
if ( idx < 0 ) {
key = keyValue;
value = "";
} else {
key = keyValue.substring( 0, idx );
value = keyValue.substring( idx + 1 );
}
ret.put( key, value );
if ( queryString.length() == 0 ) {
break;
}
}
return ret;
}
private WsdlFault getWsdlFault( Throwable throwable, WsiInvocationContextImpl.WebService webservice ) {
WsdlFault wsdlFault;
if ( throwable instanceof WsdlFault ) {
wsdlFault = (WsdlFault) throwable;
} else {
WsdlFault.FaultCode faultCode = null;
IType declaredFaultType = getExceptionTypeExpected( webservice._serviceInfo.getThrownExceptions(), TypeSystem.getTypeFromObject( throwable ) );
// set fault code
if ( declaredFaultType != null || ( throwable instanceof WebServiceException && ( (WebServiceException) throwable ).isSenderFault() ) ) {
faultCode = WsdlFault.FaultCode.Sender;
}
// set message
String message;
if ( declaredFaultType != null || throwable instanceof WebServiceException ) {
message = throwable.getMessage() == null ? "" : throwable.getMessage();
}
else {
message = throwable.toString();
throwable.printStackTrace();
}
// set detail element
XmlElement detailElement = null;
if ( declaredFaultType != null ) {
detailElement = new XmlElement( new QName( webservice._serviceInfo.getSchema().TargetNamespace().toString(), declaredFaultType.getRelativeName() ) );
}
wsdlFault = new WsdlFault( message, throwable );
wsdlFault.setCode( faultCode );
wsdlFault.setDetail( detailElement );
}
return wsdlFault;
}
private IType getExceptionTypeExpected( Set<IType> types, IType exceptionType ) {
IType testingType = exceptionType;
while ( testingType != null && !types.contains( testingType ) ) {
testingType = testingType.getSupertype();
}
return testingType;
}
private void handleWsdlFault( final WsdlFault wsdlFault, final SoapVersion soapVersion, final WsiInvocationContextImpl.WebService webservice, final OutputStream os, final WsiInvocationContextImpl context, WebservicesResponseAdapter responseAdapter ) throws IOException {
final String message = wsdlFault.getMessage();
final XmlElement detailElement = wsdlFault.getDetail();
if ( soapVersion == SoapVersion.SOAP_12 ) {
gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope responseEnvelope = new gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope();
responseEnvelope.setBody$( new gw.internal.schema.gw.xsd.w3c.soap12_envelope.Body() );
gw.internal.schema.gw.xsd.w3c.soap12_envelope.Fault soapFaultElem = new gw.internal.schema.gw.xsd.w3c.soap12_envelope.Fault();
Fault_Code faultCode = new Fault_Code();
FaultcodeEnum faultCodeValue = FaultcodeEnum.Receiver;
if ( wsdlFault.getCode() != null ) {
switch ( wsdlFault.getCode() ) {
case DataEncodingUnknown:
faultCodeValue = FaultcodeEnum.DataEncodingUnknown;
break;
case MustUnderstand:
faultCodeValue = FaultcodeEnum.MustUnderstand;
break;
case Sender:
faultCodeValue = FaultcodeEnum.Sender;
break;
case VersionMismatch:
faultCodeValue = FaultcodeEnum.VersionMismatch;
break;
}
}
faultCode.setValue$( faultCodeValue );
soapFaultElem.setCode$( faultCode );
soapFaultElem.setReason$( new Fault_Reason() );
soapFaultElem.Reason().setText$( Collections.singletonList( message ) );
soapFaultElem.Reason().Text_elem().get( 0 ).setLang$( "" );
responseEnvelope.Body().addChild( soapFaultElem );
if ( detailElement != null ) {
soapFaultElem.setDetail$( new gw.internal.schema.gw.xsd.w3c.soap12_envelope.anonymous.elements.Fault_Detail() );
soapFaultElem.Detail().addChild( detailElement );
}
if ( webservice._responseXmlTransform != null ) {
webservice._responseXmlTransform.invoke( responseEnvelope );
}
writeResponseEnvelope( responseEnvelope, os, context, webservice, responseAdapter, soapVersion );
} else {
gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope responseEnvelope = new gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope();
responseEnvelope.setBody$( new gw.internal.schema.gw.xsd.w3c.soap11_envelope.Body() );
gw.internal.schema.gw.xsd.w3c.soap11_envelope.Fault soapFaultElem = new gw.internal.schema.gw.xsd.w3c.soap11_envelope.Fault();
QName soap11FaultCodeQName = GENERIC_FAULT_TO_SOAP11_FAULT.get( wsdlFault.getCode() );
if ( soap11FaultCodeQName == null ) {
soap11FaultCodeQName = GENERIC_FAULT_TO_SOAP11_FAULT.get( WsdlFault.FaultCode.Receiver );
}
soapFaultElem.setFaultcode$( soap11FaultCodeQName );
soapFaultElem.setFaultstring$( message == null ? "" : message );
soapFaultElem.setFaultactor$( wsdlFault.getActorRole() );
responseEnvelope.Body().addChild( soapFaultElem );
if ( detailElement != null ) {
soapFaultElem.setDetail$( new gw.internal.schema.gw.xsd.w3c.soap11_envelope.anonymous.elements.Fault_Detail() );
soapFaultElem.Detail().addChild( detailElement );
}
if ( webservice._responseXmlTransform != null ) {
webservice._responseXmlTransform.invoke( responseEnvelope );
}
writeResponseEnvelope( responseEnvelope, os, context, webservice, responseAdapter, soapVersion );
}
}
private void logWsdlFault( final boolean expectedFault, final Throwable throwable ) {
if ( expectedFault ) {
if ( getILogger().isDebugEnabled() ) {
getILogger().debug( "DEBUG: EXPECTED FAULT RECEIVED", throwable );
}
getILogger().info( TypeSystem.getTypeFromObject( throwable ).getName() + ": " + throwable.getLocalizedMessage() );
} else {
getILogger().info( "Unexpected exception", throwable );
}
}
private static String getContentType( SoapVersion soapVersion ) {
if ( soapVersion == SoapVersion.SOAP_12 ) {
return "application/soap+xml;charset=utf-8";
} else {
return "text/xml;charset=utf-8";
}
}
private XmlElement getBodyFromEnv( XmlElement envelope, SoapVersion soapVersion ) {
XmlElement body;
if ( soapVersion == SoapVersion.SOAP_12 ) {
gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope env = (gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope) envelope;
body = env.Body();
} else {
gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope env = (gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope) envelope;
body = env.Body();
}
return body;
}
private WsiInvocationContextImpl.WebService getWebServiceForType( IType type ) {
IGosuClass gosuClass = (IGosuClass) type;
WsiInvocationContextImpl.WebService webservice;
TypeSystem.lock();
try {
webservice = _webservices.get( gosuClass );
// check if webservice code has changed
Class<?> backingClass = gosuClass.getBackingClass();
//noinspection ObjectEquality
if ( webservice != null && webservice._backingClass != backingClass ) {
_webservices.remove( gosuClass );
webservice = null;
}
if ( webservice == null ) {
webservice = new WsiInvocationContextImpl.WebService();
webservice._backingClass = backingClass;
webservice._worker = type.getTypeInfo().getConstructor().getConstructor().newInstance();
resolveRequestTransformAnnotation( type, webservice );
resolveResponseTransformAnnotation( type, webservice );
resolveRequestXmlTransformAnnotation( type, webservice );
resolveResponseXmlTransformAnnotation( type, webservice );
resolveInvocationHandlerAnnotation( type, webservice );
XmlParseOptions parseOptions;
XmlSerializationOptions serializationOptions;
IAnnotationInfo parseOptionsAnnotation = type.getTypeInfo().getAnnotation( _wsiParseOptionsAnnotationType );
if ( parseOptionsAnnotation != null ) {
Object instance = parseOptionsAnnotation.getInstance();
parseOptions = (XmlParseOptions) _wsiParseOptionsAnnotationType.getTypeInfo().getProperty( "ParseOptions" ).getAccessor().getValue( instance );
if ( parseOptions == null ) {
throw new IllegalArgumentException( "null argument to " + _wsiParseOptionsAnnotationType.getName() + " annotation on class " + type.getName() );
}
parseOptions = parseOptions.copy();
} else {
parseOptions = new XmlParseOptions();
}
final List<XmlSchemaIndex> additionalSchemaIndexes = new ArrayList<XmlSchemaIndex>();
for ( XmlSchemaAccess xmlSchemaAccess : parseOptions.getAdditionalSchemas() ) {
additionalSchemaIndexes.add( ( (XmlSchemaAccessImpl) xmlSchemaAccess ).getSchemaIndex() );
}
IModule defaultModule = TypeSystem.getGlobalModule();
additionalSchemaIndexes.add( XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace( defaultModule, "gw.xsd.w3c.soap12_envelope" ) );
additionalSchemaIndexes.add( XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace( defaultModule, "gw.xsd.w3c.soap11_envelope" ) );
additionalSchemaIndexes.add( XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace( defaultModule, "gw.xsd.guidewire.soapheaders" ) );
IAnnotationInfo serializationOptionsAnnotation = type.getTypeInfo().getAnnotation( _wsiSerializationOptionsAnnotationType );
if ( serializationOptionsAnnotation != null ) {
Object instance = serializationOptionsAnnotation.getInstance();
serializationOptions = (XmlSerializationOptions) _wsiSerializationOptionsAnnotationType.getTypeInfo().getProperty( "SerializationOptions" ).getAccessor().getValue( instance );
if ( serializationOptions == null ) {
throw new IllegalArgumentException( "null argument to " + _wsiSerializationOptionsAnnotationType.getName() + " annotation on class " + type.getName() );
}
serializationOptions = serializationOptions.copy();
} else {
serializationOptions = null;
}
StringBuilder xsdRootURL = new StringBuilder();
String[] parts = type.getName().split( "\\." );
for ( int i = 0; i < parts.length - 1; i++ ) {
if ( xsdRootURL.length() > 0 ) {
xsdRootURL.append( '/' );
}
xsdRootURL.append( ".." );
}
final WsiServiceInfo serviceInfo = WsiUtilities.generateWsdl( type, "urn:ignored", xsdRootURL.toString(), this );
for ( XmlSchemaIndex schema : serviceInfo.getSchemas() ) {
additionalSchemaIndexes.add( schema );
}
List<XmlSchemaAccess> additionalSchemaAccesses = new ArrayList<XmlSchemaAccess>();
for ( XmlSchemaIndex schemaIndex : additionalSchemaIndexes ) {
additionalSchemaAccesses.add( schemaIndex.getXmlSchemaAccess() );
}
parseOptions.setAdditionalSchemas( additionalSchemaAccesses );
final Schema schemaForValidation;
try {
// this is necessary since there is no XmlSchemaAccess or XmlSchemaIndex for the wsdl representing this service
schemaForValidation = XmlSchemaIndex.parseSchemasButDoNotCache( additionalSchemaIndexes, serviceInfo.getWsdl(), serviceInfo.getWebserviceType() );
}
catch ( Exception ex ) {
throw GosuExceptionUtil.forceThrow( ex );
}
XmlTypeResolver typeResolver = new XmlTypeResolver() {
@Override
public Pair<IType, IType> resolveTypes( List<QName> elementStack ) {
// Resolve elements/typeinstances at a depth that they are actually based on a schema on the server side,
// since the elements containing them are imaginary as far as the XSD typeloader is concerned.
int elementStackDepth = elementStack.size();
if ( elementStackDepth > 2 && elementStack.get( 1 ).getLocalPart().equals( "Body" ) ) {
if ( elementStackDepth >= 4 ) {
QName opName = elementStack.get( 2 );
QName paramName = elementStack.get( 3 );
Map<QName, MarshalInfo> map = serviceInfo.getMarshalInfoMap().get( opName );
if ( map != null ) {
MarshalInfo marshalInfo = map.get( paramName );
if ( marshalInfo != null ) {
int arrayLevels = 0;
MarshalInfo componentMarshalInfo = marshalInfo;
while ( true ) {
if ( componentMarshalInfo instanceof ArrayMarshalInfo ) {
arrayLevels++;
componentMarshalInfo = ( (ArrayMarshalInfo) componentMarshalInfo ).getComponentMarshalInfo();
} else if ( componentMarshalInfo instanceof ListMarshalInfo ) {
arrayLevels++;
componentMarshalInfo = ( (ListMarshalInfo) componentMarshalInfo ).getComponentMarshalInfo();
} else {
break;
}
}
if ( elementStackDepth == 4 + arrayLevels && componentMarshalInfo instanceof XmlTypeInstanceMarshalInfo ) {
return new Pair<IType, IType>( null, componentMarshalInfo.getType() );
} else if ( elementStackDepth == 5 + arrayLevels && componentMarshalInfo instanceof XmlElementMarshalInfo ) {
// If typed as XmlElement, processContents is skip, so skip typing
if ( !TypeSystem.get( XmlElement.class ).equals( componentMarshalInfo.getType() ) ) {
for ( XmlSchemaIndex schema : serviceInfo.getSchemas() ) {
QName elementName = elementStack.get( 4 + arrayLevels );
try {
XmlSchemaElement element = schema.getXmlSchemaElementByQName( elementName );
IType elementType = XmlSchemaIndex.getGosuTypeBySchemaObject( element );
if ( elementType == null ) {
throw new IllegalStateException( "Element " + elementName + " not found in schema " + schema );
}
return new Pair<IType, IType>( elementType, null );
} catch ( NotFoundException ex ) {
// continue
}
}
}
}
}
}
}
}
return new Pair<IType, IType>( null, null );
}
@Override
public Schema getSchemaForValidation() {
return schemaForValidation;
}
@Override
public String getValidationSchemasDescription() {
StringBuilder sb = new StringBuilder();
sb.append( "@WsiWebService " );
sb.append( serviceInfo.getWebserviceType().getName() );
if ( ! additionalSchemaIndexes.isEmpty() ) {
TreeSet<String> sortedSet = new TreeSet<String>();
for ( XmlSchemaIndex schemaIndex : additionalSchemaIndexes ) {
sortedSet.add( schemaIndex.getPackageName() );
}
sb.append( " and schemas [" );
boolean first = true;
for ( String schemaPackageName : sortedSet ) {
if ( ! first ) {
sb.append( ", " );
}
first = false;
sb.append( schemaPackageName );
}
sb.append( "]" );
}
return sb.toString();
}
};
webservice._parseOptions = parseOptions;
webservice._serializationOptions = serializationOptions;
webservice._typeResolver = typeResolver;
webservice._serviceInfo = serviceInfo;
_webservices.put( gosuClass, webservice );
}
} finally {
TypeSystem.unlock();
}
return webservice;
}
private void resolveRequestTransformAnnotation( IType type, WsiInvocationContextImpl.WebService webservice ) {
IAnnotationInfo requestTransformAnnotation = type.getTypeInfo().getAnnotation( _requestTransformType );
if ( requestTransformAnnotation != null ) {
Object instance = requestTransformAnnotation.getInstance();
webservice._requestTransform = (Function1) _requestTransformType.getTypeInfo().getProperty( "Transform" ).getAccessor().getValue( instance );
}
}
private void resolveResponseTransformAnnotation( IType type, WsiInvocationContextImpl.WebService webservice ) {
IAnnotationInfo responseTransformAnnotation = type.getTypeInfo().getAnnotation( _responseTransformType );
if ( responseTransformAnnotation != null ) {
Object instance = responseTransformAnnotation.getInstance();
webservice._responseTransform = (Function1) _responseTransformType.getTypeInfo().getProperty( "Transform" ).getAccessor().getValue( instance );
}
}
private void resolveRequestXmlTransformAnnotation( IType type, WsiInvocationContextImpl.WebService webservice ) {
IAnnotationInfo xmlTransformAnnotation = type.getTypeInfo().getAnnotation( _requestXmlTransformType );
if ( xmlTransformAnnotation != null ) {
Object instance = xmlTransformAnnotation.getInstance();
webservice._requestXmlTransform = (Function1) _requestXmlTransformType.getTypeInfo().getProperty( "Transform" ).getAccessor().getValue( instance );
}
}
private void resolveResponseXmlTransformAnnotation( IType type, WsiInvocationContextImpl.WebService webservice ) {
IAnnotationInfo xmlTransformAnnotation = type.getTypeInfo().getAnnotation( _responseXmlTransformType );
if ( xmlTransformAnnotation != null ) {
Object instance = xmlTransformAnnotation.getInstance();
webservice._responseXmlTransform = (Function1) _responseXmlTransformType.getTypeInfo().getProperty( "Transform" ).getAccessor().getValue( instance );
}
}
private void resolveInvocationHandlerAnnotation( IType type, WsiInvocationContextImpl.WebService webservice ) {
IAnnotationInfo invocationHandlerAnnotationInfo = type.getTypeInfo().getAnnotation( _wsiInvocationHandlerType );
if ( invocationHandlerAnnotationInfo != null ) {
Object instance = invocationHandlerAnnotationInfo.getInstance();
webservice._invocationHandler = (DefaultWsiInvocationHandler) _wsiInvocationHandlerType.getTypeInfo().getProperty( "InvocationHandler" ).getAccessor().getValue( instance );
} else {
webservice._invocationHandler = new DefaultWsiInvocationHandler();
}
}
public void preUnmarshal( IType type, XmlElement headersFromEnvelope, XmlElement body, IMethodInfo method, List<Callable> finallyList, WebservicesRequest request, QName opName ) throws IOException {
}
@Override
protected void service( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException {
Throwable throwable = null;
try {
super.service( req, resp );
}
catch ( Throwable t ) {
throwable = t;
throw GosuExceptionUtil.forceThrow( t );
}
finally {
for (WebservicesServletBaseListenerForTesting listener : _listenersForTesting ) {
listener.requestComplete( req, throwable );
}
}
}
public static void addListenerForTesting( WebservicesServletBaseListenerForTesting listener ) {
_listenersForTesting.add( listener );
}
public static void removeListenerForTesting( WebservicesServletBaseListenerForTesting listener ) {
_listenersForTesting.remove( listener );
}
@Override
protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException {
doGet( new ServletWebservicesRequest( request ), new ServletWebservicesResponseAdapter( response, getILogger() ) );
}
public void doGet( WebservicesRequest request, WebservicesResponseAdapter responseAdapter ) throws ServletException {
beforeInvoke( request, responseAdapter );
try {
maybeInit();
String path = request.getPathInfo();
String queryStr = request.getQueryString();
if ( path == null ) {
path = "";
}
String fullRequest = request.getRequestURL();
String prefix;
if ( path.length() == 0 ) {
int idx = fullRequest.lastIndexOf( '/' );
prefix = fullRequest.substring( idx + 1 ) + "/";
path = "/";
} else {
prefix = "";
}
if ( path.equals( "/" ) ) {
if ( !_hideServiceListPage ) {
doGetIndex( responseAdapter, prefix );
}
} else if ( path.startsWith( "/resources." ) ) {
if ( RESOURCES.containsKey( path ) ) {
try {
Pair<String, String> pair = RESOURCES.get( path );
String relativePath = pair.getFirst();
String contentType = pair.getSecond();
responseAdapter.setContentType( contentType );
IModule module = TypeSystem.getCurrentModule();
IFile resourceFile = module.getFileRepository().findFirstFile( relativePath );
StreamUtil.copy( resourceFile.openInputStream(), responseAdapter.getOutputStream() );
} catch ( IOException ex ) {
throw new ServletException( ex );
}
} else {
getILogger().info( "Webservices servlet: 404 Not Found: " + path );
send404NotFound( responseAdapter );
}
} else if ( path.endsWith( XSD_SUFFIX ) ) {
doGetXSD( responseAdapter, path.substring( 1 ) );
} else if ( path.endsWith( WSDL_SUFFIX ) ) {
doGetXSD( responseAdapter, path.substring( 1 ) );
} else if ( path.endsWith( GX_SUFFIX ) ) {
doGetXSD( responseAdapter, path.substring( 1 ) );
} else {
if ( queryStr != null && queryStr.equalsIgnoreCase( "wsdl" ) ) {
Pair<IType, SoapVersion> pair = getWebServiceInfo(request);
if ( pair != null ) {
doGetWSDL( responseAdapter, pair.getFirst(), request.getRequestURL() );
} else {
getILogger().info( "Webservices servlet: 404 Not Found (WSDL): " + path );
send404NotFound( responseAdapter );
}
} else {
getILogger().info( "Webservices servlet: 404 Not Found (other): " + path );
send404NotFound( responseAdapter );
}
}
} finally {
afterInvoke( request, responseAdapter );
}
}
private void maybeInit() {
if ( _initialized ) {
return;
}
TypeSystem.lock();
try {
if ( _initialized ) {
return;
}
_requestTransformType = TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiRequestTransform" );
_responseTransformType = TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiResponseTransform" );
_requestXmlTransformType = TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiRequestXmlTransform" );
_responseXmlTransformType = TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiResponseXmlTransform" );
_wsiInvocationHandlerType = TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiInvocationHandler" );
_wsiParseOptionsAnnotationType = TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiParseOptions" );
_wsiSerializationOptionsAnnotationType = TypeSystem.getByFullName( "gw.xml.ws.annotation.WsiSerializationOptions" );
_hideServiceListPage = getBooleanValue( getServletConfig(), CONFIG_PARAM_HIDE_SERVICE_LIST_PAGE, false );
final String path = getStringValue( getServletConfig(), CONFIG_PARAM_AVAILABLE_SERVICES, null );
if ( path != null && path.length() > 0 ) {
_initializedFromConfig = true;
initAvailable( path );
}
if ( !_initializedFromConfig ) {
initAvailable();
}
_initialized = true;
TypeSystem.addTypeLoaderListenerAsWeakRef(this);
} finally {
TypeSystem.unlock();
}
}
private Pair<IType, SoapVersion> getWebServiceInfo(WebservicesRequest request) {
String soapVersionString = null;
String fqName = request.getPathInfo().substring( 1 ).replace( "/", "." ); // /foo/bar -> foo.bar (path to namespace)
IType type = TypeSystem.getByFullNameIfValid( fqName );
if ( type == null ) {
soapVersionString = GosuClassUtil.getNameNoPackage( fqName );
fqName = GosuClassUtil.getPackage( fqName );
type = TypeSystem.getByFullNameIfValid( fqName );
}
if ( type instanceof IGosuClass && checkWebServiceForErrors( type ) == null && isWebserviceAvailable( type ) ) {
SoapVersion soapVersion;
if ( soapVersionString == null || soapVersionString.equals( "soap12" ) ) {
soapVersion = SoapVersion.SOAP_12;
} else if ( soapVersionString.equals( "soap11" ) ) {
soapVersion = SoapVersion.SOAP_11;
} else {
return null;
}
return new Pair<IType, SoapVersion>( type, soapVersion );
}
return null;
}
private void send404NotFound( WebservicesResponseAdapter responseAdapter ) {
try {
responseAdapter.setStatus( HttpServletResponse.SC_NOT_FOUND );
responseAdapter.setContentType( "text/plain;charset=utf-8" );
responseAdapter.getOutputStream().write( StreamUtil.toBytes( "404 Not Found" ) );
} catch ( IOException ex ) {
throw GosuExceptionUtil.forceThrow( ex );
}
}
protected void printIndexBody( DftreeNode node, String reqPath ) throws IOException {
printIndexWebServices( node, reqPath );
Set<String> xsds = new TreeSet<String>();
Set<String> wsdls = new TreeSet<String>();
extractXmlResourcesForIndex( xsds, wsdls );
printIndexSchemas( node, reqPath, xsds );
printIndexWSDLs( node, reqPath, wsdls );
}
protected void printIndexWebServices( DftreeNode node, String reqPath ) throws IOException {
TreeMap<String, String> path2TypeName = new TreeMap<String, String>();
for ( CharSequence typeNameCS : getAllWebserviceTypes() ) {
String typeName = typeNameCS.toString();
IType type = TypeSystem.getByFullNameIfValid( typeName );
if ( type != null ) {
if ( checkWebServiceForErrors( type ) == null && isWebserviceAvailable( type ) ) {
String nsPath = typeName.replace( ".", "/" );
path2TypeName.put( reqPath + nsPath, typeName );
}
}
}
if ( !path2TypeName.isEmpty() ) {
DftreeNode category = new DftreeNode( "Document/Literal Web Services" );
node.addChild( category );
for ( Map.Entry<String, String> entry : path2TypeName.entrySet() ) {
addDftreeChild( category, entry.getValue().replace( '.', '/' ), entry.getKey() + "?WSDL" );
}
}
}
protected void printIndexWSDLs( DftreeNode node, String reqPath, Set<String> wsdls ) throws IOException {
DftreeNode category = new DftreeNode( "Supporting WSDLs" );
node.addChild( category );
for ( String path : wsdls ) {
addDftreeChild( category, path, reqPath + path );
}
}
protected void printIndexSchemas( DftreeNode node, String reqPath, Set<String> xsds ) throws IOException {
DftreeNode category = new DftreeNode( "Supporting Schemas" );
node.addChild( category );
for ( String path : xsds ) {
String url = reqPath + path;
addDftreeChild( category, path, url );
}
}
protected void addDftreeChild( DftreeNode parent, String name, String url ) {
int idx = name.indexOf( '/' );
if ( idx < 0 ) {
DftreeNode child = new DftreeNode( name );
parent.addChild( child );
child.setTargetPath( url );
} else {
String start = name.substring( 0, idx );
String rest = name.substring( idx + 1 );
DftreeNode child = null;
if ( parent.getChildren() != null ) {
// find existing child if available
for ( DftreeNode dftreeNode : parent.getChildren() ) {
if ( dftreeNode.getName().equals( start ) ) {
child = dftreeNode;
break;
}
}
}
if ( child == null ) {
child = new DftreeNode( start );
parent.addChild( child );
}
addDftreeChild( child, rest, url );
}
}
protected void extractXmlResourcesForIndex(Set<String> xsds, Set<String> wsdls) {
for ( XmlSchemaResourceTypeLoaderBase<?> typeLoader : TypeSystem.getGlobalModule().getTypeLoaders( XmlSchemaResourceTypeLoaderBase.class ) ) {
String tpClassName = typeLoader.getClass().getName();
if ( tpClassName.equals( "gw.internal.xml.xsd.typeprovider.XmlSchemaResourceTypeLoader" ) ) {
for ( String ns : typeLoader.getAllSchemaNamespaces() ) {
final XmlSchemaIndex<?> schemaForNamespace = typeLoader.getSchemaForNamespace( ns );
final String path = schemaForNamespace.getXSDSourcePath();
xsds.add( path );
}
} else if ( tpClassName.equals( "com.guidewire.commons.system.gx.GXTypeLoader" ) ) {
for ( String ns : typeLoader.getAllSchemaNamespaces() ) {
final XmlSchemaIndex<?> schemaForNamespace = typeLoader.getSchemaForNamespace( ns );
final String path = schemaForNamespace.getXSDSourcePath();
xsds.add( path );
}
} else if ( tpClassName.equals( "gw.internal.xml.ws.typeprovider.WsdlTypeLoader" ) ) {
for ( String ns : typeLoader.getAllSchemaNamespaces() ) {
if ( !ns.startsWith( "wsi.local." ) ) {
final XmlSchemaIndex<?> schemaForNamespace = typeLoader.getSchemaForNamespace( ns );
final String path = schemaForNamespace.getXSDSourcePath();
wsdls.add( path );
}
}
} else {
throw new RuntimeException( "Unrecognized typeloader class: " + tpClassName );
}
}
}
private Collection<String> getAllWebserviceTypes() {
TypeSystem.lock();
try {
return _availableServices;
} finally {
TypeSystem.unlock();
}
}
protected void doGetXSD( WebservicesResponseAdapter responseAdapter, String path ) throws ServletException {
try {
boolean found = false;
OUTER:
for ( XmlSchemaResourceTypeLoaderBase<?> typeLoader : TypeSystem.getGlobalModule().getTypeLoaders( XmlSchemaResourceTypeLoaderBase.class ) ) {
final Collection<String> namespaces = typeLoader.getAllSchemaNamespaces();
for ( String ns : namespaces ) {
final XmlSchemaIndex si = typeLoader.getSchemaForNamespace( ns );
if ( si.getXSDSourcePath().equals( path ) ) {
found = true;
responseAdapter.setContentType( "text/xml" );
StreamUtil.copy( si.getXSDSource().getInputStream( false ), responseAdapter.getOutputStream() );
responseAdapter.setStatus( HttpServletResponse.SC_OK );
break OUTER;
}
}
}
if ( !found ) {
getILogger().info( "Webservices servlet: 404 Not Found (XSD): " + path );
send404NotFound( responseAdapter );
}
} catch ( IOException ex ) {
throw GosuExceptionUtil.forceThrow( ex );
}
}
private void doGetWSDL( WebservicesResponseAdapter responseAdapter, IType type, String requestURL ) {
try {
String xsdRootURL = getXsdRootURL( type );
WsiServiceInfo serviceInfo = WsiUtilities.generateWsdl( type, requestURL, xsdRootURL, this );
responseAdapter.setContentType( "text/xml" );
serviceInfo.getWsdl().writeTo( responseAdapter.getOutputStream(), new XmlSerializationOptions().withSort( false ) );
responseAdapter.setStatus( HttpServletResponse.SC_OK );
} catch ( IOException e ) {
throw GosuExceptionUtil.forceThrow( e );
}
}
public static String checkWebServiceForErrors( IType type ) {
if ( type == null ) {
return "Null webservice type";
}
ITypeInfo typeInfo = type.getTypeInfo();
if ( !( type instanceof IGosuClass ) ) {
return "Type is not a Gosu class";
}
if ( !( typeInfo.hasAnnotation( _wsiWebServiceAnnotationType.get() ) ) ) {
return "Type does not have @WsiWebService annotation";
}
if ( !type.isValid() ) {
IGosuClass clazz = (IGosuClass) type;
//noinspection ThrowableResultOfMethodCallIgnored
return "Type is not valid." + ( clazz.getParseResultsException() == null ? "" : ( "\n\n" + GosuExceptionUtil.getStackTraceAsString( clazz.getParseResultsException() ) ) );
}
return null;
}
private boolean isWebserviceAvailable( IType type ) {
if ( _wsiLocal ) {
return true;
}
TypeSystem.lock();
try {
return _availableServices.contains( type.getName() );
} finally {
TypeSystem.unlock();
}
}
private String getXsdRootURL( IType type ) {
StringBuilder xsdRootURL = new StringBuilder();
String[] parts = GosuClassUtil.getPackage( type.getName() ).split( "\\." );
int numEntries = parts.length;
for ( int i = 0; i < numEntries; i++ ) {
xsdRootURL.append( "../" );
}
return xsdRootURL.toString();
}
@SuppressWarnings( { "StringConcatenationInsideStringBufferAppend" } )
protected void doGetIndex( WebservicesResponseAdapter responseAdapter, String path ) {
try {
responseAdapter.setContentType( "text/html;charset=utf-8" );
responseAdapter.setStatus( HttpServletResponse.SC_OK );
PrintWriter out = new PrintWriter( StreamUtil.getOutputStreamWriter( responseAdapter.getOutputStream() ) );
out.append( "<html><head><script type=\"text/javascript\" src=\"" + path + "resources.dftree/dftree.js\"></script>" );
out.append( "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + path + "resources.dftree/dftree.css\">" );
out.append( "<title>Available Webservice-related Resources</title></head><body>" );
out.append( "<script language=\"javascript\">\n<!--\n" );
out.append( "tree = new dFTree({name: 'tree',useIcons:true, icondir:'" + path + "resources.dftree/img'});\n" );
DftreeNode root = new DftreeNode( "root" );
printIndexBody( root, path );
int[] nextId = { 1 };
for ( DftreeNode dftreeNode : root.getChildren() ) {
generateDftreeCode( dftreeNode, 0, out, nextId );
}
out.append( "tree.draw();\n" );
out.append( "-->\n" );
out.append( "</script></body></html>" );
out.flush();
} catch ( IOException e ) {
throw GosuExceptionUtil.forceThrow( e );
}
}
@SuppressWarnings( { "StringConcatenationInsideStringBufferAppend" } )
private void generateDftreeCode( DftreeNode dftreeNode, int parentId, PrintWriter out, int[] nextId ) {
int myId = nextId[ 0 ]++;
out.append( "tree.add(new dNode({id: '" + myId + "',caption: '" + dftreeNode.getName() + "'" );
String targetPath = dftreeNode.getTargetPath();
if ( targetPath != null ) {
if ( targetPath.startsWith( "/" ) ) {
targetPath = targetPath.substring( 1 ); // Weblogic workaround - we should remove this at some point and find the real problem
}
out.append( ", url: '" + targetPath + "'" );
}
if ( dftreeNode.getChildren() == null ) {
out.append( ", isFolder:0" );
}
out.append( "})," + parentId + ");\n" );
if ( dftreeNode.getChildren() != null ) {
Collections.sort( dftreeNode.getChildren(), new Comparator<DftreeNode>() {
@Override
public int compare( DftreeNode o1, DftreeNode o2 ) {
int result = Boolean.valueOf( o1.getChildren() == null ).compareTo( o2.getChildren() == null );
if ( result == 0 ) {
result = o1.getName().compareTo( o2.getName() );
}
return result;
}
} );
for ( DftreeNode child : dftreeNode.getChildren() ) {
generateDftreeCode( child, myId, out, nextId );
}
}
}
public static void setDefaultLocalWebservicesServletClass( Class<? extends WebservicesServletBase> clazz ) {
_defaultLocalWebservicesServletClass = clazz;
_defaultLocalWebservicesServlet.clear();
}
// Called from Gosu code
@SuppressWarnings( { "UnusedDeclaration" } )
public static WebservicesServletBase getDefaultLocalWebservicesServlet() {
return _defaultLocalWebservicesServlet.get();
}
public boolean isWsiLocal() {
return _wsiLocal;
}
public abstract void postConfigureWebservice( IType type, WsiServiceInfo serviceInfo );
public String postConfigureWebservicePath( IType type, String xsdRootPath ) {
return "";
}
public static Map<WsiRequestLocal, ?> getRequestLocalMapForCurrentThread() {
Map<WsiRequestLocal, ?> ret = _requestLocals.get();
if ( ret == null ) {
throw new IllegalStateException( "The current thread is not a webservice request thread" );
}
return ret;
}
private XmlElement getHeadersFromEnvelope( XmlElement envelope, SoapVersion soapVersion ) {
XmlElement headersFromEnvelope;
if ( soapVersion == SoapVersion.SOAP_12 ) {
gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope env = (gw.internal.schema.gw.xsd.w3c.soap12_envelope.Envelope) envelope;
headersFromEnvelope = env.Header();
} else {
gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope env = (gw.internal.schema.gw.xsd.w3c.soap11_envelope.Envelope) envelope;
headersFromEnvelope = env.Header();
}
return headersFromEnvelope;
}
@Override
public void refreshedTypes( RefreshRequest request ) {
}
@Override
public void refreshed() {
onRefreshTypeSystem();
}
}