package railo.runtime.converter; import java.io.IOException; import java.io.StringReader; import java.io.Writer; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; import javax.xml.parsers.FactoryConfigurationError; import org.apache.xerces.parsers.DOMParser; import org.w3c.dom.CharacterData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import railo.commons.date.TimeZoneConstants; import railo.commons.lang.NumberUtil; import railo.runtime.Component; import railo.runtime.ComponentScope; import railo.runtime.ComponentWrap; import railo.runtime.PageContext; import railo.runtime.coder.Base64Coder; import railo.runtime.coder.CoderException; import railo.runtime.component.Property; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.PageException; import railo.runtime.op.Caster; import railo.runtime.op.Decision; import railo.runtime.op.date.DateCaster; import railo.runtime.orm.ORMUtil; import railo.runtime.text.xml.XMLUtil; import railo.runtime.type.Array; import railo.runtime.type.ArrayImpl; import railo.runtime.type.Collection; import railo.runtime.type.Collection.Key; import railo.runtime.type.KeyImpl; import railo.runtime.type.Query; import railo.runtime.type.QueryImpl; import railo.runtime.type.Struct; import railo.runtime.type.StructImpl; import railo.runtime.type.UDF; import railo.runtime.type.cfc.ComponentAccess; import railo.runtime.type.dt.DateTime; import railo.runtime.type.dt.DateTimeImpl; import railo.runtime.type.util.CollectionUtil; import railo.runtime.type.util.ComponentUtil; import railo.runtime.type.util.KeyConstants; /** * class to serialize and desirilize WDDX Packes */ public final class WDDXConverter extends ConverterSupport { private static final Collection.Key REMOTING_FETCH = KeyImpl.intern("remotingFetch"); private int deep=1; private boolean xmlConform; private char _; private TimeZone timeZone; private boolean ignoreRemotingFetch=true; //private PageContext pcx; /** * constructor of the class * @param timeZone * @param xmlConform define if generated xml conform output or wddx conform output (wddx is not xml conform) */ public WDDXConverter(TimeZone timeZone, boolean xmlConform,boolean ignoreRemotingFetch) { this.xmlConform=xmlConform; _=(xmlConform)?'"':'\''; this.timeZone=timeZone; this.ignoreRemotingFetch=ignoreRemotingFetch; } /** * defines timezone info will * @param timeZone */ public void setTimeZone(TimeZone timeZone) { this.timeZone=timeZone; } /** * serialize a Date * @param date Date to serialize * @return serialized date * @throws ConverterException */ private String _serializeDate(Date date) { return _serializeDateTime(new DateTimeImpl(date)); } /** * serialize a DateTime * @param dateTime DateTime to serialize * @return serialized dateTime * @throws ConverterException */ private String _serializeDateTime(DateTime dateTime) { //try { String strDate = new railo.runtime.format.DateFormat(Locale.US).format(dateTime,"yyyy-m-d",TimeZoneConstants.UTC); String strTime = new railo.runtime.format.TimeFormat(Locale.US).format(dateTime,"H:m:s",TimeZoneConstants.UTC); return goIn()+"<dateTime>"+ strDate+ "T"+strTime+ "+0:0"+ "</dateTime>"; /*} catch (PageException e) { throw new ConverterException(e); }*/ } private String _serializeBinary(byte[] binary) { return new StringBuilder("<binary length='") .append(binary.length) .append("'>") .append(Base64Coder.encode(binary)) .append("</binary>").toString(); } /** * @param dateTime * @return returns the time zone info */ private String getTimeZoneInfo(DateTime dateTime) { timeZone=ThreadLocalPageContext.getTimeZone(timeZone); //if(timeZone==null) return ""; int minutes=timeZone.getOffset(dateTime.getTime())/1000/60; String operator=(minutes>=0)?"+":"-"; if(operator.equals("-"))minutes=minutes-(minutes+minutes); int hours=minutes/60; minutes=minutes%60; return operator+hours+":"+minutes; } /** * serialize a Array * @param array Array to serialize * @param done * @return serialized array * @throws ConverterException */ private String _serializeArray(Array array, Set<Object> done) throws ConverterException { return _serializeList(array.toList(),done); } /** * serialize a List (as Array) * @param list List to serialize * @param done * @return serialized list * @throws ConverterException */ private String _serializeList(List list, Set<Object> done) throws ConverterException { StringBuffer sb=new StringBuffer(goIn()+"<array length="+_+list.size()+_+">"); ListIterator it=list.listIterator(); while(it.hasNext()) { sb.append(_serialize(it.next(),done)); } sb.append(goIn()+"</array>"); return sb.toString(); } /** * serialize a Component * @param component Component to serialize * @param done * @return serialized component * @throws ConverterException */ private String _serializeComponent(Component component, Set<Object> done) throws ConverterException { StringBuffer sb=new StringBuffer(); ComponentAccess ca; try { component=new ComponentWrap(Component.ACCESS_PRIVATE, ca=ComponentUtil.toComponentAccess(component)); } catch (ExpressionException e1) { throw toConverterException(e1); } boolean isPeristent=ca.isPersistent(); deep++; Object member; Iterator<Key> it = component.keyIterator(); Collection.Key key; while(it.hasNext()) { key=Caster.toKey(it.next(),null); member = component.get(key,null); if(member instanceof UDF) continue; sb.append(goIn()+"<var scope=\"this\" name="+_+key.toString()+_+">"); sb.append(_serialize(member,done)); sb.append(goIn()+"</var>"); } Property p; Boolean remotingFetch; Struct props = ignoreRemotingFetch?null:ComponentUtil.getPropertiesAsStruct(ca,false); ComponentScope scope = ca.getComponentScope(); it=scope.keyIterator(); while(it.hasNext()) { key=Caster.toKey(it.next(),null); if(!ignoreRemotingFetch) { p=(Property) props.get(key,null); if(p!=null) { remotingFetch=Caster.toBoolean(p.getDynamicAttributes().get(REMOTING_FETCH,null),null); if(remotingFetch==null){ if(isPeristent && ORMUtil.isRelated(p)) continue; } else if(!remotingFetch.booleanValue()) continue; } } member = scope.get(key,null); if(member instanceof UDF || key.equals(KeyConstants._this)) continue; sb.append(goIn()+"<var scope=\"variables\" name="+_+key.toString()+_+">"); sb.append(_serialize(member,done)); sb.append(goIn()+"</var>"); } deep--; try { //return goIn()+"<struct>"+sb+"</struct>"; return goIn()+"<component md5=\""+ComponentUtil.md5(component)+"\" name=\""+component.getAbsName()+"\">"+sb+"</component>"; } catch (Exception e) { throw toConverterException(e); } } /** * serialize a Struct * @param struct Struct to serialize * @param done * @return serialized struct * @throws ConverterException */ private String _serializeStruct(Struct struct, Set<Object> done) throws ConverterException { StringBuffer sb=new StringBuffer(goIn()+"<struct>"); Iterator<Key> it = struct.keyIterator(); deep++; while(it.hasNext()) { Key key = it.next(); sb.append(goIn()+"<var name="+_+key.toString()+_+">"); sb.append(_serialize(struct.get(key,null),done)); sb.append(goIn()+"</var>"); } deep--; sb.append(goIn()+"</struct>"); return sb.toString(); } /** * serialize a Map (as Struct) * @param map Map to serialize * @param done * @return serialized map * @throws ConverterException */ private String _serializeMap(Map map, Set<Object> done) throws ConverterException { StringBuffer sb=new StringBuffer(goIn()+"<struct>"); Iterator it=map.keySet().iterator(); deep++; while(it.hasNext()) { Object key=it.next(); sb.append(goIn()+"<var name="+_+key.toString()+_+">"); sb.append(_serialize(map.get(key),done)); sb.append(goIn()+"</var>"); } deep--; sb.append(goIn()+"</struct>"); return sb.toString(); } /** * serialize a Query * @param query Query to serialize * @param done * @return serialized query * @throws ConverterException */ private String _serializeQuery(Query query, Set<Object> done) throws ConverterException { Collection.Key[] keys = CollectionUtil.keys(query); StringBuffer sb=new StringBuffer(goIn()+"<recordset rowCount="+_+query.getRecordcount()+_+" fieldNames="+_+railo.runtime.type.util.ListUtil.arrayToList(keys,",")+_+" type="+_+"coldfusion.sql.QueryTable"+_+">"); deep++; int len=query.getRecordcount(); for(int i=0;i<keys.length;i++) { sb.append(goIn()+"<field name="+_+keys[i].getString()+_+">"); for(int y=1;y<=len;y++) { try { sb.append(_serialize(query.getAt(keys[i],y),done)); } catch (PageException e) { sb.append(_serialize(e.getMessage(),done)); } } sb.append(goIn()+"</field>"); } deep--; sb.append(goIn()+"</recordset>"); return sb.toString(); } /** * serialize a Object to his xml Format represenation * @param object Object to serialize * @param done * @return serialized Object * @throws ConverterException */ private String _serialize(Object object, Set<Object> done) throws ConverterException { String rtn; deep++; // NULL if(object==null) { rtn= goIn()+"<null/>"; deep--; return rtn; } // String if(object instanceof String) { rtn= goIn()+"<string>"+XMLUtil.escapeXMLString(object.toString())+"</string>"; deep--; return rtn; } // Number if(object instanceof Number) { rtn= goIn()+"<number>"+((Number)object).doubleValue()+"</number>"; deep--; return rtn; } // Boolean if(object instanceof Boolean) { rtn= goIn()+"<boolean value="+_+((Boolean)object).booleanValue()+_+"/>"; deep--; return rtn; } // DateTime if(object instanceof DateTime) { rtn= _serializeDateTime((DateTime)object); deep--; return rtn; } // Date if(object instanceof Date) { rtn= _serializeDate((Date)object); deep--; return rtn; } // Date if(Decision.isCastableToBinary(object, false)) { rtn= _serializeBinary(Caster.toBinary(object,null)); deep--; return rtn; } Object raw = LazyConverter.toRaw(object); if(done.contains(raw)){ rtn= goIn()+"<null/>"; deep--; return rtn; } done.add(raw); try { // Component if(object instanceof Component) { rtn= _serializeComponent((Component)object,done); deep--; return rtn; } // Struct if(object instanceof Struct) { rtn= _serializeStruct((Struct)object,done); deep--; return rtn; } // Map if(object instanceof Map) { rtn= _serializeMap((Map)object,done); deep--; return rtn; } // Array if(object instanceof Array) { rtn= _serializeArray((Array)object,done); deep--; return rtn; } // List if(object instanceof List) { rtn= _serializeList((List)object,done); deep--; return rtn; } // Query if(object instanceof Query) { rtn= _serializeQuery((Query)object,done); deep--; return rtn; } } finally{ done.remove(raw); } // Others rtn="<struct type="+_+"L"+object.getClass().getName()+";"+_+"></struct>"; deep--; return rtn; } @Override public void writeOut(PageContext pc, Object source, Writer writer) throws ConverterException, IOException { writer.write(serialize(source)); writer.flush(); } /** * serialize a Object to his xml Format represenation and create a valid wddx representation * @param object Object to serialize * @return serialized wddx package * @throws ConverterException */ public String serialize(Object object) throws ConverterException { deep=0; StringBuffer sb=new StringBuffer(); if(xmlConform)sb.append("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"); sb.append("<wddxPacket version="+_+"1.0"+_+">"); deep++; sb.append(goIn()+"<header/>"); sb.append(goIn()+"<data>"); sb.append(_serialize(object,new HashSet<Object>())); sb.append(goIn()+"</data>"); deep--; sb.append("</wddxPacket>"); return sb.toString(); } /** * deserialize a WDDX Package (XML String Representation) to a runtime object * @param strWddx * @param validate * @return Object represent WDDX Package * @throws ConverterException * @throws IOException * @throws FactoryConfigurationError */ public Object deserialize(String strWddx, boolean validate) throws ConverterException, IOException, FactoryConfigurationError { try { DOMParser parser = new DOMParser(); if(validate) parser.setEntityResolver(new WDDXEntityResolver()); parser.parse(new InputSource(new StringReader(strWddx))); Document doc=parser.getDocument(); // WDDX Package NodeList docChldren = doc.getChildNodes(); Node wddxPacket=doc; int len = docChldren.getLength(); for(int i = 0; i < len; i++) { Node node=docChldren.item(i); if(node.getNodeName().equalsIgnoreCase("wddxPacket")) { wddxPacket=node; break; } } NodeList nl = wddxPacket.getChildNodes(); int n = nl.getLength(); for(int i = 0; i < n; i++) { Node data = nl.item(i); if(data.getNodeName().equals("data")) { NodeList list=data.getChildNodes(); len=list.getLength(); for(int y=0;y<len;y++) { Node node=list.item(y); if(node instanceof Element) return _deserialize((Element)node); } } } throw new IllegalArgumentException("Invalid WDDX Format: node 'data' not found in WDD packet"); } catch(org.xml.sax.SAXException sxe) { throw new IllegalArgumentException("XML Error: " + sxe.toString()); } } /** * deserialize a WDDX Package (XML Element) to a runtime object * @param element * @return deserialized Element * @throws ConverterException */ private Object _deserialize(Element element) throws ConverterException { String nodeName=element.getNodeName().toLowerCase(); // NULL if(nodeName.equals("null")) { return null; } // String else if(nodeName.equals("string")) { return _deserializeString(element); /*Node data=element.getFirstChild(); if(data==null) return ""; String value=data.getNodeValue(); if(value==null) return ""; return XMLUtil.unescapeXMLString(value);*/ } // Number else if(nodeName.equals("number")) { try { Node data=element.getFirstChild(); if(data==null) return new Double(0); return Caster.toDouble(data.getNodeValue()); } catch (Exception e) { throw toConverterException(e); } } // Boolean else if(nodeName.equals("boolean")) { try { return Caster.toBoolean(element.getAttribute("value")); } catch (PageException e) { throw toConverterException(e); } } // Array else if(nodeName.equals("array")) { return _deserializeArray(element); } // Component else if(nodeName.equals("component")) { return _deserializeComponent(element); } // Struct else if(nodeName.equals("struct")) { return _deserializeStruct(element); } // Query else if(nodeName.equals("recordset")) { return _deserializeQuery(element); } // DateTime else if(nodeName.equalsIgnoreCase("dateTime")) { try { return DateCaster.toDateAdvanced(element.getFirstChild().getNodeValue(),timeZone); } catch (Exception e) { throw toConverterException(e); } } // Query else if(nodeName.equals("binary")) { return _deserializeBinary(element); } else throw new ConverterException("can't deserialize Element of type ["+nodeName+"] to a Object representation"); } private Object _deserializeString(Element element) { NodeList childList = element.getChildNodes(); int len = childList.getLength(); StringBuffer sb=new StringBuffer(); Node data; String str; for(int i=0;i<len;i++) { data=childList.item(i); if(data==null)continue; //<char code="0a"/> if("char".equals(data.getNodeName())) { str=((Element)data).getAttribute("code"); sb.append((char)NumberUtil.hexToInt(str, 10)); } else { sb.append(str=data.getNodeValue()); } } return sb.toString(); //return XMLUtil.unescapeXMLString(sb.toString()); } /** * Desirialize a Query Object * @param recordset Query Object as XML Element * @return Query Object * @throws ConverterException */ private Object _deserializeQuery(Element recordset) throws ConverterException { try { // create Query Object Query query=new QueryImpl( railo.runtime.type.util.ListUtil.listToArray( recordset.getAttribute("fieldNames"),',' ) ,Caster.toIntValue(recordset.getAttribute("rowCount")),"query" ); NodeList list = recordset.getChildNodes(); int len=list.getLength(); for(int i=0;i<len;i++) { Node node=list.item(i); if(node instanceof Element) { _deserializeQueryField(query,(Element) node); } } return query; } catch(PageException e) { throw toConverterException(e); } } private Object _deserializeBinary(Element el) throws ConverterException { Node node = el.getFirstChild(); if(node instanceof CharacterData) { String data=((CharacterData)node).getData(); try { return Base64Coder.decode(data); } catch (CoderException e) { throw new ConverterException(e.getMessage()); } } throw new ConverterException("cannot convert serialized binary back to binary data"); } /** * deserilize a single Field of a query WDDX Object * @param query * @param field * @throws ConverterException * @throws PageException */ private void _deserializeQueryField(Query query,Element field) throws PageException, ConverterException { String name=field.getAttribute("name"); NodeList list = field.getChildNodes(); int len=list.getLength(); int count=0; for(int i=0;i<len;i++) { Node node=list.item(i); if(node instanceof Element) { query.setAt(KeyImpl.init(name),++count,_deserialize((Element) node)); } } } /** * Desirialize a Component Object * @param elComp Component Object as XML Element * @return Component Object * @throws ConverterException * @throws ConverterException */ private Object _deserializeComponent(Element elComp) throws ConverterException { // String type=elStruct.getAttribute("type"); String name=elComp.getAttribute("name"); String md5=elComp.getAttribute("md5"); // TLPC PageContext pc = ThreadLocalPageContext.get(); // Load comp ComponentAccess comp=null; try { comp = ComponentUtil.toComponentAccess(pc.loadComponent(name)); if(!ComponentUtil.md5(comp).equals(md5)){ throw new ConverterException("component ["+name+"] in this enviroment has not the same interface as the component to load, it is possible that one off the components has Functions added dynamicly."); } } catch (ConverterException e) { throw e; } catch (Exception e) { throw new ConverterException(e.getMessage()); } NodeList list=elComp.getChildNodes(); ComponentScope scope = comp.getComponentScope(); int len=list.getLength(); String scopeName; Element var,value; Collection.Key key; for(int i=0;i<len;i++) { Node node=list.item(i); if(node instanceof Element) { var=(Element)node; value=getChildElement((Element)node); scopeName=var.getAttribute("scope"); if(value!=null) { key=Caster.toKey(var.getAttribute("name"),null); if(key==null) continue; if("variables".equalsIgnoreCase(scopeName)) scope.setEL(key,_deserialize(value)); else comp.setEL(key,_deserialize(value)); } } } return comp; } /** * Desirialize a Struct Object * @param elStruct Struct Object as XML Element * @return Struct Object * @throws ConverterException */ private Object _deserializeStruct(Element elStruct) throws ConverterException { String type=elStruct.getAttribute("type"); Struct struct=new StructImpl(); NodeList list=elStruct.getChildNodes(); int len=list.getLength(); for(int i=0;i<len;i++) { //print.ln(i); Node node=list.item(i); if(node instanceof Element) { Element var=(Element)node; Element value=getChildElement((Element)node); if(value!=null) { struct.setEL(var.getAttribute("name"),_deserialize(value)); } } } if(struct.size()==0 && type!=null && type.length()>0) { return ""; } return struct; } /** * Desirialize a Struct Object * @param el Struct Object as XML Element * @return Struct Object * @throws ConverterException */ private Array _deserializeArray(Element el) throws ConverterException { Array array=new ArrayImpl(); NodeList list=el.getChildNodes(); int len=list.getLength(); for(int i=0;i<len;i++) { Node node=list.item(i); if(node instanceof Element) try { array.append(_deserialize((Element)node)); } catch (PageException e) { throw toConverterException(e); } } return array; } /** * return fitst child Element of a Element, if there are no child Elements return null * @param parent parent node * @return child Element */ private Element getChildElement(Element parent) { NodeList list=parent.getChildNodes(); int len=list.getLength(); for(int i=0;i<len;i++) { Node node=list.item(i); if(node instanceof Element) { return (Element)node; } } return null; } /** * @return return current blockquote */ private String goIn() { //StringBuffer rtn=new StringBuffer(deep); //for(int i=0;i<deep;i++) rtn.append('\t'); //return rtn.toString(); return ""; } @Override public boolean equals(Object obj) { return timeZone.equals(obj); } }