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.dump; 020 021import java.io.File; 022import java.lang.reflect.Field; 023import java.lang.reflect.Method; 024import java.sql.ResultSet; 025import java.text.SimpleDateFormat; 026import java.util.Calendar; 027import java.util.Date; 028import java.util.Enumeration; 029import java.util.Iterator; 030import java.util.List; 031import java.util.ListIterator; 032import java.util.Locale; 033import java.util.Map; 034import java.util.Set; 035 036import javax.servlet.http.Cookie; 037import javax.servlet.http.HttpSession; 038 039import lucee.commons.date.TimeZoneUtil; 040import lucee.commons.io.res.Resource; 041import lucee.commons.lang.ExceptionUtil; 042import lucee.commons.lang.IDGenerator; 043import lucee.commons.lang.StringUtil; 044import lucee.runtime.PageContext; 045import lucee.runtime.coder.Base64Coder; 046import lucee.runtime.converter.WDDXConverter; 047import lucee.runtime.exp.PageException; 048import lucee.runtime.net.rpc.Pojo; 049import lucee.runtime.op.Caster; 050import lucee.runtime.op.Decision; 051import lucee.runtime.text.xml.XMLCaster; 052import lucee.runtime.type.Array; 053import lucee.runtime.type.Collection; 054import lucee.runtime.type.ObjectWrap; 055import lucee.runtime.type.QueryImpl; 056import lucee.runtime.type.dt.DateTimeImpl; 057import lucee.runtime.type.scope.CookieImpl; 058 059import org.w3c.dom.NamedNodeMap; 060import org.w3c.dom.Node; 061import org.w3c.dom.NodeList; 062 063public class DumpUtil { 064 065 public static final DumpData MAX_LEVEL_REACHED; 066 067 static { 068 069 MAX_LEVEL_REACHED = new DumpTable("Max Level Reached","#e0e0e0","#ffcc99","#888888"); 070 ((DumpTable)MAX_LEVEL_REACHED).appendRow( new DumpRow(1, new SimpleDumpData("[Max Dump Level Reached]") ) ); 071 } 072 073 public static DumpData toDumpData(Object o, PageContext pageContext, int maxlevel, DumpProperties props) { 074 if(maxlevel<0) 075 return MAX_LEVEL_REACHED; 076 077 // null 078 if(o == null) { 079 DumpTable table=new DumpTable("null","#ff6600","#ffcc99","#000000"); 080 table.appendRow(new DumpRow(0,new SimpleDumpData("Empty:null"))); 081 return table; 082 } 083 if(o instanceof DumpData) { 084 return ((DumpData)o); 085 } 086 // Date 087 if(o instanceof Date) { 088 return new DateTimeImpl((Date) o).toDumpData(pageContext,maxlevel,props); 089 } 090 // Calendar 091 if(o instanceof Calendar) { 092 Calendar c=(Calendar)o; 093 094 SimpleDateFormat df = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss zz",Locale.ENGLISH); 095 df.setTimeZone(c.getTimeZone()); 096 097 DumpTable table=new DumpTable("date","#ff9900","#ffcc00","#000000"); 098 table.setTitle("java.util.Calendar"); 099 table.appendRow(1, new SimpleDumpData("Timezone"), new SimpleDumpData(TimeZoneUtil.toString(c.getTimeZone()))); 100 table.appendRow(1, new SimpleDumpData("Time"), new SimpleDumpData(df.format(c.getTime()))); 101 102 return table; 103 } 104 // StringBuffer 105 if(o instanceof StringBuffer) { 106 DumpTable dt=(DumpTable)toDumpData(o.toString(), pageContext, maxlevel, props); 107 if(StringUtil.isEmpty(dt.getTitle())) 108 dt.setTitle(Caster.toClassName(o)); 109 return dt; 110 } 111 // StringBuilder 112 if(o instanceof StringBuilder) { 113 DumpTable dt=(DumpTable)toDumpData(o.toString(), pageContext, maxlevel, props); 114 if(StringUtil.isEmpty(dt.getTitle())) 115 dt.setTitle(Caster.toClassName(o)); 116 return dt; 117 } 118 // String 119 if(o instanceof String) { 120 String str=(String) o; 121 if(str.trim().startsWith("<wddxPacket ")) { 122 try { 123 WDDXConverter converter =new WDDXConverter(pageContext.getTimeZone(),false,true); 124 converter.setTimeZone(pageContext.getTimeZone()); 125 Object rst = converter.deserialize(str,false); 126 DumpData data = toDumpData(rst, pageContext, maxlevel, props); 127 128 DumpTable table = new DumpTable("string","#cc9999","#ffffff","#000000"); 129 table.setTitle("WDDX"); 130 table.appendRow(1,new SimpleDumpData("encoded"),data); 131 table.appendRow(1,new SimpleDumpData("raw"),new SimpleDumpData(str)); 132 return table; 133 } 134 catch(Throwable t) { 135 ExceptionUtil.rethrowIfNecessary(t); 136 } 137 } 138 DumpTable table = new DumpTable("string","#ff6600","#ffcc99","#000000"); 139 table.appendRow(1,new SimpleDumpData("string"),new SimpleDumpData(str)); 140 return table; 141 } 142 // Character 143 if(o instanceof Character) { 144 DumpTable table = new DumpTable("character","#ff6600","#ffcc99","#000000"); 145 table.appendRow(1,new SimpleDumpData("character"),new SimpleDumpData(o.toString())); 146 return table; 147 } 148 // Number 149 if(o instanceof Number) { 150 DumpTable table = new DumpTable("numeric","#ff6600","#ffcc99","#000000"); 151 table.appendRow(1,new SimpleDumpData("number"),new SimpleDumpData(Caster.toString(((Number)o)))); 152 return table; 153 } 154 // Boolean 155 if(o instanceof Boolean) { 156 DumpTable table = new DumpTable("boolean","#ff6600","#ffcc99","#000000"); 157 table.appendRow(1,new SimpleDumpData("boolean"),new SimpleDumpData(((Boolean)o).booleanValue())); 158 return table; 159 } 160 // File 161 if(o instanceof File) { 162 DumpTable table = new DumpTable("file","#ffcc00","#ffff66","#000000"); 163 table.appendRow(1,new SimpleDumpData("File"),new SimpleDumpData(o.toString())); 164 return table; 165 } 166 // Cookie 167 if(o instanceof Cookie) { 168 Cookie c=(Cookie) o; 169 DumpTable table = new DumpTable("Cookie","#979EAA","#DEE9FB","#000000"); 170 table.setTitle("Cookie ("+c.getClass().getName()+")"); 171 table.appendRow(1,new SimpleDumpData("name"),new SimpleDumpData(c.getName())); 172 table.appendRow(1,new SimpleDumpData("value"),new SimpleDumpData(c.getValue())); 173 table.appendRow(1,new SimpleDumpData("path"),new SimpleDumpData(c.getPath())); 174 table.appendRow(1,new SimpleDumpData("secure"),new SimpleDumpData(c.getSecure())); 175 table.appendRow(1,new SimpleDumpData("maxAge"),new SimpleDumpData(c.getMaxAge())); 176 table.appendRow(1,new SimpleDumpData("version"),new SimpleDumpData(c.getVersion())); 177 table.appendRow(1,new SimpleDumpData("domain"),new SimpleDumpData(c.getDomain())); 178 table.appendRow(1,new SimpleDumpData("httpOnly"),new SimpleDumpData(CookieImpl.isHTTPOnly(c))); 179 table.appendRow(1,new SimpleDumpData("comment"),new SimpleDumpData(c.getComment())); 180 return table; 181 } 182 // Resource 183 if(o instanceof Resource) { 184 DumpTable table = new DumpTable("resource","#ffcc00","#ffff66","#000000"); 185 table.appendRow(1,new SimpleDumpData("Resource"),new SimpleDumpData(o.toString())); 186 return table; 187 } 188 // byte[] 189 if(o instanceof byte[]) { 190 byte[] bytes=(byte[]) o; 191 int max=5000; 192 DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000"); 193 table.setTitle("Native Array ("+Caster.toClassName(o)+")"); 194 StringBuilder sb=new StringBuilder("["); 195 for(int i=0;i<bytes.length;i++) { 196 if(i!=0)sb.append(","); 197 sb.append(bytes[i]); 198 if(i==max) { 199 sb.append(", ...truncated"); 200 break; 201 } 202 } 203 sb.append("]"); 204 table.appendRow(1,new SimpleDumpData("Raw"+(bytes.length<max?"":" (truncated)")),new SimpleDumpData(sb.toString())); 205 206 207 if(bytes.length<max) { 208 // base64 209 table.appendRow(1,new SimpleDumpData("Base64 Encoded"),new SimpleDumpData(Base64Coder.encode(bytes))); 210 /*try { 211 table.appendRow(1,new SimpleDumpData("CFML expression"),new SimpleDumpData("evaluateJava('"+JavaConverter.serialize(bytes)+"')")); 212 213 } 214 catch (IOException e) {}*/ 215 } 216 217 218 return table; 219 } 220 // Collection.Key 221 if(o instanceof Collection.Key) { 222 Collection.Key key=(Collection.Key) o; 223 DumpTable table = new DumpTable("string","#ff6600","#ffcc99","#000000"); 224 table.appendRow(1,new SimpleDumpData("Collection.Key"),new SimpleDumpData(key.getString())); 225 return table; 226 } 227 228 229 String id=""+IDGenerator.intId(); 230 String refid=ThreadLocalDump.get(o); 231 if(refid!=null) { 232 DumpTable table = new DumpTable("ref","#ffffff","#cccccc","#000000"); 233 table.appendRow(1,new SimpleDumpData("Reference"),new SimpleDumpData(refid)); 234 table.setRef(refid); 235 return setId(id,table); 236 } 237 238 ThreadLocalDump.set(o,id); 239 try{ 240 241 int top = props.getMaxlevel(); 242 243 // Printable 244 if(o instanceof Dumpable) { 245 DumpData dd = ((Dumpable)o).toDumpData(pageContext,maxlevel,props); 246 return setId(id, dd); 247 } 248 // Map 249 if(o instanceof Map) { 250 Map map=(Map) o; 251 Iterator it=map.keySet().iterator(); 252 253 DumpTable table = new DumpTable("struct","#ff9900","#ffcc00","#000000"); 254 table.setTitle("Map ("+Caster.toClassName(o)+")"); 255 256 while(it.hasNext()) { 257 Object next=it.next(); 258 table.appendRow(1,toDumpData(next,pageContext,maxlevel,props),toDumpData(map.get(next),pageContext,maxlevel,props)); 259 } 260 return setId(id,table); 261 } 262 263 // List 264 if(o instanceof List) { 265 List list=(List) o; 266 ListIterator it=list.listIterator(); 267 268 DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000"); 269 table.setTitle("Array (List)"); 270 if ( list.size() > top ) 271 table.setComment("Rows: " + list.size() + " (showing top " + top + ")"); 272 273 int i = 0; 274 while(it.hasNext() && i++ < top) { 275 table.appendRow(1,new SimpleDumpData(it.nextIndex()+1),toDumpData(it.next(),pageContext,maxlevel,props)); 276 } 277 return setId(id,table); 278 } 279 280 // Set 281 if(o instanceof Set) { 282 Set set=(Set) o; 283 Iterator it = set.iterator(); 284 285 DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000"); 286 table.setTitle("Set ("+set.getClass().getName()+")"); 287 288 int i = 0; 289 while(it.hasNext() && i++ < top) { 290 table.appendRow(1,toDumpData(it.next(),pageContext,maxlevel,props)); 291 } 292 return setId(id,table); 293 } 294 295 // Resultset 296 if(o instanceof ResultSet) { 297 try { 298 DumpData dd = new QueryImpl((ResultSet)o,"query",pageContext.getTimeZone()).toDumpData(pageContext,maxlevel,props); 299 if(dd instanceof DumpTable) 300 ((DumpTable)dd).setTitle(Caster.toClassName(o)); 301 return setId(id,dd); 302 } 303 catch (PageException e) { 304 305 } 306 } 307 // Enumeration 308 if(o instanceof Enumeration) { 309 Enumeration e=(Enumeration)o; 310 311 DumpTable table = new DumpTable("enumeration","#ff9900","#ffcc00","#000000"); 312 table.setTitle("Enumeration"); 313 314 int i = 0; 315 while(e.hasMoreElements() && i++ < top) { 316 table.appendRow(0,toDumpData(e.nextElement(),pageContext,maxlevel,props)); 317 } 318 return setId(id,table); 319 } 320 // Object[] 321 if(Decision.isNativeArray(o)) { 322 Array arr; 323 try { 324 arr = Caster.toArray(o); 325 DumpTable htmlBox = new DumpTable("array","#ff9900","#ffcc00","#000000"); 326 htmlBox.setTitle("Native Array ("+Caster.toClassName(o)+")"); 327 328 int length=arr.size(); 329 330 for(int i=1;i<=length;i++) { 331 Object ox=null; 332 try { 333 ox = arr.getE(i); 334 } catch (Exception e) {} 335 htmlBox.appendRow(1,new SimpleDumpData(i),toDumpData(ox,pageContext,maxlevel,props)); 336 } 337 return setId(id,htmlBox); 338 } 339 catch (PageException e) { 340 return setId(id,new SimpleDumpData("")); 341 } 342 } 343 // Node 344 if(o instanceof Node) { 345 return setId(id,XMLCaster.toDumpData((Node)o, pageContext,maxlevel,props)); 346 } 347 // ObjectWrap 348 if(o instanceof ObjectWrap) { 349 maxlevel++; 350 return setId(id,toDumpData(((ObjectWrap)o).getEmbededObject(null), pageContext,maxlevel,props)); 351 } 352 // NodeList 353 if(o instanceof NodeList) { 354 NodeList list=(NodeList)o; 355 int len=list.getLength(); 356 DumpTable table = new DumpTable("xml","#cc9999","#ffffff","#000000"); 357 for(int i=0;i<len;i++) { 358 table.appendRow(1,new SimpleDumpData(i),toDumpData(list.item(i),pageContext,maxlevel,props)); 359 } 360 return setId(id,table); 361 362 } 363 // AttributeMap 364 if(o instanceof NamedNodeMap) { 365 NamedNodeMap attr = (NamedNodeMap)o; 366 int len = attr.getLength(); 367 DumpTable dt = new DumpTable("array","#ff9900","#ffcc00","#000000"); 368 dt.setTitle("NamedNodeMap ("+Caster.toClassName(o)+")"); 369 370 for(int i=0;i<len;i++) { 371 dt.appendRow(1,new SimpleDumpData(i),toDumpData(attr.item(i),pageContext,maxlevel,props)); 372 } 373 return setId(id,dt); 374 } 375 // HttpSession 376 if(o instanceof HttpSession) { 377 HttpSession hs = (HttpSession)o; 378 Enumeration e = hs.getAttributeNames(); 379 380 DumpTable htmlBox = new DumpTable("httpsession","#9999ff","#ccccff","#000000"); 381 htmlBox.setTitle("HttpSession"); 382 while(e.hasMoreElements()) { 383 String key=e.nextElement().toString(); 384 htmlBox.appendRow(1,new SimpleDumpData(key),toDumpData(hs.getAttribute(key), pageContext,maxlevel,props)); 385 } 386 return setId(id,htmlBox); 387 } 388 389 if(o instanceof Pojo) { 390 DumpTable table = new DumpTable(o.getClass().getName(),"#ff99cc","#ffccff","#000000"); 391 392 Class clazz=o.getClass(); 393 if(o instanceof Class) clazz=(Class) o; 394 String fullClassName=clazz.getName(); 395 int pos=fullClassName.lastIndexOf('.'); 396 String className=pos==-1?fullClassName:fullClassName.substring(pos+1); 397 398 table.setTitle("Java Bean - "+className +" ("+fullClassName+")"); 399 table.appendRow(3, 400 new SimpleDumpData("Property Name"), 401 new SimpleDumpData("Value") 402 ); 403 404 // collect the properties 405 Method[] methods=clazz.getMethods(); 406 String propName; 407 Object value; 408 String exName=null; 409 String exValue=null; 410 for(int i=0;i<methods.length;i++) { 411 Method method = methods[i]; 412 if(Object.class==method.getDeclaringClass()) continue; 413 propName=method.getName(); 414 if(propName.startsWith("get") && method.getParameterTypes().length==0) { 415 propName=propName.substring(3); 416 value=null; 417 try { 418 value=method.invoke(o, new Object[0]); 419 if(exName==null && value instanceof String && ((String)value).length()<20) { 420 exName=propName; 421 exValue=value.toString(); 422 } 423 } 424 catch (Throwable t) { 425 ExceptionUtil.rethrowIfNecessary(t); 426 value="not able to retrieve the data:"+t.getMessage(); 427 } 428 table.appendRow(0, 429 new SimpleDumpData(propName), 430 toDumpData(value, pageContext, maxlevel, props) 431 ); 432 } 433 } 434 435 if(exName==null) { 436 exName="LastName"; 437 exValue="Sorglos"; 438 } 439 440 table.setComment("JavaBeans are reusable software components for Java." + 441 "\nThey are classes that encapsulate many objects into a single object (the bean)." + 442 "\nThey allow access to properties using getter and setter methods or directly." ); 443 444 /*"\n\nExample:\n" + 445 " x=myBean.get"+exName+"(); // read a property with a getter method\n" + 446 " x=myBean."+exName+"; // read a property directly\n" + 447 " myBean.set"+exName+"(\""+exValue+"\"); // write a property with a setter method\n" + 448 " myBean."+exName+"=\""+exValue+"\"; // write a property directly");*/ 449 450 451 452 453 return setId(id,table); 454 455 456 457 458 } 459 460 // reflect 461 //else { 462 DumpTable table = new DumpTable(o.getClass().getName(),"#cc9999","#ffcccc","#000000"); 463 464 Class clazz=o.getClass(); 465 if(o instanceof Class) clazz=(Class) o; 466 String fullClassName=clazz.getName(); 467 int pos=fullClassName.lastIndexOf('.'); 468 String className=pos==-1?fullClassName:fullClassName.substring(pos+1); 469 470 table.setTitle(className); 471 table.appendRow(1,new SimpleDumpData("class"),new SimpleDumpData(fullClassName)); 472 473 // Fields 474 Field[] fields=clazz.getFields(); 475 DumpTable fieldDump = new DumpTable("#cc9999","#ffcccc","#000000"); 476 fieldDump.appendRow(7,new SimpleDumpData("name"),new SimpleDumpData("pattern"),new SimpleDumpData("value")); 477 for(int i=0;i<fields.length;i++) { 478 Field field = fields[i]; 479 DumpData value; 480 try {//print.out(o+":"+maxlevel); 481 value=new SimpleDumpData(Caster.toString(field.get(o), "")); 482 } 483 catch (Exception e) { 484 value=new SimpleDumpData(""); 485 } 486 fieldDump.appendRow(0,new SimpleDumpData(field.getName()),new SimpleDumpData(field.toString()),value); 487 } 488 if(fields.length>0)table.appendRow(1,new SimpleDumpData("fields"),fieldDump); 489 490 // Methods 491 StringBuilder objMethods=new StringBuilder(); 492 Method[] methods=clazz.getMethods(); 493 DumpTable methDump = new DumpTable("#cc9999","#ffcccc","#000000"); 494 methDump.appendRow(7,new SimpleDumpData("return"),new SimpleDumpData("interface"),new SimpleDumpData("exceptions")); 495 for(int i=0;i<methods.length;i++) { 496 Method method = methods[i]; 497 498 if(Object.class==method.getDeclaringClass()) { 499 if(objMethods.length()>0)objMethods.append(", "); 500 objMethods.append(method.getName()); 501 continue; 502 } 503 504 // exceptions 505 StringBuilder sbExp=new StringBuilder(); 506 Class[] exceptions = method.getExceptionTypes(); 507 for(int p=0;p<exceptions.length;p++){ 508 if(p>0)sbExp.append("\n"); 509 sbExp.append(Caster.toClassName(exceptions[p])); 510 } 511 512 // parameters 513 StringBuilder sbParams=new StringBuilder(method.getName()); 514 sbParams.append('('); 515 Class[] parameters = method.getParameterTypes(); 516 for(int p=0;p<parameters.length;p++){ 517 if(p>0)sbParams.append(", "); 518 sbParams.append(Caster.toClassName(parameters[p])); 519 } 520 sbParams.append(')'); 521 522 methDump.appendRow(0, 523 new SimpleDumpData(Caster.toClassName(method.getReturnType())), 524 525 new SimpleDumpData(sbParams.toString()), 526 new SimpleDumpData(sbExp.toString()) 527 ); 528 } 529 if(methods.length>0)table.appendRow(1,new SimpleDumpData("methods"),methDump); 530 531 DumpTable inherited = new DumpTable("#cc9999","#ffcccc","#000000"); 532 inherited.appendRow(7,new SimpleDumpData("Methods inherited from java.lang.Object")); 533 inherited.appendRow(0,new SimpleDumpData(objMethods.toString())); 534 table.appendRow(1,new SimpleDumpData(""),inherited); 535 return setId(id,table); 536 //} 537 } 538 finally{ 539 ThreadLocalDump.remove(o); 540 } 541 } 542 543 private static DumpData setId(String id, DumpData data) { 544 if(data instanceof DumpTable) { 545 ((DumpTable)data).setId(id); 546 } 547 // TODO Auto-generated method stub 548 return data; 549 } 550 551 public static boolean keyValid(DumpProperties props,int level, String key) { 552 if(props.getMaxlevel()-level>1) return true; 553 554 // show 555 Set set = props.getShow(); 556 if(set!=null && !set.contains(StringUtil.toLowerCase(key))) 557 return false; 558 559 // hide 560 set = props.getHide(); 561 if(set!=null && set.contains(StringUtil.toLowerCase(key))) 562 return false; 563 564 return true; 565 } 566 567 public static boolean keyValid(DumpProperties props,int level, Collection.Key key) { 568 if(props.getMaxlevel()-level>1) return true; 569 570 // show 571 Set set = props.getShow(); 572 if(set!=null && !set.contains(key.getLowerString())) 573 return false; 574 575 // hide 576 set = props.getHide(); 577 if(set!=null && set.contains(key.getLowerString())) 578 return false; 579 580 return true; 581 } 582 583 584 585 586 public static DumpProperties toDumpProperties() { 587 return DumpProperties.DEFAULT; 588 } 589}