package railo.runtime.tag;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map.Entry;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.Tag;
import railo.commons.lang.StringUtil;
import railo.runtime.Component;
import railo.runtime.Mapping;
import railo.runtime.PageContext;
import railo.runtime.PageContextImpl;
import railo.runtime.PageSource;
import railo.runtime.component.ComponentLoader;
import railo.runtime.component.Member;
import railo.runtime.config.ConfigWebImpl;
import railo.runtime.customtag.CustomTagUtil;
import railo.runtime.customtag.InitFile;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.CasterException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.exp.PageRuntimeException;
import railo.runtime.exp.PageServletException;
import railo.runtime.ext.tag.AppendixTag;
import railo.runtime.ext.tag.BodyTagTryCatchFinallyImpl;
import railo.runtime.ext.tag.DynamicAttributes;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.scope.Caller;
import railo.runtime.type.scope.CallerImpl;
import railo.runtime.type.scope.Undefined;
import railo.runtime.type.scope.Variables;
import railo.runtime.type.scope.VariablesImpl;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.ComponentUtil;
import railo.runtime.type.util.KeyConstants;
import railo.runtime.type.util.ListUtil;
import railo.runtime.type.util.Type;
import railo.runtime.util.QueryStack;
import railo.runtime.util.QueryStackImpl;
import railo.transformer.library.tag.TagLibTag;
import railo.transformer.library.tag.TagLibTagAttr;
/**
* Creates a CFML Custom Tag
**/
public class CFTag extends BodyTagTryCatchFinallyImpl implements DynamicAttributes,AppendixTag {
private static Collection.Key GENERATED_CONTENT=KeyImpl.intern("GENERATEDCONTENT");
private static Collection.Key EXECUTION_MODE=KeyImpl.intern("EXECUTIONMODE");
private static Collection.Key EXECUTE_BODY=KeyImpl.intern("EXECUTEBODY");
private static Collection.Key PARENT=KeyImpl.intern("PARENT");
private static Collection.Key CFCATCH=KeyConstants._CFCATCH;
private static Collection.Key SOURCE=KeyImpl.intern("SOURCE");
private static final Collection.Key ON_ERROR = KeyImpl.intern("onError");
private static final Collection.Key ON_FINALLY = KeyImpl.intern("onFinally");
private static final Collection.Key ON_START_TAG = KeyImpl.intern("onStartTag");
private static final Collection.Key ON_END_TAG = KeyImpl.intern("onEndTag");
private static final Collection.Key ATTRIBUTE_TYPE = KeyImpl.intern("attributetype");
private static final Collection.Key RT_EXPR_VALUE = KeyImpl.intern("rtexprvalue");
private static final String MARKER = "2w12801";
/**
* Field <code>attributesScope</code>
*/
// new scopes
protected StructImpl attributesScope;
private Caller callerScope;
private StructImpl thistagScope;
private Variables ctVariablesScope;
private boolean hasBody;
/**
* Field <code>filename</code>
*/
//protected String filename;
/**
* Field <code>source</code>
*/
protected InitFile source;
private String appendix;
//private boolean doCustomTagDeepSearch;
private Component cfc;
private boolean isEndTag;
/**
* constructor for the tag class
**/
public CFTag() {
attributesScope = new StructImpl();
callerScope = new CallerImpl();
//thistagScope = new StructImpl();
}
@Override
public void setDynamicAttribute(String uri, String name, Object value) {
TagUtil.setDynamicAttribute(attributesScope,KeyImpl.init(name),value,TagUtil.ORIGINAL_CASE);
}
@Override
public void setDynamicAttribute(String uri, Collection.Key name, Object value) {
TagUtil.setDynamicAttribute(attributesScope,name,value,TagUtil.ORIGINAL_CASE);
}
@Override
public void release() {
super.release();
hasBody=false;
//filename=null;
attributesScope=new StructImpl();//.clear();
callerScope = new CallerImpl();
if(thistagScope!=null)thistagScope=null;
if(ctVariablesScope!=null)ctVariablesScope=null;
isEndTag=false;
//cfc=null;
source=null;
}
/**
* sets the appendix of the class
* @param appendix
*/
public void setAppendix(String appendix) {
this.appendix=appendix;
//filename = appendix+'.'+pageContext.getConfig().getCFMLExtension();
}
@Override
public int doStartTag() throws PageException {
PageContextImpl pci=(PageContextImpl) pageContext;
boolean old=pci.useSpecialMappings(true);
try{
initFile();
callerScope.initialize(pageContext);
if(source.isCFC())return cfcStartTag();
return cfmlStartTag();
}
finally{
pci.useSpecialMappings(old);
}
}
@Override
public int doEndTag() {
PageContextImpl pci=(PageContextImpl) pageContext;
boolean old=pci.useSpecialMappings(true);
try{
if(source.isCFC())_doCFCFinally();
return EVAL_PAGE;
}
finally{
pci.useSpecialMappings(old);
}
}
@Override
public void doInitBody() {
}
@Override
public int doAfterBody() throws PageException {
if(source.isCFC())return cfcEndTag();
return cfmlEndTag();
}
@Override
public void doCatch(Throwable t) throws Throwable {
if(source.isCFC()){
String source=isEndTag?"end":"body";
isEndTag=false;
_doCFCCatch(t,source);
}
else super.doCatch(t);
}
void initFile() throws PageException {
source=initFile(pageContext);
}
public InitFile initFile(PageContext pageContext) throws PageException {
return CustomTagUtil.loadInitFile(pageContext, appendix);
}
private int cfmlStartTag() throws PageException {
callerScope.initialize(pageContext);
// thistag
if(thistagScope==null)thistagScope=new StructImpl(StructImpl.TYPE_LINKED);
thistagScope.set(GENERATED_CONTENT,"");
thistagScope.set(EXECUTION_MODE,"start");
thistagScope.set(EXECUTE_BODY,Boolean.TRUE);
thistagScope.set(KeyConstants._HASENDTAG,Caster.toBoolean(hasBody));
ctVariablesScope=new VariablesImpl();
ctVariablesScope.setEL(KeyConstants._ATTRIBUTES,attributesScope);
ctVariablesScope.setEL(KeyConstants._CALLER,callerScope);
ctVariablesScope.setEL(KeyConstants._THISTAG,thistagScope);
// include
doInclude();
return Caster.toBooleanValue(thistagScope.get(EXECUTE_BODY))?EVAL_BODY_BUFFERED:SKIP_BODY;
}
private int cfmlEndTag() throws PageException {
// thistag
String genConBefore = bodyContent.getString();
thistagScope.set(GENERATED_CONTENT,genConBefore);
thistagScope.set(EXECUTION_MODE,"end");
thistagScope.set(EXECUTE_BODY,Boolean.FALSE);
writeEL(bodyContent, MARKER);
// include
try{
doInclude();
}
catch(Throwable t){
writeOut(genConBefore);
throw Caster.toPageException(t);
}
writeOut(genConBefore);
return Caster.toBooleanValue(thistagScope.get(EXECUTE_BODY))?EVAL_BODY_BUFFERED:SKIP_BODY;
}
private void writeOut(String genConBefore) throws PageException {
String output = bodyContent.getString();
bodyContent.clearBody();
String genConAfter = Caster.toString(thistagScope.get(GENERATED_CONTENT));
if(genConBefore!=genConAfter){
if(output.startsWith(genConBefore+MARKER)){
output=output.substring((genConBefore+MARKER).length());
}
output=genConAfter+output;
}
else {
if(output.startsWith(genConBefore+MARKER)){
output=output.substring((genConBefore+MARKER).length());
output=genConBefore+output;
}
}
writeEL(bodyContent.getEnclosingWriter(),output);
}
private void writeEL(JspWriter writer, String str) throws PageException {
try {
writer.write(str);
} catch (IOException e) {
throw Caster.toPageException(e);
}
}
void doInclude() throws PageException {
Variables var=pageContext.variablesScope();
pageContext.setVariablesScope(ctVariablesScope);
QueryStack cs=null;
Undefined undefined=pageContext.undefinedScope();
int oldMode=undefined.setMode(Undefined.MODE_NO_LOCAL_AND_ARGUMENTS);
if(oldMode!=Undefined.MODE_NO_LOCAL_AND_ARGUMENTS)
callerScope.setScope(var,pageContext.localScope(),pageContext.argumentsScope(),true);
else
callerScope.setScope(var,null,null,false);
if(pageContext.getConfig().allowImplicidQueryCall()) {
cs=undefined.getQueryStack();
undefined.setQueryStack(new QueryStackImpl());
}
try {
pageContext.doInclude(new PageSource[]{source.getPageSource()},false);
}
catch (Throwable t) {
throw Caster.toPageException(t);
}
finally {
undefined.setMode(oldMode);
//varScopeData=variablesScope.getMap();
pageContext.setVariablesScope(var);
if(pageContext.getConfig().allowImplicidQueryCall()) {
undefined.setQueryStack(cs);
}
}
}
// CFC
private int cfcStartTag() throws PageException {
callerScope.initialize(pageContext);
try {
cfc = ComponentLoader.loadComponent(pageContext,null,source.getPageSource(), source.getFilename().substring(0,source.getFilename().length()-(pageContext.getConfig().getCFCExtension().length()+1)), false,true);
}
catch (PageException e) {
Mapping m = source.getPageSource().getMapping();
ConfigWebImpl c=(ConfigWebImpl) pageContext.getConfig();
if(m==c.getTagMapping()) m=c.getServerTagMapping();
else m=null;
// is te page source from a tag mapping, so perhaps it was moved from server to web context
if(m!=null){
PageSource ps = m.getPageSource(source.getFilename());
try {
cfc = ComponentLoader.loadComponent(pageContext,null,ps, source.getFilename().substring(0,source.getFilename().length()-(pageContext.getConfig().getCFCExtension().length()+1)), false,true);
}
catch (PageException e1) {
throw e;
}
}
}
validateAttributes(cfc,attributesScope,StringUtil.ucFirst(ListUtil.last(source.getPageSource().getComponentName(),'.')));
boolean exeBody = false;
try {
Object rtn=Boolean.TRUE;
if(cfc.contains(pageContext, KeyConstants._init)){
Tag parent=getParent();
while(parent!=null && !(parent instanceof CFTag && ((CFTag)parent).isCFCBasedCustomTag())) {
parent=parent.getParent();
}
Struct args=new StructImpl(StructImpl.TYPE_LINKED);
args.set(KeyConstants._HASENDTAG, Caster.toBoolean(hasBody));
if(parent instanceof CFTag) {
args.set(PARENT, ((CFTag)parent).getComponent());
}
rtn=cfc.callWithNamedValues(pageContext, KeyConstants._init, args);
}
if(cfc.contains(pageContext, ON_START_TAG)){
Struct args=new StructImpl();
args.set(KeyConstants._ATTRIBUTES, attributesScope);
setCaller(pageContext,args);
rtn=cfc.callWithNamedValues(pageContext, ON_START_TAG, args);
}
exeBody=Caster.toBooleanValue(rtn,true);
}
catch(Throwable t){
_doCFCCatch(t,"start");
}
return exeBody?EVAL_BODY_BUFFERED:SKIP_BODY;
}
private void setCaller(PageContext pageContext, Struct args) throws PageException {
callerScope.initialize(pageContext);
boolean checkAgs=pageContext.undefinedScope().getCheckArguments();
if(checkAgs)
callerScope.setScope(pageContext.variablesScope(),pageContext.localScope(),pageContext.argumentsScope(),true);
else
callerScope.setScope(pageContext.variablesScope(),null,null,false);
args.set(KeyConstants._CALLER, callerScope);
//args.set(KeyConstants._CALLER, Duplicator.duplicate(pageContext.undefinedScope(),false));
}
private static void validateAttributes(Component cfc,StructImpl attributesScope,String tagName) throws ApplicationException, ExpressionException {
TagLibTag tag=getAttributeRequirments(cfc,false);
if(tag==null) return;
if(tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_FIXED || tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_MIXED){
Iterator<Entry<String, TagLibTagAttr>> it = tag.getAttributes().entrySet().iterator();
int count=0;
Collection.Key key;
TagLibTagAttr attr;
Object value;
Entry<String, TagLibTagAttr> entry;
// check existing attributes
while(it.hasNext()){
entry = it.next();
count++;
key=KeyImpl.toKey(entry.getKey(),null);
attr=entry.getValue();
value=attributesScope.get(key,null);
if(value==null){
if(attr.getDefaultValue()!=null){
value=attr.getDefaultValue();
attributesScope.setEL(key, value);
}
else if(attr.isRequired())
throw new ApplicationException("attribute ["+key.getString()+"] is required for tag ["+tagName+"]");
}
if(value!=null) {
if(!Decision.isCastableTo(attr.getType(),value,true,true,-1))
throw new CasterException(createMessage(attr.getType(), value));
}
}
// check if there are attributes not supported
if(tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_FIXED && count<attributesScope.size()){
Collection.Key[] keys = attributesScope.keys();
for(int i=0;i<keys.length;i++){
if(tag.getAttribute(keys[i].getLowerString())==null)
throw new ApplicationException("attribute ["+keys[i].getString()+"] is not supported for tag ["+tagName+"]");
}
//Attribute susi is not allowed for tag cfmail
}
}
}
private static String createMessage(String type, Object value) {
if(value instanceof String) return "can't cast String ["+value+"] to a value of type ["+type+"]";
else if(value!=null) return "can't cast Object type ["+Type.getName(value)+"] to a value of type ["+type+"]";
else return "can't cast Null value to value of type ["+type+"]";
}
private static TagLibTag getAttributeRequirments(Component cfc, boolean runtime) throws ExpressionException {
Struct meta=null;
//try {
//meta = Caster.toStruct(cfc.get(Component.ACCESS_PRIVATE, METADATA),null,false);
Member mem = ComponentUtil.toComponentAccess(cfc).getMember(Component.ACCESS_PRIVATE, KeyConstants._metadata,true,false);
if(mem!=null)meta = Caster.toStruct(mem.getValue(),null,false);
//}catch (PageException e) {e.printStackTrace();}
if(meta==null) return null;
TagLibTag tag=new TagLibTag(null);
// TAG
// type
String type=Caster.toString(meta.get(ATTRIBUTE_TYPE,"dynamic"),"dynamic");
if("fixed".equalsIgnoreCase(type))tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_FIXED);
//else if("mixed".equalsIgnoreCase(type))tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_MIXED);
//else if("noname".equalsIgnoreCase(type))tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_NONAME);
else tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_DYNAMIC);
if(!runtime){
// hint
String hint=Caster.toString(meta.get(KeyConstants._hint,null),null);
if(!StringUtil.isEmpty(hint))tag.setDescription(hint);
}
// ATTRIBUTES
Struct attributes=Caster.toStruct(meta.get(KeyConstants._ATTRIBUTES,null),null,false);
if(attributes!=null) {
Iterator<Entry<Key, Object>> it = attributes.entryIterator();
//Iterator it = attributes.entrySet().iterator();
Entry<Key, Object> entry;
TagLibTagAttr attr;
Struct sct;
String name;
Object defaultValue;
while(it.hasNext()){
entry=it.next();
name=Caster.toString(entry.getKey(),null);
if(StringUtil.isEmpty(name)) continue;
attr=new TagLibTagAttr(tag);
attr.setName(name);
sct=Caster.toStruct(entry.getValue(),null,false);
if(sct!=null){
attr.setRequired(Caster.toBooleanValue(sct.get(KeyConstants._required,Boolean.FALSE),false));
attr.setType(Caster.toString(sct.get(KeyConstants._type,"any"),"any"));
defaultValue= sct.get(KeyConstants._default,null);
if(defaultValue!=null)attr.setDefaultValue(defaultValue);
if(!runtime){
attr.setDescription(Caster.toString(sct.get(KeyConstants._hint,null),null));
attr.setRtexpr(Caster.toBooleanValue(sct.get(RT_EXPR_VALUE,Boolean.TRUE),true));
}
}
tag.setAttribute(attr);
}
}
return tag;
}
private int cfcEndTag() throws PageException {
boolean exeAgain = false;
try{
String output=null;
Object rtn=Boolean.FALSE;
if(cfc.contains(pageContext, ON_END_TAG)){
try {
output=bodyContent.getString();
bodyContent.clearBody();
//rtn=cfc.call(pageContext, ON_END_TAG, new Object[]{attributesScope,pageContext.variablesScope(),output});
Struct args=new StructImpl(StructImpl.TYPE_LINKED);
args.set(KeyConstants._ATTRIBUTES, attributesScope);
setCaller(pageContext, args);
args.set(GENERATED_CONTENT, output);
rtn=cfc.callWithNamedValues(pageContext, ON_END_TAG, args);
}
finally {
writeEnclosingWriter();
}
}
else writeEnclosingWriter();
exeAgain= Caster.toBooleanValue(rtn,false);
}
catch(Throwable t){
isEndTag=true;
throw Caster.toPageException(t);
}
return exeAgain?EVAL_BODY_BUFFERED:SKIP_BODY;
}
public void _doCFCCatch(Throwable t, String source) throws PageException {
writeEnclosingWriter();
// remove PageServletException wrap
if(t instanceof PageServletException) {
PageServletException pse=(PageServletException)t;
t=pse.getPageException();
}
// abort
try {
if(railo.runtime.exp.Abort.isAbort(t)){
if(bodyContent!=null){
bodyContent.writeOut(bodyContent.getEnclosingWriter());
bodyContent.clearBuffer();
}
throw Caster.toPageException(t);
}
}
catch(IOException ioe){
throw Caster.toPageException(ioe);
}
try {
if(cfc.contains(pageContext, ON_ERROR)){
PageException pe = Caster.toPageException(t);
//Object rtn=cfc.call(pageContext, ON_ERROR, new Object[]{pe.getCatchBlock(pageContext),source});
Struct args=new StructImpl(StructImpl.TYPE_LINKED);
args.set(CFCATCH, pe.getCatchBlock(ThreadLocalPageContext.getConfig(pageContext)));
args.set(SOURCE, source);
Object rtn=cfc.callWithNamedValues(pageContext, ON_ERROR, args);
if(Caster.toBooleanValue(rtn,false))
throw t;
}
else throw t;
}
catch(Throwable th) {
writeEnclosingWriter();
_doCFCFinally();
throw Caster.toPageException(th);
}
writeEnclosingWriter();
}
private void _doCFCFinally() {
if(cfc.contains(pageContext, ON_FINALLY)){
try {
cfc.call(pageContext, ON_FINALLY, ArrayUtil.OBJECT_EMPTY);
}
catch (PageException pe) {
throw new PageRuntimeException(pe);
}
finally{
writeEnclosingWriter();
}
}
}
private void writeEnclosingWriter() {
if(bodyContent!=null){
try {
String output = bodyContent.getString();
bodyContent.clearBody();
bodyContent.getEnclosingWriter().write(output);
}
catch (IOException e) {
//throw Caster.toPageException(e);
}
}
}
/**
* sets if tag has a body or not
* @param hasBody
*/
public void hasBody(boolean hasBody) {
this.hasBody=hasBody;
}
/**
* @return Returns the appendix.
*/
public String getAppendix() {
return appendix;
}
/**
* @return return thistag
*/
public Struct getThis() {
if(isCFCBasedCustomTag()){
return cfc;
}
return thistagScope;
}
/**
* @return return thistag
*/
public Struct getCallerScope() {
return callerScope;
}
/**
* @return return thistag
*/
public Struct getAttributesScope() {
return attributesScope;
}
/**
* @return the ctVariablesScope
*/
public Struct getVariablesScope() {
if(isCFCBasedCustomTag()) {
return cfc.getComponentScope();
}
return ctVariablesScope;
}
/**
* @return the cfc
*/
public Component getComponent() {
return cfc;
}
public boolean isCFCBasedCustomTag() {
return getSource().isCFC();
}
private InitFile getSource() {
if(source==null){
try {
source=initFile(pageContext);
} catch (PageException e) {
e.printStackTrace();
}
}
return source;
}
/*class InitFile {
PageSource ps;
String filename;
boolean isCFC;
public InitFile(PageSource ps,String filename,boolean isCFC){
this.ps=ps;
this.filename=filename;
this.isCFC=isCFC;
}
}*/
}