package er.extensions.appserver.ws;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.xml.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.istack.NotNull;
import com.sun.xml.ws.api.BindingID;
import com.sun.xml.ws.api.message.ExceptionHasMessage;
import com.sun.xml.ws.api.message.Message;
import com.sun.xml.ws.api.message.Packet;
import com.sun.xml.ws.api.pipe.Codec;
import com.sun.xml.ws.api.pipe.ContentType;
import com.sun.xml.ws.api.server.DocumentAddressResolver;
import com.sun.xml.ws.api.server.PortAddressResolver;
import com.sun.xml.ws.api.server.SDDocument;
import com.sun.xml.ws.api.server.ServiceDefinition;
import com.sun.xml.ws.api.server.WSEndpoint;
import com.sun.xml.ws.api.server.WSEndpoint.PipeHead;
import com.sun.xml.ws.binding.BindingImpl;
import com.sun.xml.ws.server.UnsupportedMediaException;
import com.sun.xml.ws.transport.http.WSHTTPConnection;
import com.sun.xml.ws.util.ByteArrayBuffer;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WODynamicURL;
import com.webobjects.appserver.WOMessage;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import er.extensions.appserver.ERXRequest;
import er.extensions.appserver.ERXResourceManager;
import er.extensions.appserver.ERXResponse;
/**
* @author mstoll
*
* @param <T>
*/
public class ERJaxWebService<T>
{
private static final Logger log = LoggerFactory.getLogger(ERJaxWebService.class);
/**
*
*/
protected WSEndpoint<T> wsEndpoint;
/**
*
*/
protected PipeHead pipeHead;
/**
*
*/
protected Codec codec;
/**
*
*/
protected Map<String, SDDocument> wsdls;
/**
*
*/
private Map<SDDocument, String> revWsdls;
public ERJaxWebService(Class<T> implementationClass)
{
wsEndpoint = WSEndpoint.create(implementationClass, false, null, null, null, null,
BindingImpl.create(BindingID.parse(implementationClass)), null, null, null, true);
pipeHead = wsEndpoint.createPipeHead();
codec = wsEndpoint.createCodec();
initWSDLMap(wsEndpoint.getServiceDefinition());
}
/**
* @param woRequest
* @return
*/
public WOResponse handleRequest(WORequest woRequest)
{
if(isMetadataQuery(woRequest.queryString()))
{
SDDocument doc = wsdls.get(woRequest.queryString());
if(doc == null)
{
ERXResponse resp = new ERXResponse();
resp.setStatus(WOMessage.HTTP_STATUS_NOT_FOUND);
return resp;
}
ERXResponse resp = new ERXResponse();
resp.setStatus(HttpURLConnection.HTTP_OK);
resp.setHeader("text/xml;charset=utf-8", "Content-Type");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WODynamicURL du = woRequest._uriDecomposed();
String baseUri = String.format("%s/%s.woa/%s/%s",
du.prefix(),
du.applicationName(),
du.requestHandlerKey(),
du.requestHandlerPath());
boolean isSecure;
if(woRequest instanceof ERXRequest)
{
isSecure = ((ERXRequest)woRequest).isSecure();
} else
{
isSecure = ERXRequest.isRequestSecure(woRequest);
}
String soapAddress = ERXResourceManager._completeURLForResource(
baseUri,
isSecure,
WOApplication.application().createContextForRequest(woRequest)
);
try
{
doc.writeTo(new ERPortAddressResolver(soapAddress),
new ERDocumentAddressResolver(soapAddress),
baos);
baos.flush();
}
catch(IOException e)
{
}
resp.setContent(baos.toByteArray());
return resp;
}
ERWSWOHTTPConnection con = new ERWSWOHTTPConnection(woRequest);
try
{
Packet packet;
boolean invoke = false;
try
{
packet = decodePacket(con, codec);
invoke = true;
}
catch(Exception e)
{
packet = new Packet();
log.error("Could not decode packet.", e);
if(e instanceof ExceptionHasMessage)
{
packet.setMessage(((ExceptionHasMessage)e).getFaultMessage());
}
else if(e instanceof UnsupportedMediaException)
{
con.setStatus(415);
}
else
{
con.setStatus(500);
}
}
if(invoke)
{
try
{
packet = pipeHead.process(packet, con.getWebServiceContextDelegate(),
packet.transportBackChannel);
}
catch(Exception e)
{
log.error("Could not process packet.", e);
return null;
}
}
try
{
encodePacket(packet, con, codec);
}
catch(IOException e)
{
log.error("Could not encode packet.", e);
}
}
finally
{
if(!con.isClosed())
{
con.close();
}
}
return con.generateResponse();
}
/**
* @param con
* @param codec
* @return
* @throws IOException
*/
private Packet decodePacket(WSHTTPConnection con, Codec codec)
throws IOException
{
String ct = con.getRequestHeader("Content-Type");
InputStream in = con.getInput();
Packet packet = new Packet();
packet.soapAction = fixQuotesAroundSoapAction(con.getRequestHeader("SOAPAction"));
packet.wasTransportSecure = con.isSecure();
packet.acceptableMimeTypes = con.getRequestHeader("Accept");
packet.addSatellite(con);
addSatellites(packet);
packet.webServiceContextDelegate = con.getWebServiceContextDelegate();
codec.decode(in, ct, packet);
return packet;
}
/**
* @param soapAction
* @return
*/
public static String fixQuotesAroundSoapAction(String soapAction)
{
if(soapAction != null && (!soapAction.startsWith("\"") || !soapAction.endsWith("\"")))
{
log.info("Received WS-I BP non-conformant Unquoted SoapAction HTTP header: {}", soapAction);
String fixedSoapAction = soapAction;
if(!soapAction.startsWith("\""))
fixedSoapAction = (new StringBuilder()).append("\"").append(fixedSoapAction).toString();
if(!soapAction.endsWith("\""))
fixedSoapAction = (new StringBuilder()).append(fixedSoapAction).append("\"").toString();
return fixedSoapAction;
}
else
{
return soapAction;
}
}
/**
* @param packet1
*/
protected void addSatellites(Packet packet1)
{
}
/**
* @param connStatus
* @return
*/
private boolean isClientErrorStatus(int connStatus)
{
return (connStatus == HttpURLConnection.HTTP_FORBIDDEN); // add more for future.
}
/**
* @param packet
* @param con
* @param codec
* @throws IOException
*/
private void encodePacket(@NotNull Packet packet, @NotNull WSHTTPConnection con, @NotNull Codec codec)
throws IOException
{
Message responseMessage = packet.getMessage();
if(responseMessage == null)
{
if(con.getStatus() == 0)
{
con.setStatus(WSHTTPConnection.ONEWAY);
}
}
else
{
if(con.getStatus() == 0)
{
// if the application didn't set the status code,
// set the default one.
con.setStatus(responseMessage.isFault()
? HttpURLConnection.HTTP_INTERNAL_ERROR
: HttpURLConnection.HTTP_OK);
}
if(isClientErrorStatus(con.getStatus()))
return;
ContentType contentType = codec.getStaticContentType(packet);
if(contentType != null)
{
con.setContentTypeResponseHeader(contentType.getContentType());
OutputStream os = con.getOutput();
codec.encode(packet, os);
}
else
{
ByteArrayBuffer buf = new ByteArrayBuffer();
contentType = codec.encode(packet, buf);
con.setContentTypeResponseHeader(contentType.getContentType());
OutputStream os = con.getOutput();
buf.writeTo(os);
}
}
}
/**
* @param sdef
*/
public final void initWSDLMap(ServiceDefinition sdef)
{
if(sdef == null)
{
wsdls = Collections.emptyMap();
revWsdls = Collections.emptyMap();
}
else
{
wsdls = new HashMap<>();
// wsdl=1 --> Doc
// Sort WSDL, Schema documents based on SystemId so that the same
// document gets wsdl=x mapping
Map<String, SDDocument> systemIds = new TreeMap<>();
for(SDDocument sdd : sdef)
{
if(sdd == sdef.getPrimary())
{
// No sorting for Primary WSDL
wsdls.put("wsdl", sdd);
wsdls.put("WSDL", sdd);
}
else
{
systemIds.put(sdd.getURL().toString(), sdd);
}
}
int wsdlnum = 1;
int xsdnum = 1;
for(Map.Entry<String, SDDocument> e : systemIds.entrySet())
{
SDDocument sdd = e.getValue();
if(sdd.isWSDL())
{
wsdls.put("wsdl=" + (wsdlnum++), sdd);
}
if(sdd.isSchema())
{
wsdls.put("xsd=" + (xsdnum++), sdd);
}
}
revWsdls = new HashMap<>(); // Doc --> wsdl=1
for(Entry<String, SDDocument> e : wsdls.entrySet())
{
if(!e.getKey().equals("WSDL"))
{ // map Doc --> wsdl, not WSDL
revWsdls.put(e.getValue(), e.getKey());
}
}
}
}
/**
* @param query
* @return
*/
private boolean isMetadataQuery(String query)
{
// we intentionally return true even if documents don't exist,
// so that they get 404.
return query != null && (query.equals("WSDL") || query.startsWith("wsdl") || query.startsWith("xsd="));
}
/**
* @author mstoll
*
*/
final class ERPortAddressResolver
extends PortAddressResolver
{
/**
*
*/
String base;
/**
* @param base
*/
public ERPortAddressResolver(String base)
{
super();
this.base = base;
}
/* (non-Javadoc)
* @see com.sun.xml.ws.api.server.PortAddressResolver#getAddressFor(javax.xml.namespace.QName, java.lang.String)
*/
@Override
public String getAddressFor(QName qname, String s)
{
return base;
}
};
/**
* @author mstoll
*
*/
final class ERDocumentAddressResolver
implements
DocumentAddressResolver
{
/**
*
*/
String base;
/**
* @param base
*/
public ERDocumentAddressResolver(String base)
{
super();
this.base = base;
}
/* (non-Javadoc)
* @see com.sun.xml.ws.api.server.DocumentAddressResolver#getRelativeAddressFor(com.sun.xml.ws.api.server.SDDocument, com.sun.xml.ws.api.server.SDDocument)
*/
@Override
public String getRelativeAddressFor(SDDocument sddocument, SDDocument referenced)
{
assert (revWsdls.containsKey(referenced));
return base + '?' + ((String)revWsdls.get(referenced));
}
};
}