package railo.runtime.thread; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.lang.Thread.State; import java.util.Iterator; import railo.runtime.PageContext; import railo.runtime.config.NullSupportHelper; import railo.runtime.dump.DumpData; import railo.runtime.dump.DumpProperties; import railo.runtime.dump.DumpTable; import railo.runtime.dump.DumpUtil; import railo.runtime.dump.SimpleDumpData; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.exp.ApplicationException; import railo.runtime.exp.PageException; import railo.runtime.op.Duplicator; import railo.runtime.op.ThreadLocalDuplication; import railo.runtime.tag.Http3; import railo.runtime.type.Collection; import railo.runtime.type.KeyImpl; import railo.runtime.type.StructImpl; import railo.runtime.type.dt.DateTime; import railo.runtime.type.dt.DateTimeImpl; import railo.runtime.type.it.EntryIterator; import railo.runtime.type.it.StringIterator; import railo.runtime.type.it.ValueIterator; import railo.runtime.type.util.CollectionUtil; import railo.runtime.type.util.KeyConstants; import railo.runtime.type.util.StructSupport; public class ThreadsImpl extends StructSupport implements railo.runtime.type.scope.Threads { private static final Key KEY_ERROR = KeyImpl.intern("ERROR"); private static final Key KEY_ELAPSEDTIME = KeyImpl.intern("ELAPSEDTIME"); private static final Key KEY_OUTPUT = KeyImpl.intern("OUTPUT"); private static final Key KEY_PRIORITY = KeyImpl.intern("PRIORITY"); private static final Key KEY_STARTTIME = KeyImpl.intern("STARTTIME"); private static final Key KEY_STATUS = KeyImpl.intern("STATUS"); private static final Key KEY_STACKTRACE = KeyImpl.intern("STACKTRACE"); private static final Key[] DEFAULT_KEYS=new Key[]{ KEY_ELAPSEDTIME, KeyConstants._NAME, KEY_OUTPUT, KEY_PRIORITY, KEY_STARTTIME, KEY_STATUS, KEY_STACKTRACE }; private ChildThreadImpl ct; public ThreadsImpl(ChildThreadImpl ct) { this.ct=ct; } public ChildThread getChildThread() { return ct; } @Override public boolean containsKey(Key key) { return get(key,null)!=null; } ///////////////////////////////////////////////////////////// public int getType() { return -1; } @Override public String getTypeAsString() { return "thread"; } @Override public void initialize(PageContext pc) { } @Override public boolean isInitalized() { return true; } @Override public void release() {} @Override public void release(PageContext pc) {} @Override public void clear() { ct.content.clear(); } @Override public Collection duplicate(boolean deepCopy) { StructImpl sct=new StructImpl(); ThreadLocalDuplication.set(this, sct); try{ Key[] keys = keys(); Object value; for(int i=0;i<keys.length;i++) { value=get(keys[i],null); sct.setEL(keys[i],deepCopy?Duplicator.duplicate(value, deepCopy):value); } } finally { //ThreadLocalDuplication.remove(this); removed "remove" to catch sisters and brothers } return sct; } private Object getMeta(Key key, Object defaultValue) { if(KEY_ELAPSEDTIME.equalsIgnoreCase(key)) return new Double(System.currentTimeMillis()-ct.getStartTime()); if(KeyConstants._NAME.equalsIgnoreCase(key)) return ct.getTagName(); if(KEY_OUTPUT.equalsIgnoreCase(key)) return getOutput(); if(KEY_PRIORITY.equalsIgnoreCase(key)) return ThreadUtil.toStringPriority(ct.getPriority()); if(KEY_STARTTIME.equalsIgnoreCase(key)) return new DateTimeImpl(ct.getStartTime(),true); if(KEY_STATUS.equalsIgnoreCase(key)) return getState(); if(KEY_ERROR.equalsIgnoreCase(key)) return ct.catchBlock; if(KEY_STACKTRACE.equalsIgnoreCase(key)) return getStackTrace(); return defaultValue; } private String getStackTrace() { StringBuilder sb=new StringBuilder(); try{ StackTraceElement[] trace = ct.getStackTrace(); if(trace!=null)for (int i=0; i < trace.length; i++) { sb.append("\tat "); sb.append(trace[i]); sb.append("\n"); } } catch(Throwable t){} return sb.toString(); } private Object getOutput() { if(ct.output==null)return ""; InputStream is = new ByteArrayInputStream(ct.output.toByteArray()); return Http3.getOutput(is, ct.contentType, ct.contentEncoding,true); } private Object getState() { /* The current status of the thread; one of the following values: */ try { State state = ct.getState(); if(State.NEW.equals(state)) return "NOT_STARTED"; if(State.WAITING.equals(state)) return "WAITING"; if(State.TERMINATED.equals(state)) { if(ct.terminated || ct.catchBlock!=null)return "TERMINATED"; return "COMPLETED"; } return "RUNNING"; } // java 1.4 execution catch(Throwable t) { if(ct.terminated || ct.catchBlock!=null)return "TERMINATED"; if(ct.completed)return "COMPLETED"; if(!ct.isAlive())return "WAITING"; return "RUNNING"; } } @Override public Object get(Key key, Object defaultValue) { Object meta = getMeta(key,NullSupportHelper.NULL()); if(meta!=NullSupportHelper.NULL()) return meta; return ct.content.get(key,defaultValue); } @Override public Object get(Key key) throws PageException { Object meta = getMeta(key,NullSupportHelper.NULL()); if(meta!=NullSupportHelper.NULL()) return meta; return ct.content.get(key); } @Override public Key[] keys() { Key[] skeys = CollectionUtil.keys(ct.content); if(skeys.length==0 && ct.catchBlock==null) return DEFAULT_KEYS; Key[] rtn=new Key[skeys.length+(ct.catchBlock!=null?1:0)+DEFAULT_KEYS.length]; int index=0; for(;index<DEFAULT_KEYS.length;index++) { rtn[index]=DEFAULT_KEYS[index]; } if(ct.catchBlock!=null) { rtn[index]=KEY_ERROR; index++; } for(int i=0;i<skeys.length;i++) { rtn[index++]=skeys[i]; } return rtn; } @Override public Object remove(Key key) throws PageException { if(isReadonly())throw errorOutside(); Object meta = getMeta(key,NullSupportHelper.NULL()); if(meta!=NullSupportHelper.NULL()) throw errorMeta(key); return ct.content.remove(key); } @Override public Object removeEL(Key key) { if(isReadonly())return null; return ct.content.removeEL(key); } @Override public Object set(Key key, Object value) throws PageException { if(isReadonly())throw errorOutside(); Object meta = getMeta(key,NullSupportHelper.NULL()); if(meta!=NullSupportHelper.NULL()) throw errorMeta(key); return ct.content.set(key, value); } @Override public Object setEL(Key key, Object value) { if(isReadonly()) return null; Object meta = getMeta(key,NullSupportHelper.NULL()); if(meta!=NullSupportHelper.NULL()) return null; return ct.content.setEL(key, value); } @Override public int size() { return ct.content.size()+DEFAULT_KEYS.length+(ct.catchBlock==null?0:1); } @Override public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) { Key[] keys = keys(); DumpTable table = new DumpTable("struct","#9999ff","#ccccff","#000000"); table.setTitle("Struct"); maxlevel--; int maxkeys=dp.getMaxKeys(); int index=0; for(int i=0;i<keys.length;i++) { Key key=keys[i]; if(maxkeys<=index++)break; if(DumpUtil.keyValid(dp,maxlevel, key)) table.appendRow(1,new SimpleDumpData(key.getString()),DumpUtil.toDumpData(get(key,null), pageContext,maxlevel,dp)); } return table; } @Override public Iterator<Collection.Key> keyIterator() { return new railo.runtime.type.it.KeyIterator(keys()); } @Override public Iterator<String> keysAsStringIterator() { return new StringIterator(keys()); } @Override public Iterator<Entry<Key, Object>> entryIterator() { return new EntryIterator(this,keys()); } @Override public Iterator<Object> valueIterator() { return new ValueIterator(this,keys()); } @Override public boolean castToBooleanValue() throws PageException { return ct.content.castToBooleanValue(); } @Override public Boolean castToBoolean(Boolean defaultValue) { return ct.content.castToBoolean(defaultValue); } @Override public DateTime castToDateTime() throws PageException { return ct.content.castToDateTime(); } @Override public DateTime castToDateTime(DateTime defaultValue) { return ct.content.castToDateTime(defaultValue); } @Override public double castToDoubleValue() throws PageException { return ct.content.castToDoubleValue(); } @Override public double castToDoubleValue(double defaultValue) { return ct.content.castToDoubleValue(defaultValue); } @Override public String castToString() throws PageException { return ct.content.castToString(); } @Override public String castToString(String defaultValue) { return ct.content.castToString(defaultValue); } @Override public int compareTo(String str) throws PageException { return ct.content.compareTo(str); } @Override public int compareTo(boolean b) throws PageException { return ct.content.compareTo(b); } @Override public int compareTo(double d) throws PageException { return ct.content.compareTo(d); } @Override public int compareTo(DateTime dt) throws PageException { return ct.content.compareTo(dt); } private boolean isReadonly() { PageContext pc = ThreadLocalPageContext.get(); if(pc==null) return true; return pc.getThread()!=ct; } private ApplicationException errorOutside() { return new ApplicationException("the thread scope cannot be modified from outside the owner thread"); } private ApplicationException errorMeta(Key key) { return new ApplicationException("the metadata "+key.getString()+" of the thread scope are readonly"); } }