/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.runtime.tag;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.Cookie;
import javax.servlet.jsp.tagext.Tag;
import lucee.commons.io.DevNullOutputStream;
import lucee.commons.lang.ClassException;
import lucee.commons.lang.ClassUtil;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.Pair;
import lucee.commons.lang.StringUtil;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.Component;
import lucee.runtime.ComponentImpl;
import lucee.runtime.ComponentSpecificAccess;
import lucee.runtime.PageContext;
import lucee.runtime.PageContextImpl;
import lucee.runtime.PageSource;
import lucee.runtime.component.ComponentLoader;
import lucee.runtime.config.ConfigWebImpl;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.ApplicationException;
import lucee.runtime.exp.PageException;
import lucee.runtime.ext.function.BIF;
import lucee.runtime.ext.tag.DynamicAttributes;
import lucee.runtime.functions.BIFProxy;
import lucee.runtime.op.Caster;
import lucee.runtime.reflection.Reflector;
import lucee.runtime.reflection.pairs.MethodInstance;
import lucee.runtime.thread.ThreadUtil;
import lucee.runtime.type.Collection;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.KeyImpl;
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.KeyConstants;
import lucee.transformer.library.tag.TagLib;
import lucee.transformer.library.tag.TagLibTag;
import lucee.transformer.library.tag.TagLibTagAttr;
import org.osgi.framework.BundleException;
public class TagUtil {
public static final short ORIGINAL_CASE = 0;
public static final short UPPER_CASE = 1;
public static final short LOWER_CASE = 2;
//private static final String "invalid call of the function ["+tlt.getName()+", you can not mix named on regular arguments]" = "invalid argument for function, only named arguments are allowed like struct(name:\"value\",name2:\"value2\")";
public static void setAttributeCollection(PageContext pc,Tag tag, MissingAttribute[] missingAttrs, Struct _attrs, int attrType) throws PageException {
// check missing tags
Map<Key, Object> att=new HashMap<Key, Object>();
{
Iterator<Entry<Key, Object>> it = _attrs.entryIterator();
Entry<Key, Object> e;
while(it.hasNext()){
e = it.next();
att.put(e.getKey(), e.getValue());
}
}
if(!ArrayUtil.isEmpty(missingAttrs)){
Key k;
Object value;
MissingAttribute miss;
for(int i=0;i<missingAttrs.length;i++) {
miss = missingAttrs[i];
value=att.get(miss.getName());
// check alias
if(value==null && !ArrayUtil.isEmpty(miss.getAlias())) {
String[] alias = miss.getAlias();
for(int y=0;y<alias.length;y++){
value=att.get(k=KeyImpl.init(alias[y]));
if(value!=null) {
att.remove(k);
break;
}
}
}
if(value==null)
throw new ApplicationException("attribute "+missingAttrs[i].getName().getString()+" is required but missing");
//throw new ApplicationException("attribute "+missingAttrs[i].getName().getString()+" is required for tag "+tag.getFullName());
att.put(
missingAttrs[i].getName(),
Caster.castTo(pc, missingAttrs[i].getType(), value, false));
}
}
setAttributes(pc,tag,att,attrType);
}
public static void setAttributes(PageContext pc,Tag tag, Map<Key, Object> att, int attrType) throws PageException {
Iterator<Entry<Key, Object>> it;
Entry<Key, Object> e;
//TagLibTag tlt=null;
if(TagLibTag.ATTRIBUTE_TYPE_DYNAMIC==attrType) {
DynamicAttributes da=(DynamicAttributes) tag;
it = att.entrySet().iterator();
while(it.hasNext()) {
e = it.next();
da.setDynamicAttribute(null, e.getKey(),e.getValue());
}
}
else if(TagLibTag.ATTRIBUTE_TYPE_FIXED==attrType) {
it = att.entrySet().iterator();
while(it.hasNext()) {
e = it.next();
setAttribute(pc,false,true, tag, e.getKey().getLowerString(), e.getValue());
}
}
else if(TagLibTag.ATTRIBUTE_TYPE_MIXED==attrType) {
it = att.entrySet().iterator();
while(it.hasNext()) {
e = it.next();
setAttribute(pc, true,true, tag, e.getKey().getLowerString(), e.getValue());
}
}
}
public static void setAttribute(PageContext pc,Tag tag, String name,Object value) throws PageException {
setAttribute(pc, false, false, tag, name, value);
}
public static void setAttribute(PageContext pc,boolean doDynamic,boolean silently,Tag tag, String name,Object value) throws PageException {
MethodInstance setter = Reflector.getSetter(tag, name.toLowerCase(),value,null);
if(setter!=null) {
try {
setter.invoke(tag);
}
catch (Exception _e) {
if(!(value==null && _e instanceof IllegalArgumentException)) // TODO full null support should allow null, because of that i only suppress in case of an exception
throw Caster.toPageException(_e);
}
}
else if(doDynamic) {
DynamicAttributes da=(DynamicAttributes) tag;
da.setDynamicAttribute(null, name,value);
}
else if(!silently){
throw new ApplicationException("failed to call ["+name+"] on tag "+tag);
}
}
/*private static MethodInstance getSetter(PageContext pc,TagLibTag tlt, Tag tag, String name, Object value) throws PageException {
MethodInstance setter = Reflector.getSetter(tag, name,value,null);
if(setter==null && tlt!=null) {
TagLibTagAttr attr = tlt.getAttribute(name);
if(attr==null)
throw new TemplateException(
"Attribute "+name+" is not allowed for tag "+tlt.getFullName(),
"valid attribute names are ["+tlt.getAttributeNames()+"]");
value=Caster.castTo(pc, attr.getType(), value, false);
setter = Reflector.getSetter(tag, name,value,null);
}
return setter;
}*/
/*private static TagLibTag getTLT(ConfigWeb config, Tag tag) {
TagLib[] tlds = ((ConfigWebImpl)config).getTLDs();
TagLibTag tlt;
for(int i=0;i<tlds.length;i++){
tlt = tlds[i].getTag(tag.getClass());
if(tlt!=null) return tlt;
}
return null;
}*/
public static void setDynamicAttribute(StructImpl attributes,Collection.Key name, Object value, short caseType) {
if(name.equalsIgnoreCase(KeyConstants._attributecollection)) {
if(value instanceof lucee.runtime.type.Collection) {
lucee.runtime.type.Collection coll=(lucee.runtime.type.Collection)value;
Iterator<Entry<Key, Object>> it = coll.entryIterator();
Entry<Key, Object> e;
while(it.hasNext()) {
e = it.next();
if(attributes.get(e.getKey(),null)==null)
attributes.setEL(e.getKey(),e.getValue());
}
return;
}
else if(value instanceof Map) {
Map map=(Map) value;
Iterator it = map.entrySet().iterator();
Map.Entry entry;
Key key;
while(it.hasNext()) {
entry=(Entry) it.next();
key = Caster.toKey(entry.getKey(),null);
if(!attributes.containsKey(key)){
attributes.setEL(key,entry.getValue());
}
}
return;
}
}
if(LOWER_CASE==caseType)name=KeyImpl.init(name.getLowerString());
else if(UPPER_CASE==caseType)name=KeyImpl.init(name.getUpperString());
attributes.setEL(name, value);
}
/**
* load metadata from cfc based custom tags and add the info to the tag
* @param cs
* @param config
*/
public static void addTagMetaData(ConfigWebImpl cw) {
if(true) return;
PageContextImpl pc=null;
try{
pc = ThreadUtil.createPageContext(cw, DevNullOutputStream.DEV_NULL_OUTPUT_STREAM,
"localhost", "/","", new Cookie[0], new Pair[0], null, new Pair[0], new StructImpl(),false,-1);
}
catch(Throwable t){
ExceptionUtil.rethrowIfNecessary(t);
return;
}
PageContext orgPC = ThreadLocalPageContext.get();
try{
ThreadLocalPageContext.register(pc);
// MUST MOST of them are the same, so this is a huge overhead
_addTagMetaData(pc,cw,CFMLEngine.DIALECT_CFML);
_addTagMetaData(pc,cw,CFMLEngine.DIALECT_LUCEE);
}
catch(Throwable t){ExceptionUtil.rethrowIfNecessary(t);}
finally{
pc.getConfig().getFactory().releaseLuceePageContext(pc, true);
ThreadLocalPageContext.register(orgPC);
}
}
private static void _addTagMetaData(PageContext pc,ConfigWebImpl cw, int dialect) {
TagLibTagAttr attrFileName,attrIsWeb;
String filename;
Boolean isWeb;
TagLibTag tlt;
TagLib[] tlds = cw.getTLDs(dialect);
for(int i=0;i<tlds.length;i++){
Map<String, TagLibTag> tags = tlds[i].getTags();
Iterator<TagLibTag> it = tags.values().iterator();
while(it.hasNext()){
tlt = it.next();
if(tlt.getTagClassDefinition().isClassNameEqualTo("lucee.runtime.tag.CFTagCore")) {
attrFileName = tlt.getAttribute("__filename");
attrIsWeb = tlt.getAttribute("__isweb");
if(attrFileName!=null && attrIsWeb!=null) {
filename = Caster.toString(attrFileName.getDefaultValue(),null);
isWeb=Caster.toBoolean(attrIsWeb.getDefaultValue(),null);
if(filename!=null && isWeb!=null) {
addTagMetaData(pc, tlds[i], tlt, filename,isWeb.booleanValue());
}
}
}
}
}
}
private static void addTagMetaData(PageContext pc,TagLib tl, TagLibTag tlt, String filename, boolean isWeb) {
if(pc==null) return;
try{
ConfigWebImpl config=(ConfigWebImpl) pc.getConfig();
PageSource ps = isWeb?
config.getTagMapping().getPageSource(filename):
config.getServerTagMapping().getPageSource(filename);
//Page p = ps.loadPage(pc);
ComponentImpl c = ComponentLoader.loadComponent(pc, ps, filename, true,true);
ComponentSpecificAccess cw = ComponentSpecificAccess.toComponentSpecificAccess(Component.ACCESS_PRIVATE,c);
Struct meta = Caster.toStruct( cw.get(KeyConstants._metadata,null),null);
// TODO handle all metadata here and make checking at runtime useless
if(meta!=null) {
// parse body
boolean rtexprvalue=Caster.toBooleanValue(meta.get(KeyConstants._parsebody,Boolean.FALSE),false);
tlt.setParseBody(rtexprvalue);
// hint
String hint=Caster.toString(meta.get(KeyConstants._hint,null),null);
if(!StringUtil.isEmpty(hint))tlt.setDescription(hint);
}
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
}
/**
* used by the bytecode builded
* @param pc pageContext
* @param className
* @param bundleName
* @param bundleVersion
* @return
* @throws BundleException
* @throws ClassException
*/
public static Object invokeBIF(PageContext pc, Object[] args, String className, String bundleName, String bundleVersion) throws PageException {
try {
Class<?> clazz = ClassUtil.loadClassByBundle(className, bundleName, bundleVersion, pc.getConfig().getIdentification());
BIF bif;
if(Reflector.isInstaneOf(clazz, BIF.class))
bif=(BIF)clazz.newInstance();
else
bif = new BIFProxy(clazz);
return bif.invoke(pc, args);
}
catch (Exception e) {
throw Caster.toPageException(e);
}
}
public static void setAppendix(Tag tag,String appendix) throws PageException { // used by generated bytecode
// FUTURE if(tag instanceof TagPro) ((TagPro)tag).setAppendix(appendix);
Reflector.callMethod(tag, "setAppendix", new Object[]{appendix});
}
public static void setMetaData(Tag tag,String name, Object value) throws PageException { // used by generated bytecode
// FUTURE if(tag instanceof TagPro) ((TagPro)tag).setMetaData(name,value);
Reflector.callMethod(tag, "setMetaData", new Object[]{name,value});
}
public static void hasBody(Tag tag,boolean hasBody) throws PageException { // used by generated bytecode
// FUTURE if(tag instanceof BodyTagPro) ((BodyTagPro)tag).hasBody(hasBody);
Reflector.callMethod(tag, "hasBody", new Object[]{hasBody});
}
}