blob: a1fb081ca0a5048155a9de660b8fb788c555a833 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package freemarker.ext.jsp;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import freemarker.core._DelayedJQuote;
import freemarker.core._DelayedShortClassName;
import freemarker.core._ErrorDescriptionBuilder;
import freemarker.core._TemplateModelException;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.jsp.SimpleTagDirectiveModel.TemplateExceptionWrapperJspException;
import freemarker.template.ObjectWrapper;
import freemarker.template.ObjectWrapperAndUnwrapper;
import freemarker.template.Template;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.StringUtil;
class JspTagModelBase {
protected final String tagName;
private final Class tagClass;
private final Method dynaSetter;
private final Map propertySetters = new HashMap();
protected JspTagModelBase(String tagName, Class tagClass) throws IntrospectionException {
this.tagName = tagName;
this.tagClass = tagClass;
BeanInfo bi = Introspector.getBeanInfo(tagClass);
PropertyDescriptor[] pda = bi.getPropertyDescriptors();
for (int i = 0; i < pda.length; i++) {
PropertyDescriptor pd = pda[i];
Method m = pd.getWriteMethod();
if (m != null) {
propertySetters.put(pd.getName(), m);
}
}
// Check to see if the tag implements the JSP2.0 DynamicAttributes
// interface, to allow setting of arbitrary attributes
Method dynaSetter;
try {
dynaSetter = tagClass.getMethod("setDynamicAttribute",
new Class[] {String.class, String.class, Object.class});
} catch (NoSuchMethodException nsme) {
dynaSetter = null;
}
this.dynaSetter = dynaSetter;
}
Object getTagInstance() throws IllegalAccessException, InstantiationException {
return tagClass.newInstance();
}
void setupTag(Object tag, Map args, ObjectWrapper wrapper)
throws TemplateModelException,
InvocationTargetException,
IllegalAccessException {
if (args != null && !args.isEmpty()) {
ObjectWrapperAndUnwrapper unwrapper =
wrapper instanceof ObjectWrapperAndUnwrapper ? (ObjectWrapperAndUnwrapper) wrapper
: BeansWrapper.getDefaultInstance(); // [2.4] Throw exception in this case
final Object[] argArray = new Object[1];
for (Iterator iter = args.entrySet().iterator(); iter.hasNext(); ) {
final Map.Entry entry = (Map.Entry) iter.next();
final Object arg = unwrapper.unwrap((TemplateModel) entry.getValue());
argArray[0] = arg;
final Object paramName = entry.getKey();
Method setterMethod = (Method) propertySetters.get(paramName);
if (setterMethod == null) {
if (dynaSetter == null) {
throw new TemplateModelException("Unknown property "
+ StringUtil.jQuote(paramName.toString())
+ " on instance of " + tagClass.getName());
} else {
dynaSetter.invoke(tag, null, paramName, argArray[0]);
}
} else {
if (arg instanceof BigDecimal) {
argArray[0] = BeansWrapper.coerceBigDecimal(
(BigDecimal) arg, setterMethod.getParameterTypes()[0]);
}
try {
setterMethod.invoke(tag, argArray);
} catch (Exception e) {
final Class setterType = setterMethod.getParameterTypes()[0];
final _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
"Failed to set JSP tag parameter ", new _DelayedJQuote(paramName),
" (declared type: ", new _DelayedShortClassName(setterType)
+ ", actual value's type: ",
(argArray[0] != null
? (Object) new _DelayedShortClassName(argArray[0].getClass()) : "Null"),
"). See cause exception for the more specific cause...");
if (e instanceof IllegalArgumentException && !(setterType.isAssignableFrom(String.class))
&& argArray[0] != null && argArray[0] instanceof String) {
desc.tip("This problem is often caused by unnecessary parameter quotation. Paramters "
+ "aren't quoted in FTL, similarly as they aren't quoted in most languages. "
+ "For example, these parameter assignments are wrong: ",
"<@my.tag p1=\"true\" p2=\"10\" p3=\"${someVariable}\" p4=\"${x+1}\" />",
". The correct form is: ",
"<@my.tag p1=true p2=10 p3=someVariable p4=x+1 />",
". Only string literals are quoted (regardless of where they occur): ",
"<@my.box style=\"info\" message=\"Hello ${name}!\" width=200 />",
".");
}
throw new _TemplateModelException(e, null, desc);
}
}
}
}
}
protected final TemplateModelException toTemplateModelExceptionOrRethrow(Exception e) throws TemplateModelException {
if (e instanceof RuntimeException && !isCommonRuntimeException((RuntimeException) e)) {
throw (RuntimeException) e;
}
if (e instanceof TemplateModelException) {
throw (TemplateModelException) e;
}
if (e instanceof TemplateExceptionWrapperJspException) {
return (TemplateModelException) e.getCause();
}
return new _TemplateModelException(e,
"Error while invoking the ", new _DelayedJQuote(tagName), " JSP custom tag; see cause exception");
}
/**
* Runtime exceptions that we don't want to propagate, instead we warp them into a more helpful exception. These are
* the ones where it's very unlikely that someone tries to catch specifically these around
* {@link Template#process(Object, java.io.Writer)}.
*/
private boolean isCommonRuntimeException(RuntimeException e) {
final Class eClass = e.getClass();
// We deliberately don't accept sub-classes. Those are possibly application specific and some want to catch them
// outside the template.
return eClass == NullPointerException.class
|| eClass == IllegalArgumentException.class
|| eClass == ClassCastException.class
|| eClass == IndexOutOfBoundsException.class;
}
}