blob: dc3f09eb6f5deaf43fba7dd92b122531b6fafbac [file] [log] [blame]
/*
* Portions Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.xml.internal.ws.encoding.soap;
import com.sun.xml.internal.ws.pept.encoding.Decoder;
import com.sun.xml.internal.ws.pept.ept.MessageInfo;
import com.sun.xml.internal.ws.pept.presentation.MessageStruct;
import com.sun.xml.internal.bind.api.BridgeContext;
import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.ContentType;
import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.ParseException;
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
import com.sun.xml.internal.ws.encoding.JAXWSAttachmentUnmarshaller;
import com.sun.xml.internal.ws.encoding.jaxb.JAXBBridgeInfo;
import com.sun.xml.internal.ws.encoding.jaxb.RpcLitPayload;
import com.sun.xml.internal.ws.encoding.jaxb.RpcLitPayloadSerializer;
import com.sun.xml.internal.ws.encoding.soap.internal.AttachmentBlock;
import com.sun.xml.internal.ws.encoding.soap.internal.BodyBlock;
import com.sun.xml.internal.ws.encoding.soap.internal.HeaderBlock;
import com.sun.xml.internal.ws.encoding.soap.internal.InternalMessage;
import com.sun.xml.internal.ws.encoding.soap.message.SOAPFaultInfo;
import com.sun.xml.internal.ws.encoding.soap.streaming.SOAPNamespaceConstants;
import com.sun.xml.internal.ws.handler.HandlerChainCaller;
import com.sun.xml.internal.ws.handler.HandlerContext;
import com.sun.xml.internal.ws.model.soap.SOAPRuntimeModel;
import com.sun.xml.internal.ws.server.RuntimeContext;
import com.sun.xml.internal.ws.streaming.SourceReaderFactory;
import com.sun.xml.internal.ws.streaming.XMLReaderException;
import com.sun.xml.internal.ws.streaming.XMLStreamReaderException;
import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
import com.sun.xml.internal.ws.util.MessageInfoUtil;
import com.sun.xml.internal.ws.util.SOAPUtil;
import com.sun.xml.internal.ws.client.dispatch.DispatchContext;
import com.sun.xml.internal.ws.client.dispatch.impl.encoding.DispatchUtil;
import com.sun.xml.internal.ws.client.BindingProviderProperties;
import com.sun.xml.internal.ws.util.FastInfosetReflection;
import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.soap.SOAPBinding;
import javax.xml.ws.soap.SOAPFaultException;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static javax.xml.stream.XMLStreamReader.*;
import javax.xml.bind.Unmarshaller;
/**
* @author WS Development Team
*/
public abstract class SOAPDecoder implements Decoder {
public final static String NOT_UNDERSTOOD_HEADERS =
"not-understood soap headers";
protected static final Logger logger = Logger.getLogger(
com.sun.xml.internal.ws.util.Constants.LoggingDomain + ".soap.decoder");
protected final static String MUST_UNDERSTAND_FAULT_MESSAGE_STRING =
"SOAP must understand error";
/* (non-Javadoc)
* @see com.sun.pept.encoding.Decoder#decode(com.sun.pept.ept.MessageInfo)
*/
public void decode(MessageInfo arg0) {
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see com.sun.pept.encoding.Decoder#receieveAndDecode(com.sun.pept.ept.MessageInfo)
*/
public void receiveAndDecode(MessageInfo arg0) {
throw new UnsupportedOperationException();
}
/**
* parses and binds headers, body from SOAPMessage.
*
* @param soapMessage
* @return the <code>InternalMessage</code> for the <code>soapMessage</code>
*/
public InternalMessage toInternalMessage(SOAPMessage soapMessage,
MessageInfo messageInfo) {
return null;
}
/**
* Returns the roles required for the type of binding. Returns
* an empty set if there are none.
*/
public Set<String> getRequiredRoles() {
return new HashSet<String>();
}
/**
* Parses and binds headers from SOAPMessage.
*
* @param soapMessage
* @param internalMessage
* @param messageInfo
* @return the InternalMessage representation of the SOAPMessage
*/
public InternalMessage toInternalMessage(SOAPMessage soapMessage,
InternalMessage internalMessage, MessageInfo messageInfo) {
return null;
}
public SOAPMessage toSOAPMessage(MessageInfo messageInfo) {
return null;
}
public void toMessageInfo(InternalMessage internalMessage, MessageInfo messageInfo) {
}
protected QName getEnvelopeTag() {
return SOAPConstants.QNAME_SOAP_ENVELOPE;
}
protected QName getBodyTag() {
return SOAPConstants.QNAME_SOAP_BODY;
}
protected QName getHeaderTag() {
return SOAPConstants.QNAME_SOAP_HEADER;
}
protected QName getMUAttrQName() {
return SOAPConstants.QNAME_MUSTUNDERSTAND;
}
protected QName getRoleAttrQName() {
return SOAPConstants.QNAME_ROLE;
}
protected QName getFaultTag() {
return SOAPConstants.QNAME_SOAP_FAULT;
}
protected QName getFaultDetailTag() {
return SOAPConstants.QNAME_SOAP_FAULT_DETAIL;
}
protected void skipBody(XMLStreamReader reader) {
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getBodyTag());
XMLStreamReaderUtil.skipElement(reader); // Moves to </Body>
XMLStreamReaderUtil.nextElementContent(reader);
}
protected void skipHeader(XMLStreamReader reader, MessageInfo messageInfo) {
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
if (!isDispatch(messageInfo))
return;
if (!SOAPNamespaceConstants.TAG_HEADER.equals(reader.getLocalName())) {
return;
}
//XMLStreamReaderUtil.verifyTag(reader, getHeaderTag());
dispatchUtil.collectPrefixes(reader);
XMLStreamReaderUtil.skipElement(reader); // Moves to </Header>
try {
reader.next();
} catch (Exception ex) {
ex.printStackTrace();
}
}
protected boolean skipHeader(MessageInfo messageInfo) {
if (messageInfo.getMetaData(DispatchContext.DISPATCH_MESSAGE_MODE) ==
Service.Mode.PAYLOAD) {
return true;
}
return false;
}
/*
* skipBody is true, the body is skipped during parsing.
*/
protected void decodeEnvelope(XMLStreamReader reader, InternalMessage request,
boolean skipBody, MessageInfo messageInfo) {
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getEnvelopeTag());
XMLStreamReaderUtil.nextElementContent(reader);
if (skipHeader(messageInfo))
skipHeader(reader, messageInfo);
else
decodeHeader(reader, messageInfo, request);
if (skipBody) {
skipBody(reader);
} else {
decodeBody(reader, request, messageInfo);
}
XMLStreamReaderUtil.verifyReaderState(reader, END_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getEnvelopeTag());
XMLStreamReaderUtil.nextElementContent(reader);
XMLStreamReaderUtil.verifyReaderState(reader, END_DOCUMENT);
}
protected void decodeHeader(XMLStreamReader reader, MessageInfo messageInfo,
InternalMessage request) {
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
if (!SOAPNamespaceConstants.TAG_HEADER.equals(reader.getLocalName())) {
return;
}
XMLStreamReaderUtil.verifyTag(reader, getHeaderTag());
if (isDispatch(messageInfo))
dispatchUtil.collectPrefixes(reader);
XMLStreamReaderUtil.nextElementContent(reader);
while (true) {
if (reader.getEventType() == START_ELEMENT) {
decodeHeaderElement(reader, messageInfo, request);
} else {
break;
}
}
XMLStreamReaderUtil.verifyReaderState(reader, END_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getHeaderTag());
XMLStreamReaderUtil.nextElementContent(reader);
}
/*
* If JAXB can deserialize a header, deserialize it.
* Otherwise, just ignore the header
*/
protected void decodeHeaderElement(XMLStreamReader reader, MessageInfo messageInfo,
InternalMessage msg) {
RuntimeContext rtCtxt = MessageInfoUtil.getRuntimeContext(messageInfo);
if (rtCtxt == null) {
XMLStreamReaderUtil.skipElement(reader); // Moves to END state
XMLStreamReaderUtil.nextElementContent(reader);
return;
}
BridgeContext bridgeContext = rtCtxt.getBridgeContext();
Set<QName> knownHeaders = ((SOAPRuntimeModel) rtCtxt.getModel()).getKnownHeaders();
QName name = reader.getName();
if (knownHeaders != null && knownHeaders.contains(name)) {
QName headerName = reader.getName();
if (msg.isHeaderPresent(name)) {
// More than one instance of header whose QName is mapped to a
// method parameter. Generates a runtime error.
raiseFault(getSenderFaultCode(), DUPLICATE_HEADER + headerName);
}
Object decoderInfo = rtCtxt.getDecoderInfo(name);
if (decoderInfo != null && decoderInfo instanceof JAXBBridgeInfo) {
JAXBBridgeInfo bridgeInfo = (JAXBBridgeInfo) decoderInfo;
// JAXB leaves on </env:Header> or <nextHeaderElement>
bridgeInfo.deserialize(reader, bridgeContext);
HeaderBlock headerBlock = new HeaderBlock(bridgeInfo);
msg.addHeader(headerBlock);
}
} else {
XMLStreamReaderUtil.skipElement(reader); // Moves to END state
XMLStreamReaderUtil.nextElementContent(reader);
}
}
protected void decodeBody(XMLStreamReader reader, InternalMessage response,
MessageInfo messageInfo) {
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getBodyTag());
int state = XMLStreamReaderUtil.nextElementContent(reader);
decodeBodyContent(reader, response, messageInfo);
XMLStreamReaderUtil.verifyReaderState(reader, END_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getBodyTag());
XMLStreamReaderUtil.nextElementContent(reader);
}
protected void decodeBodyContent(XMLStreamReader reader, InternalMessage response,
MessageInfo messageInfo) {
RuntimeContext rtCtxt = MessageInfoUtil.getRuntimeContext(messageInfo);
BridgeContext bridgeContext = rtCtxt.getBridgeContext();
decodeDispatchMethod(reader, response, messageInfo);
if (reader.getEventType() == START_ELEMENT) {
QName name = reader.getName(); // Operation name
if (name.getNamespaceURI().equals(getEnvelopeTag().getNamespaceURI()) &&
name.getLocalPart().equals(SOAPNamespaceConstants.TAG_FAULT)) {
SOAPFaultInfo soapFaultInfo = decodeFault(reader, response, messageInfo);
BodyBlock responseBody = new BodyBlock(soapFaultInfo);
response.setBody(responseBody);
} else {
Object decoderInfo = rtCtxt.getDecoderInfo(name);
if (decoderInfo != null && decoderInfo instanceof JAXBBridgeInfo)
{
JAXBBridgeInfo bridgeInfo = (JAXBBridgeInfo) decoderInfo;
bridgeInfo.deserialize(reader, bridgeContext);
BodyBlock responseBody = new BodyBlock(bridgeInfo);
response.setBody(responseBody);
} else
if (decoderInfo != null && decoderInfo instanceof RpcLitPayload)
{
RpcLitPayload rpcLitPayload = (RpcLitPayload) decoderInfo;
RpcLitPayloadSerializer.deserialize(reader, rpcLitPayload, bridgeContext);
BodyBlock responseBody = new BodyBlock(rpcLitPayload);
response.setBody(responseBody);
}
}
}
}
public void decodeDispatchMethod(XMLStreamReader reader, InternalMessage request, MessageInfo messageInfo) {
}
protected SOAPFaultInfo decodeFault(XMLStreamReader reader, InternalMessage internalMessage,
MessageInfo messageInfo) {
return null;
}
/*
*
*/
protected void convertBodyBlock(InternalMessage request, MessageInfo messageInfo) {
BodyBlock bodyBlock = request.getBody();
if (bodyBlock != null) {
Object value = bodyBlock.getValue();
if (value instanceof JAXBBridgeInfo || value instanceof RpcLitPayload)
{
// Nothing to do
} else if (value instanceof Source) {
Source source = (Source) value;
XMLStreamReader reader = SourceReaderFactory.createSourceReader(source, true);
XMLStreamReaderUtil.nextElementContent(reader);
decodeBodyContent(reader, request, messageInfo);
} else {
throw new WebServiceException("Shouldn't happen. Unknown type in BodyBlock =" + value.getClass());
}
}
}
/**
* @param mi
* @param im
* @param message
* @throws SOAPException
* @throws ParseException
*/
protected void processAttachments(MessageInfo mi, InternalMessage im, SOAPMessage message) throws SOAPException, ParseException, IOException {
Iterator iter = message.getAttachments();
if (iter.hasNext()) {
JAXWSAttachmentUnmarshaller au = null;
if (MessageInfoUtil.getRuntimeContext(mi) != null)
au = (JAXWSAttachmentUnmarshaller) MessageInfoUtil.getRuntimeContext(mi).getBridgeContext().getAttachmentUnmarshaller();
else {
//for dispatch
Unmarshaller m = (Unmarshaller)mi.getMetaData(BindingProviderProperties.DISPATCH_UNMARSHALLER);
if (m != null)
au = (JAXWSAttachmentUnmarshaller) m.getAttachmentUnmarshaller();
}
if (au != null){
au.setXOPPackage(isXOPPackage(message));
au.setAttachments(im.getAttachments());
}
}
while (iter.hasNext()) {
AttachmentPart ap = (AttachmentPart) iter.next();
im.addAttachment(AttachmentBlock.fromSAAJ(ap));
}
}
/**
* From the SOAP message header find out if its a XOP package.
*
* @param sm
* @return
* @throws ParseException
*/
private boolean isXOPPackage(SOAPMessage sm) throws ParseException {
String ct = getContentType(sm.getSOAPPart());
ContentType contentType = new ContentType(ct);
String primary = contentType.getPrimaryType();
String sub = contentType.getSubType();
if (primary.equalsIgnoreCase("application") && sub.equalsIgnoreCase("xop+xml"))
{
String type = contentType.getParameter("type");
if (type.toLowerCase().startsWith("text/xml") || type.toLowerCase().startsWith("application/soap+xml"))
return true;
}
return false;
}
private String getContentType(SOAPPart part) {
String[] values = part.getMimeHeader("Content-Type");
if (values == null)
return null;
else
return values[0];
}
/*
* It does mustUnderstand processing, and does best guess of MEP
*
* Avoids SAAJ call that create DOM.
*
*/
public boolean doMustUnderstandProcessing(SOAPMessage soapMessage,
MessageInfo mi, HandlerContext handlerContext, boolean getMEP)
throws SOAPException, IOException {
try {
boolean oneway = false;
Source source = soapMessage.getSOAPPart().getContent();
ByteInputStream bis = null;
if (source instanceof StreamSource) {
StreamSource streamSource = (StreamSource) source;
InputStream is = streamSource.getInputStream();
if (is != null && is instanceof ByteInputStream) {
bis = ((ByteInputStream) is);
} else {
logger.fine("SAAJ StreamSource doesn't have ByteInputStream " + is);
}
} else if (FastInfosetReflection.isFastInfosetSource(source)) {
try {
bis = (ByteInputStream) FastInfosetReflection.
FastInfosetSource_getInputStream(source);
}
catch (Exception e) {
throw new XMLReaderException("fastinfoset.noImplementation");
}
} else {
logger.fine("Inefficient Use - SOAPMessage is already parsed");
}
XMLStreamReader reader =
SourceReaderFactory.createSourceReader(source, true);
XMLStreamReaderUtil.nextElementContent(reader);
checkMustUnderstandHeaders(reader, mi, handlerContext);
if (getMEP) {
oneway = isOneway(reader, mi);
}
XMLStreamReaderUtil.close(reader);
if (bis != null) {
bis.close(); // resets stream; SAAJ has whole stream
}
return oneway;
} catch (XMLStreamReaderException xe) {
raiseBadXMLFault(handlerContext);
throw xe;
}
}
/*
* returns Oneway or not. reader is on <Body>
*
* Peek into the body and make a best guess as to whether the request
* is one-way or not. Assume request-response if it cannot be determined.
*
*/
private boolean isOneway(XMLStreamReader reader, MessageInfo mi) {
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getBodyTag()); // <Body>
int state = XMLStreamReaderUtil.nextElementContent(reader);
QName operationName = null;
if (state == START_ELEMENT) { // handles empty Body i.e. <Body/>
operationName = reader.getName();
}
RuntimeContext rtCtxt = MessageInfoUtil.getRuntimeContext(mi);
rtCtxt.setMethodAndMEP(operationName, mi);
return (mi.getMEP() == MessageStruct.ONE_WAY_MEP);
}
/*
* Does MU processing. reader is on <Envelope>, at the end of this method
* leaves it on <Body>. Once the roles and understood headers are
* known, this calls a separate method to check the message headers
* since a different behavior is expected with different bindings.
*
* Also assume handler chain caller is null unless one is found.
*/
private void checkMustUnderstandHeaders(XMLStreamReader reader,
MessageInfo mi, HandlerContext context) {
// Decode envelope
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
QName got = reader.getName();
QName exp = getEnvelopeTag();
if (got.getLocalPart().equals(exp.getLocalPart())) {
if (!got.getNamespaceURI().equals(exp.getNamespaceURI())) {
raiseFault(getVersionMismatchFaultCode(),
"Invalid SOAP envelope version");
}
}
XMLStreamReaderUtil.verifyTag(reader, getEnvelopeTag());
XMLStreamReaderUtil.nextElementContent(reader);
XMLStreamReaderUtil.verifyReaderState(reader, START_ELEMENT);
if (!SOAPNamespaceConstants.TAG_HEADER.equals(reader.getLocalName())) {
return; // No Header, no MU processing
}
XMLStreamReaderUtil.verifyTag(reader, getHeaderTag());
XMLStreamReaderUtil.nextElementContent(reader);
RuntimeContext rtCtxt = MessageInfoUtil.getRuntimeContext(mi);
// start with just the endpoint roles
Set<String> roles = new HashSet<String>();
roles.addAll(getRequiredRoles());
HandlerChainCaller hcCaller = MessageInfoUtil.getHandlerChainCaller(mi);
if (hcCaller != null) {
roles.addAll(hcCaller.getRoles());
}
if (logger.isLoggable(Level.FINEST)) {
logger.finest("roles:");
for (String r : roles) {
logger.finest("\t\"" + r + "\"");
}
}
// keep set=null if there are no understood headers
Set<QName> understoodHeaders = null;
if (rtCtxt != null) {
SOAPRuntimeModel model = (SOAPRuntimeModel) rtCtxt.getModel();
if (model != null && model.getKnownHeaders() != null) {
understoodHeaders = new HashSet<QName>(
((SOAPRuntimeModel) rtCtxt.getModel()).getKnownHeaders());
}
}
if (understoodHeaders == null) {
if (hcCaller != null) {
understoodHeaders = hcCaller.getUnderstoodHeaders();
}
} else {
if (hcCaller != null) {
understoodHeaders.addAll(hcCaller.getUnderstoodHeaders());
}
}
if (logger.isLoggable(Level.FINEST)) {
logger.finest("understood headers:");
if (understoodHeaders == null || understoodHeaders.isEmpty()) {
logger.finest("\tnone");
} else {
for (QName nameX : understoodHeaders) {
logger.finest("\t" + nameX.toString());
}
}
}
checkHeadersAgainstKnown(reader, roles, understoodHeaders, mi);
XMLStreamReaderUtil.verifyReaderState(reader, END_ELEMENT);
XMLStreamReaderUtil.verifyTag(reader, getHeaderTag());
XMLStreamReaderUtil.nextElementContent(reader);
}
/*
* This method is overridden for other bindings
*/
protected void checkHeadersAgainstKnown(XMLStreamReader reader,
Set<String> roles, Set<QName> understoodHeaders, MessageInfo mi) {
while (true) {
if (reader.getEventType() == START_ELEMENT) {
// check MU header for each role
QName qName = reader.getName();
String mu = reader.getAttributeValue(
getMUAttrQName().getNamespaceURI(),
getMUAttrQName().getLocalPart());
if (mu != null && (mu.equals("1") ||
mu.equalsIgnoreCase("true"))) {
String role = reader.getAttributeValue(
getRoleAttrQName().getNamespaceURI(),
getRoleAttrQName().getLocalPart());
if (role != null && roles.contains(role)) {
logger.finest("Element=" + qName +
" targeted at=" + role);
if (understoodHeaders == null ||
!understoodHeaders.contains(qName)) {
logger.finest("Element not understood=" + qName);
SOAPFault sf = SOAPUtil.createSOAPFault(
MUST_UNDERSTAND_FAULT_MESSAGE_STRING,
SOAPConstants.FAULT_CODE_MUST_UNDERSTAND,
role, null, SOAPBinding.SOAP11HTTP_BINDING);
throw new SOAPFaultException(sf);
}
}
}
XMLStreamReaderUtil.skipElement(reader); // Moves to END state
XMLStreamReaderUtil.nextElementContent(reader);
} else {
break;
}
}
}
protected boolean isDispatch(MessageInfo messageInfo) {
DispatchContext context = (DispatchContext)
messageInfo.getMetaData(BindingProviderProperties.DISPATCH_CONTEXT);
if (context != null)
return true;
return false;
}
protected String getSOAPMessageCharsetEncoding(SOAPMessage sm) throws SOAPException {
String charset = (String) sm.getProperty(SOAPMessage.CHARACTER_SET_ENCODING);
return (charset != null) ? charset : "UTF-8";
}
protected final void raiseFault(QName faultCode, String faultString) {
throw new SOAPFaultException(SOAPUtil.createSOAPFault(faultString, faultCode, null, null, getBindingId()));
}
protected void raiseBadXMLFault(HandlerContext ctxt) {
}
protected abstract QName getSenderFaultCode();
protected abstract QName getReceiverFaultCode();
protected abstract QName getVersionMismatchFaultCode();
public abstract String getBindingId();
private final static String DUPLICATE_HEADER =
"Duplicate Header in the message:";
public DispatchUtil getDispatchUtil() {
return dispatchUtil;
}
protected DispatchUtil dispatchUtil = new DispatchUtil();
}