/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.io.xml.canonicalization;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Writer;
import org.ws4d.java.constants.XMLConstants;
import org.ws4d.java.io.xml.ElementHandler;
import org.ws4d.java.io.xml.ElementHandlerRegistry;
import org.ws4d.java.io.xml.XmlSerializer;
import org.ws4d.java.io.xml.XmlSerializerImplementation;
import org.ws4d.java.io.xml.cache.XmlAttribute;
import org.ws4d.java.io.xml.cache.XmlPrefix;
import org.ws4d.java.io.xml.cache.XmlStructure;
import org.ws4d.java.io.xml.cache.XmlTag;
import org.ws4d.java.io.xml.cache.XmlText;
import org.ws4d.java.structures.ArrayList;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.structures.List;
import org.ws4d.java.types.QName;
import org.ws4d.java.util.StringUtil;
import org.xmlpull.mxp1_serializer.MXSerializer;
import org.xmlpull.v1.IllegalStateException;
public class CanonicalSerializer extends MXSerializer implements XmlSerializer {
public static final int EXC_C14N_XML_SERIALIZER = 1;
private int type = EXC_C14N_XML_SERIALIZER;
Writer writer;
private ArrayList xmlCache;
private int currentCmd = -1;
// these are pointers to support faster xml processing
private int xmlCommandCounter = 0;
private int secuInjectionStart = -1;
private String id = "";
private String lastStartedTag;
boolean canonicalize = false;
String currentCanonElement = "";
public static final String[] characterEntities = { """, "&", "'", "<", ">" };
public static final String[] correpondingCharacters = { "\"", "&", "'", "<", ">" };
private ArrayList recognNamespaces = new ArrayList();
private byte[] bodyByteArray = null;
public CanonicalSerializer(String id) {
this.id = id;
xmlCache = new ArrayList();
}
public void setPrefix(String prefix, String namespace) {
recognNamespaces.add(new NamespaceRecord(prefix, namespace));
xmlCommandCounter++;
if (currentCmd != -1) {
currentCmd++;
xmlCache.add(currentCmd, new XmlPrefix(prefix, namespace));
return;
}
xmlCache.add(new XmlPrefix(prefix, namespace));
}
public org.xmlpull.v1.XmlSerializer startTag(String namespace, String name) {
lastStartedTag = name;
xmlCommandCounter++;
if (currentCmd != -1) {
currentCmd++;
xmlCache.add(currentCmd, new XmlTag(true, namespace, name));
return this;
// currentCmd++;
// xmlCache.add(currentCmd, new XmlTag(true, namespace, name));
// return this;
}
if (canonicalize == true) {
xmlCache.add(new XmlTag(true, namespace, name));
} else {
xmlCache.add(new XmlTag(true, namespace, name));
}
// xmlCache.add(new XmlTag(true, namespace, name));
return this;
}
public org.xmlpull.v1.XmlSerializer endTag(String ns, String name) {
xmlCommandCounter++;
if (currentCmd != -1) {
currentCmd++;
xmlCache.add(currentCmd, new XmlTag(false, ns, name));
return this;
}
if (StringUtil.equalsIgnoreCase(name, "header")) secuInjectionStart = xmlCommandCounter - 2;
if (canonicalize)
xmlCache.add(new XmlTag(false, ns, name));
else
xmlCache.add(new XmlTag(false, ns, name));
// xmlCache.add(new XmlTag(false, ns, name));
if ((ns + name).equals(currentCanonElement)) {
canonicalize = false;
}
return this;
}
public org.xmlpull.v1.XmlSerializer attribute(String namespace, String name, String value) {
if (canonicalize) value = c14nAttributeString(value);
if (name.equals("ID") && value.equals(id)) {
canonicalize = true;
currentCanonElement = lastStartedTag;
}
xmlCommandCounter++;
if (currentCmd != -1) {
currentCmd++;
xmlCache.add(currentCmd, new XmlAttribute(namespace, name, value));
return this;
}
xmlCache.add(new XmlAttribute(namespace, name, value));
return this;
}
public void attributeo(String namespace, String name, String value) {
try {
super.attribute(namespace, name, value);
} catch (IOException e) {
e.printStackTrace();
}
}
public void setPrefixo(String prefix, String namespace) {
try {
super.setPrefix(prefix, namespace);
} catch (IOException e) {
e.printStackTrace();
}
}
public XmlSerializer startTago(String namespace, String name) {
try {
super.startTag(namespace, name);
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public XmlSerializer endTago(String ns, String name) {
try {
super.endTag(ns, name);
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public org.xmlpull.v1.XmlSerializer texto(String text) {
try {
super.text(text);
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public void flushCache() {
Iterator it = xmlCache.iterator();
boolean write = true;
while (it.hasNext()) {
try {
XmlStructure st = (XmlStructure) it.next();
if ((st.getType() == XmlStructure.XML_START_TAG) && ((XmlTag) st).getName().equals("Body")) {
writer.write(byteArrayToString(bodyByteArray == null ? (bodyByteArray = bodyPart()) : bodyByteArray));
write = false;
}
if (write) {
st.flush(this);
}
if ((st.getType() == XmlStructure.XML_END_TAG) && ((XmlTag) st).getName().equals("Body")) write = true;
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
super.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private String byteArrayToString(byte[] d) {
String g = "";
for (int i = 0; i < d.length; i++) {
g += (char) d[i];
}
return g;
}
/**
* @param writer the writer to set
*/
public void setOutput(Writer writer) {
super.setOutput(writer);
this.writer = writer;
}
/**
* Writes a block of XML directly to the underlying stream, especially
* without ignoring any special chars.
*
* @param text the XML block to write
* @throws IOException
*/
public void plainText(String text) throws IOException {
writer.write(text);
}
// private String exchangePrefixes(String text) {
// int index = 0;
// String one;
// String two;
//
// while ((index = text.indexOf(":", index)) > 0) {
// int startIndexofOldNs = text.substring(0, index).lastIndexOf(' ');
// if (startIndexofOldNs == index - 1) {
// index++;
// continue;
// }
// String ns = text.substring(startIndexofOldNs < 0 ? 0 : startIndexofOldNs,
// index);
//
// if (newNs == null) {
// index++;
// continue;
// }
//
// one = text.substring(0, startIndexofOldNs < 0 ? 0 : startIndexofOldNs);
// two = text.substring(index, text.length());
//
// text = one + newNs + two;
//
// index++;
// }
// return text;
// }
public org.xmlpull.v1.XmlSerializer text(String text) {
if (canonicalize) {
text = c14nValueString(text);
// text = exchangePrefixes(text);
}
xmlCommandCounter++;
if (currentCmd != -1) {
currentCmd++;
xmlCache.add(currentCmd, new XmlText(text));
}
xmlCache.add(new XmlText(text));
return this;
}
/**
* Exchanges certain characters into xml character entities
*
* @param text
* @return
*/
private String c14nValueString(String text) {
for (int i = 0; i < correpondingCharacters.length; i++) {
int index = -1;
String one;
String two;
while ((index = text.indexOf(correpondingCharacters[i], index == -1 ? 0 : index)) > 0 && text.indexOf(characterEntities[i], index == -1 ? 0 : index) <= -1) {
one = text.substring(0, index);
two = text.substring(index + correpondingCharacters[i].length());
index += characterEntities[i].length() - 1;
text = one + characterEntities[i] + two;
}
}
return text;
}
private String c14nAttributeString(String text) {
for (int i = 0; i < characterEntities.length; i++) {
int index = -1;
String one;
String two;
while ((index = text.indexOf(characterEntities[i], index == -1 ? 0 : index)) > 0) {
one = text.substring(0, index);
two = text.substring(index + characterEntities[i].length());
index += correpondingCharacters[i].length();
text = one + correpondingCharacters[i] + two;
}
}
return text;
}
/**
* resoluteNamespace
*
* @param the prefix to resolute.
* @return the namespace.
*/
// public String resoluteNamespace(String prefix) {
// for (int i = this.recognNamespaces.size() - 1; i >= 0; i--) {
// if (((NamespaceRecord) (recognNamespaces.get(i))).prefix.equals(prefix))
// {
// return ((NamespaceRecord) recognNamespaces.get(i)).namespace;
// }
// }
// return prefix;
// }
/**
* @param qname the fully qualified name of the elements to expect within
* the <code>list</code>
* @param elements the list of elements to serialize; all are expected to be
* of the same type; note that this list can be empty or have
* just one element
* @throws IOException
*/
public void unknownElements(QName qname, List elements) throws IOException {
ElementHandler handler = ElementHandlerRegistry.getRegistry().getElementHandler(qname);
if (handler != null) {
for (Iterator at = elements.iterator(); at.hasNext();) {
handler.serializeElement(this, qname, at.next());
}
}
}
// sorting
public void namespaceWriter() {
boolean inSection = false;
HashMap ns_rendered = new HashMap();
int afterLastStartedTagPosition = -1;
for (int iteration = 0; iteration < xmlCache.size(); iteration++) {
XmlStructure st = (XmlStructure) xmlCache.get(iteration);
if (st.getType() == XmlStructure.XML_START_TAG) {
afterLastStartedTagPosition = iteration;
}
if (st.getType() == XmlStructure.XML_START_TAG && StringUtil.equalsIgnoreCase(((XmlTag) st).getName(), "Body")) {
inSection = true;
String prefix = getPrefixFor(((XmlTag) st).getNamespace());
ns_rendered.put(prefix, ((XmlTag) st).getNamespace());
xmlCache.add(afterLastStartedTagPosition + 1, new XmlAttribute("", "xmlns:" + prefix, ((XmlStructure) st).getNamespace()));
} else if (inSection) {
String ns;
if ((ns = getPrefixFor(((XmlStructure) st).getNamespace())) != null && st.getType() != XmlStructure.XML_END_TAG) {
if (ns_rendered.containsKey(ns)) continue;
xmlCache.add(afterLastStartedTagPosition + 1, new XmlAttribute("", "xmlns:" + ns, ((XmlStructure) st).getNamespace()));
ns_rendered.put(ns, ((XmlStructure) st).getNamespace());
}
}
}
}
private void writeSortedAttributes(XmlSerializer s, ArrayList unsorted) {
ArrayList nsDelarations = new ArrayList();
ArrayList attributes = new ArrayList();
Iterator it = unsorted.iterator();
XmlStructure st;
while (it.hasNext()) {
st = (XmlStructure) it.next();
if (st.getType() == XmlStructure.XML_PREFIX || st.getName().startsWith("xmlns")) {
nsDelarations.add(st);
} else
attributes.add(st);
}
attributes = sortXmlElemets(attributes);
nsDelarations = sortXmlElemets(nsDelarations);
writeArrayToSerializer(s, nsDelarations);
writeArrayToSerializer(s, attributes);
}
private void writeArrayToSerializer(XmlSerializer s, ArrayList l) {
Iterator it = l.iterator();
while (it.hasNext()) {
try {
((XmlStructure) it.next()).flush(s);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private ArrayList sortXmlElemets(ArrayList xmlStructs) {
int i, j;
XmlStructure newValue;
for (i = 1; i < xmlStructs.size(); i++) {
newValue = (XmlStructure) xmlStructs.get(i);
j = i;
while (j > 0 && ((XmlStructure) xmlStructs.get(j - 1)).getValue().compareTo(newValue.getValue()) > 0) {
xmlStructs.set(j, xmlStructs.get(j - 1));
j--;
}
xmlStructs.set(j, newValue);
}
return xmlStructs;
}
public byte[] bodyPart() {
XmlSerializer serializer = new XmlSerializerImplementation();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
serializer.setOutput(baos, XMLConstants.ENCODING);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
namespaceWriter();
boolean write = false;
boolean tagEnded = false;
Iterator it = xmlCache.iterator();
ArrayList attributes = new ArrayList();
while (it.hasNext()) {
XmlStructure st = (XmlStructure) it.next();
if (write) {
try {
if (st.getType() == XmlStructure.XML_ATTRIBUTE || st.getType() == XmlStructure.XML_PREFIX) {
attributes.add(st.getType() == XmlStructure.XML_PREFIX ? new XmlAttribute("", "xmlns:" + st.getName(), st.getValue()) : st);
continue;
} else if (st.getType() == XmlStructure.XML_START_TAG) {
if (!attributes.isEmpty()) {
if (!tagEnded) {
writeSortedAttributes(serializer, attributes);
attributes = new ArrayList();
}
}
tagEnded = false;
// if(!((XmlTag) st).getNamespace().isEmpty() &&
// )attributes.add(new XmlAttribute("",
// "xmlns:"+this.getPrefixFor(((XmlTag)
// st).getNamespace()), ((XmlTag) st).getNamespace()));
((XmlTag) st).setName(getPrefixFor(((XmlTag) st).getNamespace()) + ":" + st.getName());
((XmlTag) st).setNamespace("");
} else if (st.getType() == XmlStructure.XML_TEXT || st.getType() == XmlStructure.XML_END_TAG) {
if (st.getType() == XmlStructure.XML_END_TAG && !StringUtil.equalsIgnoreCase(st.getName(), "body")) {
tagEnded = true;
((XmlTag) st).setName(getPrefixFor(st.getNamespace()) + ":" + st.getName());
st.setNameSpace("");
}
if (!attributes.isEmpty()) {
writeSortedAttributes(serializer, attributes);
attributes = new ArrayList();
}
}
if (st.getType() == XmlStructure.XML_END_TAG && StringUtil.equalsIgnoreCase(((XmlTag) st).getName(), "Body")) {
st = new XmlTag(false, "", getPrefixFor(((XmlTag) st).getNamespace()) + ":" + st.getName());
st.flush(serializer);
break;
}
st.flush(serializer);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} else if (st.getType() == XmlStructure.XML_START_TAG) {
if (StringUtil.equalsIgnoreCase(((XmlTag) st).getName(), "Body")) {
write = true;
try {
st = new XmlTag(true, "", getPrefixFor(((XmlTag) st).getNamespace()) + ":" + st.getName());
st.flush(serializer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
try {
serializer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bodyByteArray = baos.toByteArray();
return bodyByteArray;
}
public void injectSecurityStart() {
if (this.secuInjectionStart != -1) {
this.currentCmd = secuInjectionStart;
return;
}
Iterator it = xmlCache.iterator();
for (int pointer = 0; it.hasNext(); pointer++) {
XmlStructure st = (XmlStructure) it.next();
if (st.getType() == XmlStructure.XML_START_TAG) {
if (StringUtil.equalsIgnoreCase(((XmlTag) st).getName(), "header")) {
currentCmd = pointer + 1;
return;
}
}
}
}
public void injectSecurityDone() {
currentCmd = -1;
}
/*
* (non-Javadoc)
* @see org.ws4d.java.io.xml.XmlSerializer#getType()
*/
public int getType() {
return type;
}
/*
* (non-Javadoc)
* @see org.ws4d.java.io.xml.XmlSerializer#getOutput()
*/
public Writer getOutput() {
return super.getWriter();
}
public String getPrefixFor(String namespace) {
Iterator iter = recognNamespaces.iterator();
while (iter.hasNext()) {
NamespaceRecord nr = (NamespaceRecord) iter.next();
if (nr.namespace.equals(namespace)) return nr.prefix;
}
return null;
}
public String getNamespaceFor(String prefix) {
Iterator iter = recognNamespaces.iterator();
while (iter.hasNext()) {
NamespaceRecord nr = (NamespaceRecord) iter.next();
if (nr.prefix.equals(prefix)) return nr.namespace;
}
return null;
}
}
class NamespaceRecord {
public String prefix;
public String namespace;
public NamespaceRecord(String p, String ns) {
prefix = p;
namespace = ns;
}
}