/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.jsp.java; import com.caucho.config.inject.InjectManager.ReferenceFactory; import com.caucho.jsp.JspParseException; import com.caucho.jsp.JspUtil; import com.caucho.jsp.TagInstance; import com.caucho.util.BeanUtil; import com.caucho.vfs.WriteStream; import com.caucho.xml.QName; import javax.servlet.jsp.tagext.*; import java.beans.BeanInfo; import java.beans.Introspector; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.logging.Level; import java.util.logging.Logger; /** * Represents a custom tag. */ abstract public class GenericTag extends JspContainerNode { private static final Logger log = Logger.getLogger(GenericTag.class.getName()); private static final String DEFAULT_VAR_TYPE = "java.lang.String"; private static final HashSet<String> _primTypes = new HashSet<String>(); protected TagInstance _tag; protected TagInfo _tagInfo; protected Class<?> _tagClass; protected VariableInfo []_varInfo; private boolean _isDeclaringInstance; public GenericTag() { } public void setTagInfo(TagInfo tagInfo) { _tagInfo = tagInfo; } public TagInfo getTagInfo() { return _tagInfo; } @Override public boolean isJsp21() { return ! isPre21Taglib(); } @Override public boolean isPre21Taglib() { if (_tagInfo == null) return false; TagLibraryInfo library = _tagInfo.getTagLibrary(); if (library == null || library.getRequiredVersion() == null) return false; return "2.1".compareTo(library.getRequiredVersion()) > 0; } public TagInstance getTag() { return _tag; } /** * Returns the tag name for the current tag. */ public String getCustomTagName() { return _tag.getId(); } /** * Returns true if the tag is a simple tag. */ public boolean isSimple() { return _tag.isSimpleTag(); } /** * True if this is a jstl node. */ @Override public boolean isJstl() { String uri = _tag.getTagInfo().getTagLibrary().getURI(); return (JavaJspBuilder.JSTL_CORE_URI.equals(uri) || JavaJspBuilder.JSTL_EL_CORE_URI.equals(uri) || JavaJspBuilder.JSTL_FMT_URI.equals(uri) || JavaJspBuilder.JSTL_XML_URI.equals(uri) || JavaJspBuilder.JSTL_EL_XML_URI.equals(uri) || JavaJspBuilder.JSTL_SQL_URI.equals(uri) || JavaJspBuilder.JSTL_EL_SQL_URI.equals(uri)); } public void setTagClass(Class cl) { _tagClass = cl; } public VariableInfo []getVarInfo() { return _varInfo; } /** * Returns the body content. */ @Override public String getBodyContent() { return _tagInfo.getBodyContent(); } /** * Adds a child node. */ @Override public void addChild(JspNode node) throws JspParseException { if (! "empty".equals(getBodyContent())) super.addChild(node); else if (node instanceof JspAttribute) { super.addChild(node); } else if (node instanceof StaticText && ((StaticText) node).isWhitespace()) { } else { throw error(L.l("<{0}> must be empty. Since <{0}> has a body-content of 'empty', it must not have any content.", getTagName())); } } /** * Completes the element */ @Override public void endElement() throws Exception { addTagDepend(); Hashtable<String,Object> tags = new Hashtable<String,Object>(); for (int i = 0; i < _attributeNames.size(); i++) { QName qName = _attributeNames.get(i); Object value = _attributeValues.get(i); String name = qName.getName(); TagAttributeInfo attrInfo = getAttributeInfo(qName); Method method = getAttributeMethod(qName); Class type = null; if (method != null) type = method.getParameterTypes()[0]; if (value instanceof JspAttribute) { JspAttribute attr = (JspAttribute) value; if (attr.isStatic()) { String textValue = attr.getStaticText(); /* if (type != null) tags.put(name, staticStringToValue(type, textValue)); else tags.put(name, textValue); */ tags.put(name, textValue); } else tags.put(name, TagData.REQUEST_TIME_VALUE); } else if (value instanceof String && hasRuntimeAttribute((String) value)) tags.put(name, TagData.REQUEST_TIME_VALUE); else tags.put(name, value); String typeName = null; boolean isFragment = false; if (attrInfo != null) { typeName = attrInfo.getTypeName(); isFragment = attrInfo.isFragment(); if (isFragment && type != null && type.isAssignableFrom(JspFragment.class)) typeName = JspFragment.class.getName(); } else if (method != null) typeName = type.getName(); if (! isFragment && ! JspFragment.class.getName().equals(typeName)) { } else if (value instanceof JspAttribute) { JspAttribute jspAttr = (JspAttribute) value; jspAttr.setJspFragment(true); } } TagData tagData = new TagData(tags); try { _varInfo = _tagInfo.getVariableInfo(tagData); } catch (Exception e) { throw error(e); } if (_varInfo == null) _varInfo = fillVariableInfo(_tagInfo.getTagVariableInfos(), tagData); TagExtraInfo tei = _tagInfo.getTagExtraInfo(); ValidationMessage []messages; if (tei != null) { messages = tei.validate(tagData); _gen.addDepend(tei.getClass()); if (messages != null && messages.length != 0) { throw error(messages[0].getMessage()); } } } protected void addTagDepend() { if (_tagClass != null) _gen.addDepend(_tagClass); } /** * True if the node has scripting */ @Override public boolean hasScripting() { if (super.hasScripting()) return true; // Any conflicting values must be set each time. for (int i = 0; i < _attributeValues.size(); i++) { QName name = _attributeNames.get(i); Object value = _attributeValues.get(i); try { if (value instanceof String && hasRuntimeAttribute((String) value)) return true; } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); return true; } } return false; } /** * True if the jsf-parent setting is required. */ @Override public boolean isJsfParentRequired() { return true; } /** * Returns true if this instance declares the tag. */ public boolean isDeclaringInstance() { return _isDeclaringInstance; } /** * Returns the variable containing the jsf component */ @Override public String getJsfVar() { return null; } /** * Returns the variable containing the jsf body */ @Override public String getJsfBodyVar() { return null; } @Override public void generateClassEpilogue(JspJavaWriter out) throws IOException { super.generateClassEpilogue(out); if (_tag.getAnalyzedTag().getHasInjection() && ! _tag.getAndSetInjectFactory(true)) { out.println(); out.print("static "); out.printClass(ReferenceFactory.class); out.println(" _jsp_inject_" + _tag.getId()); out.print(" = "); out.printClass(JspUtil.class); out.print(".getInjectFactory("); out.printClass(_tag.getTagClass()); out.println(".class);"); } } /** * Generates code before the actual JSP. */ @Override public void generatePrologue(JspJavaWriter out) throws Exception { for (int i = 0; i < _attributeNames.size(); i++) { QName name = _attributeNames.get(i); Object value = _attributeValues.get(i); if (! (value instanceof JspFragmentNode)) continue; JspFragmentNode frag = (JspFragmentNode) value; TagAttributeInfo attribute = getAttributeInfo(name); String typeName = null; boolean isFragment = false; if (attribute != null && attribute.isFragment()) isFragment = true; String fragmentClass = JspFragment.class.getName(); if (attribute != null && fragmentClass.equals(attribute.getTypeName())) isFragment = true; Method method = getAttributeMethod(name); if (method != null) { typeName = method.getParameterTypes()[0].getName(); if (fragmentClass.equals(typeName)) isFragment = true; } if (isFragment) frag.generateFragmentPrologue(out); } TagInstance parent = getParent().getTag(); boolean isBodyTag = BodyTag.class.isAssignableFrom(_tagClass); boolean isEmpty = isEmpty(); boolean hasBodyContent = isBodyTag && ! isEmpty; _tag = parent.findTag(getQName(), _attributeNames, hasBodyContent); if (_tag == null || ! _parseState.isRecycleTags()) { _tag = parent.addTag(_gen, getQName(), _tagInfo, _tagClass, _attributeNames, _attributeValues, hasBodyContent); if (! JspTagFileSupport.class.isAssignableFrom(_tagClass)) { out.printClass(_tagClass); out.println(" " + _tag.getId() + " = null;"); } _isDeclaringInstance = true; /* if (SimpleTag.class.isAssignableFrom(_tagClass) && hasCustomTag()) out.println("javax.servlet.jsp.tagext.Tag " + _tag.getId() + "_adapter = null;"); */ } else { // Any conflicting values must be set each time. for (int i = 0; i < _attributeNames.size(); i++) { QName name = _attributeNames.get(i); Object value = _attributeValues.get(i); _tag.addAttribute(name, value); } } if (_tag == null) throw new NullPointerException(); /* already taken care of if (! isEmpty()) _tag.setBodyContent(true); */ generatePrologueDeclare(out); generatePrologueChildren(out); } @Override public void generatePrologueDeclare(JspJavaWriter out) throws Exception { // Any AT_END variables for (int i = 0; _varInfo != null && i < _varInfo.length; i++) { VariableInfo var = _varInfo[i]; if (var == null) { } else if (! _gen.hasScripting()) { } else if ((var.getScope() == VariableInfo.AT_END || var.getScope() == VariableInfo.AT_BEGIN) && var.getDeclare() && ! _gen.isDeclared(var.getVarName())) { String className = var.getClassName(); if (className == null || "".equals(className) || "null".equals(className)) className = DEFAULT_VAR_TYPE; validateClass(className, var.getVarName()); out.print(className + " " + var.getVarName() + " = "); _gen.addDeclared(var.getVarName()); if ("byte".equals(var.getClassName()) || "short".equals(var.getClassName()) || "char".equals(var.getClassName()) || "int".equals(var.getClassName()) || "long".equals(var.getClassName()) || "float".equals(var.getClassName()) || "double".equals(var.getClassName())) out.println("0;"); else if ("boolean".equals(var.getClassName())) out.println("false;"); else out.println("null;"); } } } /** * Generates the XML text representation for the tag validation. * * @param os write stream to the generated XML. */ @Override public void printXml(WriteStream os) throws IOException { TagInfo tag = getTagInfo(); String prefix = tag.getTagLibrary().getPrefixString(); String uri = tag.getTagLibrary().getURI(); String name = prefix + ':' + tag.getTagName(); os.print("<" + name); os.print(" xmlns:" + prefix + "=\"" + uri + "\""); printJspId(os); for (int i = 0; i < _attributeNames.size(); i++) { QName attrName = _attributeNames.get(i); Object value = _attributeValues.get(i); if (value instanceof String) { String string = (String) value; printXmlAttribute(os, attrName.getName(), string); } } os.print(">"); printXmlChildren(os); os.print("</" + name + ">"); } /** * Generates the code for a custom tag. * * @param out the output writer for the generated java. */ abstract public void generate(JspJavaWriter out) throws Exception; protected void fillAttributes(JspJavaWriter out, String name) throws Exception { TagAttributeInfo attrs[] = _tagInfo.getAttributes(); // clear any attributes mentioned in the taglib that aren't set for (int i = 0; attrs != null && i < attrs.length; i++) { int p = getAttributeIndex(attrs[i].getName()); if (p < 0 && attrs[i].isRequired()) { throw error(L.l("required attribute '{0}' missing from <{1}>", attrs[i].getName(), getTagName())); } } boolean isDynamic = DynamicAttributes.class.isAssignableFrom(_tagClass); // fill all mentioned attributes for (int i = 0; i < _attributeNames.size(); i++) { QName attrName = _attributeNames.get(i); Object value = _attributeValues.get(i); TagAttributeInfo attribute = getAttributeInfo(attrName); if (attrs != null && attribute == null && ! isDynamic) throw error(L.l("unexpected attribute '{0}' in <{1}>", attrName.getName(), getTagName())); if (_tag.getAttribute(attrName) != null) continue; boolean isFragment = false; if (attribute != null) { isFragment = (attribute.isFragment() || JspFragment.class.getName().equals(attribute.getTypeName())); } if (value instanceof JspAttribute && ((JspAttribute) value).isJspFragment()) isFragment = true; generateSetAttribute(out, name, attrName, value, attribute == null || attribute.canBeRequestTime(), isFragment, attribute); } } private TagAttributeInfo getAttributeInfo(QName attrName) { TagAttributeInfo attrs[] = _tagInfo.getAttributes(); int j = 0; for (j = 0; attrs != null && j < attrs.length; j++) { if (isNameMatch(attrs[j].getName(), attrName)) return attrs[j]; } return null; } private int getAttributeIndex(String name) { for (int i = 0; i < _attributeNames.size(); i++) { QName attrName = _attributeNames.get(i); if (isNameMatch(name, attrName)) return i; } return -1; } private boolean isNameMatch(String defName, QName attrName) { if (defName.equals(attrName.getName())) { return true; } else if (defName.equals(attrName.getLocalName()) && attrName.getPrefix().equals(getQName().getPrefix())) { return true; } else return false; } /** * Sets an attribute for a tag * * @param info the tag's introspected information * @param name the tag's Java variable name * @param attrName the attribute name to set * @param value the new value of the tag. */ void generateSetAttribute(JspJavaWriter out, String name, QName attrName, Object value, boolean allowRtexpr, boolean isFragment, TagAttributeInfo attrInfo) throws Exception { Method method = getAttributeMethod(attrName); boolean isDynamic = DynamicAttributes.class.isAssignableFrom(_tagClass); if (method != null) { // jsp/18cq if (Modifier.isStatic(method.getModifiers())) throw error(L.l("attribute '{0}' may not be a static method.", method.getName())); generateSetParameter(out, name, value, method, allowRtexpr, "pageContext", false, isFragment, attrInfo); } else if (! isDynamic) { throw error(L.l("attribute '{0}' in tag '{1}' has no corresponding set method in tag class '{2}'", attrName.getName(), getTagName(), _tagClass.getName())); } else if (isFragment) { String uri = attrName.getNamespaceURI(); String local = attrName.getLocalName(); out.print(name + ".setDynamicAttribute("); if (uri == null) out.print("null, "); else out.print("\"" + escapeJavaString(uri) + "\", "); JspFragmentNode frag = (JspFragmentNode) value; out.print("\"" + escapeJavaString(local) + "\", "); out.print(frag.generateValue()); out.println(");"); } else { String uri = attrName.getNamespaceURI(); String local = attrName.getLocalName(); out.print(name + ".setDynamicAttribute("); if (uri == null) out.print("null, "); else out.print("\"" + escapeJavaString(uri) + "\", "); out.print("\"" + escapeJavaString(local) + "\", "); out.print(generateRTValue(Object.class, value)); out.println(");"); } } private Method getAttributeMethod(QName attrName) throws Exception { Method method = null; try { BeanInfo info = Introspector.getBeanInfo(_tagClass); if (info != null) method = BeanUtil.getSetMethod(info, attrName.getLocalName()); if (method != null) return method; } catch (Exception e) { log.log(Level.FINER, e.toString(), e); } /* try { method = BeanUtil.getSetMethod(_tagClass, attrName.getLocalName()); if (method != null) return method; } catch (Throwable e) { log.log(Level.FINER, e.toString(), e); } */ return method; } /** * Returns true if there is a tag variable declaration matching the scope. */ protected boolean hasVarDeclaration(int scope) throws Exception { for (int i = 0; _varInfo != null && i < _varInfo.length; i++) { VariableInfo var = _varInfo[i]; if (var != null && var.getScope() == scope) return true; } return false; } /** * Prints a tag variable declaration. Only the variables matching the * scope will be printed. * * @param out the stream to the java code. * @param scope the variable scope to print */ protected void printVarDeclaration(JspJavaWriter out, int scope) throws Exception { for (int i = 0; _varInfo != null && i < _varInfo.length; i++) { VariableInfo var = _varInfo[i]; if (var != null) { printVarDeclare(out, scope, var); printVarAssign(out, scope, var); } } } /** * Prints a tag variable declaration. Only the variables matching the * scope will be printed. * * @param out the stream to the java code. * @param scope the variable scope to print */ protected void printVarDeclare(JspJavaWriter out, int scope) throws Exception { for (int i = 0; _varInfo != null && i < _varInfo.length; i++) { VariableInfo var = _varInfo[i]; if (var != null) printVarDeclare(out, scope, var); } } /** * Prints a tag variable declaration. Only the variables matching the * scope will be printed. * * @param out the stream to the java code. * @param scope the variable scope to print */ protected void printVarAssign(JspJavaWriter out, int scope) throws Exception { for (int i = 0; _varInfo != null && i < _varInfo.length; i++) { VariableInfo var = _varInfo[i]; if (var != null) printVarAssign(out, scope, var); } } /** * Returns the VariableInfo corresponding the to tag vars and the tag * data. Mainly, this means looking up the variable names from the * attributes for the name-from-attribute. * * @param tagVars the implicit tag variables for the tag * @param tagData the parsed tag attributes * * @return an array of filled VariableInfo */ protected VariableInfo []fillVariableInfo(TagVariableInfo []tagVars, TagData tagData) throws JspParseException { if (tagVars == null) return null; VariableInfo []vars = new VariableInfo[tagVars.length]; for (int i = 0; i < tagVars.length; i++) { TagVariableInfo tagVar = tagVars[i]; String name = null; String attributeName = tagVar.getNameFromAttribute(); if (attributeName != null) { Object value = tagData.getAttribute(attributeName); if (value != null && ! (value instanceof String)) { throw error(L.l("tag variable '{0}' may not be a request time attribute", attributeName)); } name = tagData.getAttributeString(attributeName); } if (name == null || "".equals(name) || "null".equals(name)) name = null; if (tagVar.getNameGiven() != null) { if (name != null) throw error(L.l("name-given='{0}' conflicts with name-from-attribute='{1}' because the attribute value is the same as name-given.", tagVar.getNameGiven(), attributeName)); name = tagVar.getNameGiven(); } else if (name == null) continue; vars[i] = new VariableInfo(name, tagVar.getClassName(), tagVar.getDeclare(), tagVar.getScope()); } return vars; } /** * Prints a tag variable declaration. Only the variables matching the * scope will be printed. * * @param out the stream to the java code. * @param scope the variable scope to print */ protected void printVarDeclare(JspJavaWriter out, int scope, VariableInfo var) throws Exception { if (! _gen.hasScripting() || var == null || var.getVarName() == null || "".equals(var.getVarName()) || "null".equals(var.getVarName())) return; if (var.getScope() == scope || var.getScope() == VariableInfo.AT_BEGIN) { if (var.getVarName() == null) throw error(L.l("tag variable expects a name")); String className = var.getClassName(); if (className == null || "".equals(className) || "null".equals(className)) className = DEFAULT_VAR_TYPE; /* if (var.getClassName() == null) throw error(L.l("tag variable '{0}' expects a classname", var.getVarName())); */ validateVarName(var.getVarName()); // jsp/107r if (var.getDeclare() && var.getScope() == scope && (var.getScope() == VariableInfo.NESTED && hasScripting() || var.getScope() == VariableInfo.AT_BEGIN) && ! varAlreadyDeclared(var.getVarName())) { validateClass(className, var.getVarName()); out.println(className + " " + var.getVarName() + ";"); } } } /** * Prints a tag variable declaration. Only the variables matching the * scope will be printed. * * @param out the stream to the java code. * @param scope the variable scope to print */ protected void printVarAssign(JspJavaWriter out, int scope, VariableInfo var) throws Exception { if ("".equals(var.getVarName()) || "null".equals(var.getVarName())) return; if (var.getScope() == scope || var.getScope() == VariableInfo.AT_BEGIN) { if (var.getVarName() == null) throw error(L.l("tag variable expects a name")); String className = var.getClassName(); if (className == null || className.equals("null")) className = DEFAULT_VAR_TYPE; /* if (var.getClassName() == null) throw error(L.l("tag variable '{0}' expects a classname", var.getVarName())); */ validateVarName(var.getVarName()); if (! _gen.hasScripting()) { } else if (var.getScope() != VariableInfo.NESTED || hasScripting()) { out.setLocation(_filename, _startLine); out.print(var.getVarName() + " = "); String v = "pageContext.findAttribute(\"" + var.getVarName() + "\")"; convertParameterValue(out, className, v); out.println(";"); } try { _gen.addBeanClass(var.getVarName(), className); } catch (Exception e) { Throwable cause = e.getCause(); if (cause == null) cause = e; throw error(L.l("'{0}' is an unknown class for tag variable '{1}' in <{2}>", className, var.getVarName(), getTagInfo().getTagName()), cause); } } } private void validateVarName(String name) throws JspParseException { if (! Character.isJavaIdentifierStart(name.charAt(0))) throw error(L.l("tag variable '{0}' is an illegal Java identifier.", name)); for (int i = 0; i < name.length(); i++) { if (! Character.isJavaIdentifierPart(name.charAt(i))) throw error(L.l("tag variable '{0}' is an illegal Java identifier.", name)); } } /** * Returns true if the variable has been declared. */ private boolean varAlreadyDeclared(String varName) { if (_gen.isDeclared(varName)) return true; for (JspNode node = getParent(); node != null; node = node.getParent()) { if (! (node instanceof GenericTag)) continue; if (node instanceof JspFragmentNode) break; GenericTag tag = (GenericTag) node; VariableInfo []varInfo = tag.getVarInfo(); for (int i = 0; varInfo != null && i < varInfo.length; i++) { if (varInfo[i] == null) continue; else if (varInfo[i].getVarName().equals(varName)) return true; } } return false; } /** * Returns true if the tag instance has been declared */ protected boolean isDeclared() { if (! _gen.getRecycleTags()) return false; JspNode parent = getParent(); if (! (parent instanceof JspRoot) && ! (parent instanceof JspTop) && ! (parent instanceof GenericTag) && ! (parent instanceof JspAttribute)) return false; boolean isDeclared = false; ArrayList<JspNode> siblings = getParent().getChildren(); for (int i = 0; i < siblings.size(); i++) { JspNode node = siblings.get(i); if (node == this) { return isDeclared; } if (hasScriptlet(node)) { return false; } if (node instanceof GenericTag) { GenericTag customTag = (GenericTag) node; if (customTag.getTag() == getTag()) isDeclared = true; } } return isDeclared; } /** * Returns true if the node or one of its children is a scriptlet */ protected boolean hasScriptlet(JspNode node) { if (node instanceof JspScriptlet || node instanceof JspExpression) return true; ArrayList<JspNode> children = node.getChildren(); if (children == null) return false; for (int i = 0; i < children.size(); i++) { JspNode child = children.get(i); if (hasScriptlet(child)) return true; } return false; } /** * Checks that the given class is a valid variable class. */ protected void validateClass(String className, String varName) throws JspParseException { try { if (_primTypes.contains(className)) return; else if (className.endsWith("[]")) { validateClass(className.substring(0, className.length() - 2), varName); return; } Class<?> cl = _gen.getBeanClass(className); } catch (ClassNotFoundException e) { throw error(L.l("'{0}' is an unknown class for tag variable '{1}'.", className, varName)); } } static { _primTypes.add("boolean"); _primTypes.add("byte"); _primTypes.add("short"); _primTypes.add("int"); _primTypes.add("long"); _primTypes.add("float"); _primTypes.add("double"); _primTypes.add("char"); } }