001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.converter; 020 021import java.io.File; 022import java.io.IOException; 023import java.io.Writer; 024import java.lang.reflect.Field; 025import java.lang.reflect.Method; 026import java.lang.reflect.Modifier; 027import java.nio.charset.Charset; 028import java.nio.charset.CharsetEncoder; 029import java.util.Date; 030import java.util.HashSet; 031import java.util.Iterator; 032import java.util.List; 033import java.util.ListIterator; 034import java.util.Map; 035import java.util.Map.Entry; 036import java.util.Set; 037 038import lucee.commons.lang.CFTypes; 039import lucee.commons.lang.ExceptionUtil; 040import lucee.commons.lang.StringUtil; 041import lucee.runtime.Component; 042import lucee.runtime.ComponentScope; 043import lucee.runtime.ComponentSpecificAccess; 044import lucee.runtime.PageContext; 045import lucee.runtime.component.Property; 046import lucee.runtime.exp.PageException; 047import lucee.runtime.java.JavaObject; 048import lucee.runtime.op.Caster; 049import lucee.runtime.op.Decision; 050import lucee.runtime.orm.ORMUtil; 051import lucee.runtime.reflection.Reflector; 052import lucee.runtime.text.xml.XMLCaster; 053import lucee.runtime.type.Array; 054import lucee.runtime.type.Collection; 055import lucee.runtime.type.Collection.Key; 056import lucee.runtime.type.KeyImpl; 057import lucee.runtime.type.ObjectWrap; 058import lucee.runtime.type.Query; 059import lucee.runtime.type.Struct; 060import lucee.runtime.type.StructImpl; 061import lucee.runtime.type.UDF; 062import lucee.runtime.type.dt.DateTime; 063import lucee.runtime.type.dt.DateTimeImpl; 064import lucee.runtime.type.dt.TimeSpan; 065import lucee.runtime.type.util.ArrayUtil; 066import lucee.runtime.type.util.CollectionUtil; 067import lucee.runtime.type.util.ComponentProUtil; 068import lucee.runtime.type.util.ComponentUtil; 069 070import org.w3c.dom.Node; 071 072/** 073 * class to serialize and desirilize WDDX Packes 074 */ 075public final class JSONConverter extends ConverterSupport { 076 077 private static final Collection.Key REMOTING_FETCH = KeyImpl.intern("remotingFetch"); 078 079 private static final Key TO_JSON = KeyImpl.intern("_toJson"); 080 private static final Object NULL = new Object(); 081 private static final String NULL_STRING = ""; 082 083 private boolean ignoreRemotingFetch; 084 085 private CharsetEncoder charsetEncoder; 086 087 /** 088 * @param ignoreRemotingFetch 089 * @param charset if set, characters not supported by the charset are escaped. 090 */ 091 public JSONConverter(boolean ignoreRemotingFetch, Charset charset) { 092 this.ignoreRemotingFetch=ignoreRemotingFetch; 093 charsetEncoder = charset!=null?charset.newEncoder():null;//.canEncode("string"); 094 } 095 096 097 /** 098 * serialize Serializable class 099 * @param serializable 100 * @param sb 101 * @param serializeQueryByColumns 102 * @param done 103 * @throws ConverterException 104 */ 105 106 private void _serializeClass(PageContext pc,Set test,Class clazz,Object obj, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 107 Struct sct=new StructImpl(Struct.TYPE_LINKED); 108 if(test==null)test=new HashSet(); 109 110 // Fields 111 Field[] fields = clazz.getFields(); 112 Field field; 113 for(int i=0;i<fields.length;i++){ 114 field=fields[i]; 115 if(obj!=null || (field.getModifiers()&Modifier.STATIC)>0) 116 try { 117 sct.setEL(field.getName(), testRecusrion(test,field.get(obj))); 118 } catch (Exception e) { 119 e.printStackTrace(); 120 } 121 } 122 if(obj !=null){ 123 // setters 124 Method[] setters=Reflector.getSetters(clazz); 125 for(int i=0;i<setters.length;i++){ 126 sct.setEL(setters[i].getName().substring(3), NULL); 127 } 128 // getters 129 Method[] getters=Reflector.getGetters(clazz); 130 for(int i=0;i<getters.length;i++){ 131 try { 132 sct.setEL(getters[i].getName().substring(3), testRecusrion(test,getters[i].invoke(obj, ArrayUtil.OBJECT_EMPTY))); 133 134 } 135 catch (Exception e) {} 136 } 137 } 138 test.add(clazz); 139 140 141 _serializeStruct(pc,test,sct, sb, serializeQueryByColumns, true,done); 142 } 143 144 145 private Object testRecusrion(Set test, Object obj) { 146 if(test.contains(obj.getClass())) return obj.getClass().getName(); 147 return obj; 148 } 149 150 151 /** 152 * serialize a Date 153 * @param date Date to serialize 154 * @param sb 155 * @throws ConverterException 156 */ 157 private void _serializeDate(Date date, StringBuilder sb) { 158 _serializeDateTime(new DateTimeImpl(date),sb); 159 } 160 /** 161 * serialize a DateTime 162 * @param dateTime DateTime to serialize 163 * @param sb 164 * @throws ConverterException 165 */ 166 private void _serializeDateTime(DateTime dateTime, StringBuilder sb) { 167 168 sb.append(StringUtil.escapeJS(JSONDateFormat.format(dateTime,null),'"',charsetEncoder)); 169 170 /*try { 171 sb.append(goIn()); 172 sb.append("createDateTime("); 173 sb.append(DateFormat.call(null,dateTime,"yyyy,m,d")); 174 sb.append(' '); 175 sb.append(TimeFormat.call(null,dateTime,"HH:mm:ss")); 176 sb.append(')'); 177 } 178 catch (PageException e) { 179 throw new ConverterException(e); 180 }*/ 181 //Januar, 01 2000 01:01:01 182 } 183 184 /** 185 * serialize a Array 186 * @param array Array to serialize 187 * @param sb 188 * @param serializeQueryByColumns 189 * @param done 190 * @throws ConverterException 191 */ 192 private void _serializeArray(PageContext pc,Set test,Array array, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 193 _serializeList(pc,test,array.toList(),sb,serializeQueryByColumns,done); 194 } 195 196 /** 197 * serialize a List (as Array) 198 * @param list List to serialize 199 * @param sb 200 * @param serializeQueryByColumns 201 * @param done 202 * @throws ConverterException 203 */ 204 private void _serializeList(PageContext pc,Set test,List list, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 205 206 sb.append(goIn()); 207 sb.append("["); 208 boolean doIt=false; 209 ListIterator it=list.listIterator(); 210 while(it.hasNext()) { 211 if(doIt)sb.append(','); 212 doIt=true; 213 _serialize(pc,test,it.next(),sb,serializeQueryByColumns,done); 214 } 215 216 sb.append(']'); 217 } 218 private void _serializeArray(PageContext pc,Set test,Object[] arr, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 219 220 sb.append(goIn()); 221 sb.append("["); 222 for(int i=0;i<arr.length;i++) { 223 if(i>0)sb.append(','); 224 _serialize(pc,test,arr[i],sb,serializeQueryByColumns,done); 225 } 226 sb.append(']'); 227 } 228 229 /** 230 * serialize a Struct 231 * @param struct Struct to serialize 232 * @param sb 233 * @param serializeQueryByColumns 234 * @param addUDFs 235 * @param done 236 * @throws ConverterException 237 */ 238 public void _serializeStruct(PageContext pc,Set test,Struct struct, StringBuilder sb, boolean serializeQueryByColumns, boolean addUDFs, Set<Object> done) throws ConverterException { 239 // Component 240 if(struct instanceof Component){ 241 String res = castToJson(pc, (Component)struct, NULL_STRING); 242 if(res!=NULL_STRING) { 243 sb.append(res); 244 return; 245 } 246 } 247 248 249 sb.append(goIn()); 250 sb.append("{"); 251 //Key[] keys = struct.keys(); 252 //Key key; 253 Iterator<Entry<Key, Object>> it = struct.entryIterator(); 254 Entry<Key, Object> e; 255 Object value; 256 boolean doIt=false; 257 while(it.hasNext()) { 258 e = it.next(); 259 //key=keys[i]; 260 value=e.getValue(); 261 if(!addUDFs && (value instanceof UDF || value==null))continue; 262 if(doIt)sb.append(','); 263 doIt=true; 264 sb.append(StringUtil.escapeJS(e.getKey().getString(),'"',charsetEncoder)); 265 sb.append(':'); 266 _serialize(pc,test,value,sb,serializeQueryByColumns,done); 267 } 268 269 if(struct instanceof Component){ 270 Boolean remotingFetch; 271 Component cfc = (Component)struct; 272 boolean isPeristent=false; 273 isPeristent=ComponentProUtil.isPersistent(cfc); 274 275 Property[] props = cfc.getProperties(false); 276 ComponentScope scope = cfc.getComponentScope(); 277 for(int i=0;i<props.length;i++) { 278 if(!ignoreRemotingFetch) { 279 remotingFetch=Caster.toBoolean(props[i].getDynamicAttributes().get(REMOTING_FETCH,null),null); 280 if(remotingFetch==null){ 281 if(isPeristent && ORMUtil.isRelated(props[i])) continue; 282 } 283 else if(!remotingFetch.booleanValue()) continue; 284 285 } 286 Key key = KeyImpl.getInstance(props[i].getName()); 287 value=scope.get(key,null); 288 if(!addUDFs && (value instanceof UDF || value==null))continue; 289 if(doIt)sb.append(','); 290 doIt=true; 291 sb.append(StringUtil.escapeJS(key.getString(),'"',charsetEncoder)); 292 sb.append(':'); 293 _serialize(pc,test,value,sb,serializeQueryByColumns,done); 294 } 295 } 296 297 298 sb.append('}'); 299 } 300 301 private static String castToJson(PageContext pc,Component cfc, String defaultValue) throws ConverterException { 302 Object o=cfc.get(TO_JSON,null); 303 if(!(o instanceof UDF)) return defaultValue; 304 UDF udf=(UDF) o; 305 if(udf.getReturnType()!=CFTypes.TYPE_VOID && udf.getFunctionArguments().length==0) { 306 try { 307 return Caster.toString(cfc.call(pc, TO_JSON, new Object[0])); 308 } catch (PageException e) { 309 e.printStackTrace(); 310 throw toConverterException(e); 311 } 312 } 313 return defaultValue; 314 } 315 316 317 318 /** 319 * serialize a Map (as Struct) 320 * @param map Map to serialize 321 * @param sb 322 * @param serializeQueryByColumns 323 * @param done 324 * @throws ConverterException 325 */ 326 private void _serializeMap(PageContext pc,Set test,Map map, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 327 sb.append(goIn()); 328 sb.append("{"); 329 330 Iterator it=map.keySet().iterator(); 331 boolean doIt=false; 332 while(it.hasNext()) { 333 Object key=it.next(); 334 if(doIt)sb.append(','); 335 doIt=true; 336 sb.append(StringUtil.escapeJS(key.toString(),'"',charsetEncoder)); 337 sb.append(':'); 338 _serialize(pc,test,map.get(key),sb,serializeQueryByColumns,done); 339 } 340 341 sb.append('}'); 342 } 343 /** 344 * serialize a Component 345 * @param component Component to serialize 346 * @param sb 347 * @param serializeQueryByColumns 348 * @param done 349 * @throws ConverterException 350 */ 351 private void _serializeComponent(PageContext pc,Set test,Component component, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 352 ComponentSpecificAccess cw = ComponentSpecificAccess.toComponentSpecificAccess(Component.ACCESS_PRIVATE,component); 353 _serializeStruct(pc,test,cw, sb, serializeQueryByColumns,false,done); 354 } 355 356 357 private void _serializeUDF(PageContext pc,Set test,UDF udf, StringBuilder sb,boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 358 Struct sct=new StructImpl(); 359 try { 360 // Meta 361 Struct meta = udf.getMetaData(pc); 362 sct.setEL("Metadata", meta); 363 364 // Parameters 365 sct.setEL("MethodAttributes", meta.get("PARAMETERS")); 366 } 367 catch (PageException e) { 368 throw toConverterException(e); 369 } 370 371 sct.setEL("Access", ComponentUtil.toStringAccess(udf.getAccess(),"public")); 372 sct.setEL("Output", Caster.toBoolean(udf.getOutput())); 373 sct.setEL("ReturnType", udf.getReturnTypeAsString()); 374 try{ 375 sct.setEL("PagePath", udf.getPageSource().getResource().getAbsolutePath()); 376 }catch(Throwable t){ 377 ExceptionUtil.rethrowIfNecessary(t); 378 } 379 380 _serializeStruct(pc,test,sct, sb, serializeQueryByColumns, true,done); 381 // TODO key SuperScope and next? 382 } 383 384 385 386 /** 387 * serialize a Query 388 * @param query Query to serialize 389 * @param sb 390 * @param serializeQueryByColumns 391 * @param done 392 * @throws ConverterException 393 */ 394 private void _serializeQuery(PageContext pc,Set test,Query query, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 395 396 Collection.Key[] _keys = CollectionUtil.keys(query); 397 sb.append(goIn()); 398 sb.append("{"); 399 400 /* 401 402{"DATA":[["a","b"],["c","d"]]} 403{"DATA":{"aaa":["a","c"],"bbb":["b","d"]}} 404 * */ 405 // Rowcount 406 if(serializeQueryByColumns){ 407 sb.append("\"ROWCOUNT\":"); 408 sb.append(Caster.toString(query.getRecordcount())); 409 sb.append(','); 410 } 411 412 // Columns 413 sb.append("\"COLUMNS\":["); 414 String[] cols = query.getColumns(); 415 for(int i=0;i<cols.length;i++) { 416 if(i>0)sb.append(","); 417 sb.append(StringUtil.escapeJS(cols[i].toUpperCase(),'"',charsetEncoder)); 418 } 419 sb.append("],"); 420 421 // Data 422 sb.append("\"DATA\":"); 423 if(serializeQueryByColumns) { 424 sb.append('{'); 425 boolean oDoIt=false; 426 int len=query.getRecordcount(); 427 for(int i=0;i<_keys.length;i++) { 428 if(oDoIt)sb.append(','); 429 oDoIt=true; 430 sb.append(goIn()); 431 sb.append(StringUtil.escapeJS(_keys[i].getString(),'"',charsetEncoder)); 432 sb.append(":["); 433 boolean doIt=false; 434 for(int y=1;y<=len;y++) { 435 if(doIt)sb.append(','); 436 doIt=true; 437 try { 438 _serialize(pc,test,query.getAt(_keys[i],y),sb,serializeQueryByColumns,done); 439 } catch (PageException e) { 440 _serialize(pc,test,e.getMessage(),sb,serializeQueryByColumns,done); 441 } 442 } 443 444 sb.append(']'); 445 } 446 447 sb.append('}'); 448 } 449 else { 450 sb.append('['); 451 boolean oDoIt=false; 452 int len=query.getRecordcount(); 453 for(int row=1;row<=len;row++) { 454 if(oDoIt)sb.append(','); 455 oDoIt=true; 456 457 sb.append("["); 458 boolean doIt=false; 459 for(int col=0;col<_keys.length;col++) { 460 if(doIt)sb.append(','); 461 doIt=true; 462 try { 463 _serialize(pc,test,query.getAt(_keys[col],row),sb,serializeQueryByColumns,done); 464 } catch (PageException e) { 465 _serialize(pc,test,e.getMessage(),sb,serializeQueryByColumns,done); 466 } 467 } 468 sb.append(']'); 469 } 470 sb.append(']'); 471 } 472 sb.append('}'); 473 } 474 475 /** 476 * serialize a Object to his xml Format represenation 477 * @param object Object to serialize 478 * @param sb StringBuilder to write data 479 * @param serializeQueryByColumns 480 * @param done 481 * @throws ConverterException 482 */ 483 private void _serialize(PageContext pc,Set test,Object object, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException { 484 485 // NULL 486 if(object==null || object==NULL) { 487 sb.append(goIn()); 488 sb.append("null"); 489 return; 490 } 491 // String 492 if(object instanceof String || object instanceof StringBuilder) { 493 sb.append(goIn()); 494 sb.append(StringUtil.escapeJS(object.toString(),'"',charsetEncoder)); 495 return; 496 } 497 // Character 498 if(object instanceof Character) { 499 sb.append(goIn()); 500 sb.append(StringUtil.escapeJS(String.valueOf(((Character)object).charValue()),'"',charsetEncoder)); 501 return; 502 } 503 // Number 504 if(object instanceof Number) { 505 sb.append(goIn()); 506 sb.append(Caster.toString(((Number)object))); 507 return; 508 } 509 // Boolean 510 if(object instanceof Boolean) { 511 sb.append(goIn()); 512 sb.append(Caster.toString(((Boolean)object).booleanValue())); 513 return; 514 } 515 // DateTime 516 if(object instanceof DateTime) { 517 _serializeDateTime((DateTime)object,sb); 518 return; 519 } 520 // Date 521 if(object instanceof Date) { 522 _serializeDate((Date)object,sb); 523 return; 524 } 525 // XML 526 if(object instanceof Node) { 527 _serializeXML((Node)object,sb); 528 return; 529 } 530 // Timespan 531 if(object instanceof TimeSpan) { 532 _serializeTimeSpan((TimeSpan) object,sb); 533 return; 534 } 535 // File 536 if(object instanceof File) { 537 _serialize(pc,test, ((File)object).getAbsolutePath(), sb, serializeQueryByColumns,done); 538 return; 539 } 540 // String Converter 541 if(object instanceof ScriptConvertable) { 542 sb.append(((ScriptConvertable)object).serialize()); 543 return; 544 } 545 Object raw = LazyConverter.toRaw(object); 546 if(done.contains(raw)){ 547 sb.append(goIn()); 548 sb.append("null"); 549 return; 550 } 551 552 553 done.add(raw); 554 try{ 555 // Component 556 if(object instanceof Component) { 557 _serializeComponent(pc,test,(Component)object,sb,serializeQueryByColumns,done); 558 return; 559 } 560 // UDF 561 if(object instanceof UDF) { 562 _serializeUDF(pc,test,(UDF)object,sb,serializeQueryByColumns,done); 563 return; 564 } 565 // Struct 566 if(object instanceof Struct) { 567 _serializeStruct(pc,test,(Struct)object,sb,serializeQueryByColumns,true,done); 568 return; 569 } 570 // Map 571 if(object instanceof Map) { 572 _serializeMap(pc,test,(Map)object,sb,serializeQueryByColumns,done); 573 return; 574 } 575 // Array 576 if(object instanceof Array) { 577 _serializeArray(pc,test,(Array)object,sb,serializeQueryByColumns,done); 578 return; 579 } 580 // List 581 if(object instanceof List) { 582 _serializeList(pc,test,(List)object,sb,serializeQueryByColumns,done); 583 return; 584 } 585 // Query 586 if(object instanceof Query) { 587 _serializeQuery(pc,test,(Query)object,sb,serializeQueryByColumns,done); 588 return; 589 } 590 // Native Array 591 if(Decision.isNativeArray(object)){ 592 if(object instanceof char[]) 593 _serialize(pc,test,new String((char[])object), sb, serializeQueryByColumns,done); 594 else { 595 _serializeArray(pc,test,ArrayUtil.toReferenceType(object,ArrayUtil.OBJECT_EMPTY), sb, serializeQueryByColumns,done); 596 } 597 return; 598 599 } 600 // ObjectWrap 601 if(object instanceof ObjectWrap) { 602 try { 603 _serialize(pc,test,((ObjectWrap)object).getEmbededObject(), sb, serializeQueryByColumns,done); 604 } catch (PageException e) { 605 if(object instanceof JavaObject){ 606 _serializeClass(pc,test,((JavaObject)object).getClazz(),null,sb,serializeQueryByColumns,done); 607 } 608 else throw new ConverterException("can't serialize Object of type [ "+Caster.toClassName(object)+" ]"); 609 } 610 return; 611 } 612 613 _serializeClass(pc,test,object.getClass(),object,sb,serializeQueryByColumns,done); 614 } 615 finally{ 616 done.remove(raw); 617 } 618 } 619 620 private void _serializeXML(Node node, StringBuilder sb) { 621 node=XMLCaster.toRawNode(node); 622 sb.append(goIn()); 623 sb.append(StringUtil.escapeJS(XMLCaster.toString(node,""),'"',charsetEncoder)); 624 } 625 626 627 private void _serializeTimeSpan(TimeSpan span, StringBuilder sb) { 628 629 sb.append(goIn()); 630 sb.append("createTimeSpan("); 631 sb.append(span.getDay()); 632 sb.append(','); 633 sb.append(span.getHour()); 634 sb.append(','); 635 sb.append(span.getMinute()); 636 sb.append(','); 637 sb.append(span.getSecond()); 638 sb.append(')'); 639 640 } 641 642 /** 643 * serialize a Object to his literal Format 644 * @param object Object to serialize 645 * @param serializeQueryByColumns 646 * @return serialized wddx package 647 * @throws ConverterException 648 */ 649 public String serialize(PageContext pc,Object object, boolean serializeQueryByColumns) throws ConverterException { 650 StringBuilder sb=new StringBuilder(); 651 _serialize(pc,null,object,sb,serializeQueryByColumns,new HashSet<Object>()); 652 return sb.toString(); 653 } 654 655 @Override 656 public void writeOut(PageContext pc, Object source, Writer writer) throws ConverterException, IOException { 657 writer.write(serialize(pc,source,false)); 658 writer.flush(); 659 } 660 661 662 /** 663 * @return return current blockquote 664 */ 665 private String goIn() { 666 return ""; 667 } 668 669 670}