/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: SAXEventBufferAxisSerializer.java 2326 2007-03-27 21:59:44Z mlipp $
*
* $Log$
* Revision 1.2 2006/09/29 12:32:13 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.1.1.2 2004/08/18 15:17:39 drmlipp
* Update to 1.2
*
* Revision 1.3 2004/03/17 12:43:02 lipp
* Added arrayType generation for arrays.
*
* Revision 1.2 2003/06/27 08:51:44 lipp
* Fixed copyright/license information.
*
* Revision 1.1 2003/06/24 16:08:12 lipp
* Implemented invocation.
*
*/
package de.danet.an.workflow.tools.soapclient;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
import org.apache.axis.Constants;
import org.apache.axis.MessageContext;
import org.apache.axis.encoding.SerializationContext;
import org.apache.axis.encoding.Serializer;
import org.apache.axis.schema.SchemaVersion;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.utils.Messages;
import org.apache.axis.wsdl.fromJava.Types;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import de.danet.an.util.XMLUtil;
import de.danet.an.util.sax.HandlerStack;
import de.danet.an.util.sax.StackedHandler;
import de.danet.an.workflow.api.SAXEventBuffer;
/**
* General purpose serializer/deserializerFactory for SAXEventBuffer in
* the AXIS mplementation of the JAX-RPC interface.
* Creates a xml output for a given SAXEventBuffer,
* preserving the stucture and text content.
*/
public class SAXEventBufferAxisSerializer implements Serializer, Serializable {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog
(SAXEventBufferAxisSerializer.class);
/**
* Helper class that holds a namespace uri and a local name.
*/
private class NsAndLocal {
private String namespace;
private String localName;
/**
* Create a new object with attributes initialized to the
* given values.
* @param n the namespace
* @param l the local name
*/
public NsAndLocal (String n, String l) {
namespace = n;
localName = l;
}
/**
* Return the namespace.
* @return namespace
*/
public String namespace () {
return namespace;
}
/**
* Return the local name.
* @return local name
*/
public String localName () {
return localName;
}
/**
* Compare with other object.
* @param o other object
* @return <code>true</code> if equal
*/
public boolean equals (Object o) {
String ons = ((NsAndLocal)o).namespace;
if (namespace == null && ons != null
|| namespace != null && ons == null
|| namespace != null && ons != null && !namespace.equals(ons)) {
return false;
}
return localName.equals (((NsAndLocal)o).localName);
}
/**
* Return a hash code.
* @return the hash code
*/
public int hashCode () {
return namespace.hashCode() ^ localName.hashCode();
}
/**
* Return a string representation for debugging purposes.
* @return string representation
*/
public String toString () {
return "{" + namespace + "}" + localName;
}
}
/**
* Default constructor.
*/
public SAXEventBufferAxisSerializer() {
}
/**
* From the Serializer interface.
* @param name name
* @param attributes attributes
* @param value value
* @param context context
* @throws IOException IOException
*/
public void serialize(QName name, Attributes attributes,
Object value, SerializationContext context)
throws IOException {
logger.debug("SAXEventBufferAxis serialize() called for \"" + name + "\"");
if (!(value instanceof SAXEventBuffer)) {
throw new IOException(Messages.getMessage("can not serialize!"));
}
MessageContext msgContext = context.getMessageContext();
SchemaVersion schema = SchemaVersion.SCHEMA_2001;
SOAPConstants soap = SOAPConstants.SOAP11_CONSTANTS;
if(msgContext != null) {
schema = msgContext.getSchemaVersion();
soap = msgContext.getSOAPConstants();
}
try {
int typeIdx = attributes.getIndex (schema.getXsiURI(), "type");
if (typeIdx >= 0) {
String prefixForSoapEnc
= context.getPrefixForURI(soap.getEncodingURI());
if (logger.isDebugEnabled ()) {
logger.debug
("Type is \"" + attributes.getValue(typeIdx) + "\""
+ " (\""+prefixForSoapEnc+":Array\" denotes array)");
}
if (attributes.getValue(typeIdx)
.equals(prefixForSoapEnc + ":Array")
&& attributes.getIndex(soap.getEncodingURI(),
soap.getAttrItemType()) == -1) {
AttributesImpl attrs = new AttributesImpl (attributes);
attrs.addAttribute
(soap.getEncodingURI(), soap.getAttrItemType(),
prefixForSoapEnc + ":arrayType", "CDATA",
determineArrayType ((SAXEventBuffer)value, context));
attributes = attrs;
}
}
context.startElement(name, attributes);
((SAXEventBuffer)value).emit (new EventForwarder (context));
context.endElement();
logger.debug("SAXEventBuffer serialize() called");
} catch (SAXException e) {
logger.error ("Problem creating XML argument: "
+ e.getMessage (), e);
throw new IOException (e.getMessage ());
}
}
private class ArrayTypeExtractor extends StackedHandler {
private SchemaVersion schema = SchemaVersion.SCHEMA_2001;
private int items = 0;
private boolean typeValid = true;
/**
* Create a new event forwarder.
* @param context the context to forward to
*/
public ArrayTypeExtractor (SerializationContext context) {
MessageContext msgContext = context.getMessageContext();
if(msgContext != null) {
schema = msgContext.getSchemaVersion();
}
}
/**
* Receive notification of the beginning of an element.
*
* @param namespaceURI The Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace
* processing is not being performed.
* @param localName The local name (without prefix), or the
* empty string if Namespace processing is not being
* performed.
* @param qName The qualified name (with prefix), or the
* empty string if qualified names are not available.
* @param atts The attributes attached to the element. If
* there are no attributes, it shall be an empty
* Attributes object.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see #endElement
* @see org.xml.sax.Attributes
*/
public void startElement (String namespaceURI, String localName,
String qName, Attributes atts)
throws SAXException {
if (getStack().getRelativeDepth() != 1) {
return;
}
// top level element, i.e. item
items += 1;
setContextData ("items", new Integer (items));
if (!typeValid) {
return;
}
int typeIdx = atts.getIndex (schema.getXsiURI(), "type");
if (typeIdx < 0) {
logger.warn ("Type declaration missing in array element "
+ items);
typeValid = false;
setContextData ("typesValid", Boolean.FALSE);
return;
}
String typeQName = atts.getValue(typeIdx);
NsAndLocal type = null;
int colonPos = typeQName.indexOf(':');
if (colonPos < 0) {
type = new NsAndLocal (null, typeQName);
} else {
String typeURI = getStack().getURIForPrefix
(typeQName.substring(0, colonPos));
if (typeURI == null) {
logger.warn ("Invalid type declaration in array element "
+ items + ": uses undefined prefix in qname");
typeValid = false;
setContextData ("typesValid", Boolean.FALSE);
return;
}
type = new NsAndLocal
(typeURI, typeQName.substring(colonPos + 1));
}
if (logger.isDebugEnabled ()) {
logger.debug ("Type of element " + items + " is " + type);
}
((Set)getContextData("types")).add (type);
}
}
private String determineArrayType
(SAXEventBuffer buf, SerializationContext context)
throws SAXException {
HandlerStack hs = new HandlerStack (new ArrayTypeExtractor(context));
hs.setContextData("types", new HashSet ());
hs.setContextData ("items", new Integer (0));
hs.setContextData ("typesValid", Boolean.TRUE);
hs.contentHandler().startElement
("", "root", "root", new AttributesImpl ());
buf.emit (hs.contentHandler());
hs.contentHandler().endElement ("", "root", "root");
Set types = (Set)hs.getContextData("types");
String xsdPref = context.getPrefixForURI(XMLUtil.XMLNS_SCHEMA);
String arrayType;
String dim = "[" + (Integer)hs.getContextData("items") + "]";
if (!((Boolean)hs.getContextData ("typesValid")).booleanValue()
|| types.size () > 1) {
arrayType = xsdPref + ":" + "ur-type" + dim;
} else {
Iterator i = types.iterator();
NsAndLocal type = (NsAndLocal)i.next ();
String typePref = context.getPrefixForURI(type.namespace());
arrayType = typePref + ":" + type.localName() + dim;
}
if (logger.isDebugEnabled ()) {
logger.debug ("Array type is " + arrayType);
}
return arrayType;
}
private class EventForwarder implements ContentHandler {
private SerializationContext ctx;
/**
* Receive an object for locating the origin of SAX document events.
*
* @param locator An object that can return the location of any
* SAX document event.
* @see org.xml.sax.Locator
*/
public void setDocumentLocator (Locator locator) {
// ignore
}
/**
* Create a new event forwarder.
* @param context the context to forward to
*/
public EventForwarder (SerializationContext context) {
ctx = context;
}
/**
* Receive notification of the beginning of a document.
*
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see #endDocument
*/
public void startDocument() throws SAXException {
// shouldn't be called
}
/**
* Receive notification of the end of a document.
*
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see #startDocument
*/
public void endDocument() throws SAXException {
// shouldn't be called
}
/**
* Begin the scope of a prefix-URI Namespace mapping.
*
* @param prefix The Namespace prefix being declared.
* @param uri The Namespace URI the prefix is mapped to.
* @throws SAXException The client may throw an
* exception during processing.
* @see #endPrefixMapping
* @see #startElement
*/
public void startPrefixMapping (String prefix, String uri)
throws SAXException {
ctx.registerPrefixForURI(prefix, uri);
}
/**
* End the scope of a prefix-URI mapping.
*
* @param prefix The prefix that was being mapping.
* @throws SAXException The client may throw
* an exception during processing.
* @see #startPrefixMapping
* @see #endElement
*/
public void endPrefixMapping(String prefix) throws SAXException {
// nothing to do
}
/**
* Receive notification of the beginning of an element.
*
* @param namespaceURI The Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace
* processing is not being performed.
* @param localName The local name (without prefix), or the
* empty string if Namespace processing is not being
* performed.
* @param qName The qualified name (with prefix), or the
* empty string if qualified names are not available.
* @param atts The attributes attached to the element. If
* there are no attributes, it shall be an empty
* Attributes object.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see #endElement
* @see org.xml.sax.Attributes
*/
public void startElement (String namespaceURI, String localName,
String qName, Attributes atts)
throws SAXException {
try {
AttributesImpl a = new AttributesImpl ();
// Axis has some problems with namespaces reported
// as attributes, filter them out
for (int i = 0; i < atts.getLength(); i++) {
if (! atts.getURI(i).equals (XMLUtil.XMLNS_NS)) {
a.addAttribute (atts.getURI(i), atts.getLocalName(i),
atts.getQName(i), atts.getType(i),
atts.getValue(i));
}
}
ctx.startElement(new QName (namespaceURI, localName), a);
} catch (IOException e) {
throw new SAXException (e);
}
}
/**
* Receive notification of the end of an element.
*
* @param namespaceURI The Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace
* processing is not being performed.
* @param localName The local name (without prefix), or the
* empty string if Namespace processing is not being
* performed.
* @param qName The qualified XML 1.0 name (with prefix), or the
* empty string if qualified names are not available.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
*/
public void endElement (String namespaceURI, String localName,
String qName)
throws SAXException {
try {
ctx.endElement();
} catch (IOException e) {
throw new SAXException (e);
}
}
/**
* Receive notification of character data.
*
* @param ch The characters from the XML document.
* @param start The start position in the array.
* @param length The number of characters to read from the array.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see #ignorableWhitespace
* @see org.xml.sax.Locator
*/
public void characters (char[] ch, int start, int length)
throws SAXException {
try {
ctx.writeChars(ch, start, length);
} catch (IOException e) {
throw new SAXException (e);
}
}
/**
* Receive notification of ignorable whitespace in element content.
*
* @param ch The characters from the XML document.
* @param start The start position in the array.
* @param length The number of characters to read from the array.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
* @see #characters
*/
public void ignorableWhitespace (char[] ch, int start, int length)
throws SAXException {
try {
ctx.writeChars(ch, start, length);
} catch (IOException e) {
throw new SAXException (e);
}
}
/**
* Receive notification of a processing instruction.
*
* @param target The processing instruction target.
* @param data The processing instruction data, or null if
* none was supplied. The data does not include any
* whitespace separating it from the target.
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
*/
public void processingInstruction (String target, String data)
throws SAXException {
}
/**
* Receive notification of a skipped entity.
*
* @param name The name of the skipped entity. If it is a
* parameter entity, the name will begin with '%', and if it is
* the external DTD subset, it will be the string "[dtd]".
* @throws SAXException Any SAX exception, possibly
* wrapping another exception.
*/
public void skippedEntity (String name)
throws SAXException {
}
}
/**
* From the Serializer interface (Axis pre 1.1 version).
* Currently we do not support serialization.
* @param types types
* @return false
*/
public boolean writeSchema(Types types){
return false;
}
/**
* Return XML schema for the specified type, suitable for insertion into
* the <types> element of a WSDL document, or underneath an
* <element> or <attribute> declaration.
*
* @param javaType the Java Class we're writing out schema for
* @param types the Java2WSDL Types object which holds the context
* for the WSDL being generated
* @return a type element containing a schema simpleType/complexType
* @throws Exception if an error occurs
* @see org.apache.axis.wsdl.fromJava.Types
*/
public Element writeSchema(Class javaType, Types types) throws Exception {
return null;
}
/**
* From the Serializer and Deserializerinterface.
* @return Constants.AXIS_SAX
*/
public String getMechanismType() {
return Constants.AXIS_SAX;
}
}