1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.tomcat.util.descriptor.tld;
18
19 import java.lang.reflect.Method;
20
21 import javax.servlet.jsp.tagext.TagAttributeInfo;
22 import javax.servlet.jsp.tagext.TagVariableInfo;
23 import javax.servlet.jsp.tagext.VariableInfo;
24
25 import org.apache.tomcat.util.digester.Digester;
26 import org.apache.tomcat.util.digester.Rule;
27 import org.apache.tomcat.util.digester.RuleSet;
28 import org.xml.sax.Attributes;
29
30 /**
31  * RulesSet for digesting TLD files.
32  */

33 public class TldRuleSet implements RuleSet {
34     private static final String PREFIX = "taglib";
35     private static final String VALIDATOR_PREFIX = PREFIX + "/validator";
36     private static final String TAG_PREFIX = PREFIX + "/tag";
37     private static final String TAGFILE_PREFIX = PREFIX + "/tag-file";
38     private static final String FUNCTION_PREFIX = PREFIX + "/function";
39
40     @Override
41     public void addRuleInstances(Digester digester) {
42
43         digester.addCallMethod(PREFIX + "/tlibversion""setTlibVersion", 0);
44         digester.addCallMethod(PREFIX + "/tlib-version""setTlibVersion", 0);
45         digester.addCallMethod(PREFIX + "/jspversion""setJspVersion", 0);
46         digester.addCallMethod(PREFIX + "/jsp-version""setJspVersion", 0);
47         digester.addRule(PREFIX, new Rule() {
48             // for TLD 2.0 and later, jsp-version is set by version attribute
49             @Override
50             public void begin(String namespace, String name, Attributes attributes) {
51                 TaglibXml taglibXml = (TaglibXml) digester.peek();
52                 taglibXml.setJspVersion(attributes.getValue("version"));
53             }
54         });
55         digester.addCallMethod(PREFIX + "/shortname""setShortName", 0);
56         digester.addCallMethod(PREFIX + "/short-name""setShortName", 0);
57
58         // common rules
59         digester.addCallMethod(PREFIX + "/uri""setUri", 0);
60         digester.addCallMethod(PREFIX + "/info""setInfo", 0);
61         digester.addCallMethod(PREFIX + "/description""setInfo", 0);
62         digester.addCallMethod(PREFIX + "/listener/listener-class""addListener", 0);
63
64         // validator
65         digester.addObjectCreate(VALIDATOR_PREFIX, ValidatorXml.class.getName());
66         digester.addCallMethod(VALIDATOR_PREFIX + "/validator-class""setValidatorClass", 0);
67         digester.addCallMethod(VALIDATOR_PREFIX + "/init-param""addInitParam", 2);
68         digester.addCallParam(VALIDATOR_PREFIX + "/init-param/param-name", 0);
69         digester.addCallParam(VALIDATOR_PREFIX + "/init-param/param-value", 1);
70         digester.addSetNext(VALIDATOR_PREFIX, "setValidator", ValidatorXml.class.getName());
71
72
73         // tag
74         digester.addObjectCreate(TAG_PREFIX, TagXml.class.getName());
75         addDescriptionGroup(digester, TAG_PREFIX);
76         digester.addCallMethod(TAG_PREFIX + "/name""setName", 0);
77         digester.addCallMethod(TAG_PREFIX + "/tagclass""setTagClass", 0);
78         digester.addCallMethod(TAG_PREFIX + "/tag-class""setTagClass", 0);
79         digester.addCallMethod(TAG_PREFIX + "/teiclass""setTeiClass", 0);
80         digester.addCallMethod(TAG_PREFIX + "/tei-class""setTeiClass", 0);
81         digester.addCallMethod(TAG_PREFIX + "/bodycontent""setBodyContent", 0);
82         digester.addCallMethod(TAG_PREFIX + "/body-content""setBodyContent", 0);
83
84         digester.addRule(TAG_PREFIX + "/variable"new ScriptVariableRule());
85         digester.addCallMethod(TAG_PREFIX + "/variable/name-given""setNameGiven", 0);
86         digester.addCallMethod(TAG_PREFIX + "/variable/name-from-attribute",
87                 "setNameFromAttribute", 0);
88         digester.addCallMethod(TAG_PREFIX + "/variable/variable-class""setClassName", 0);
89         digester.addRule(TAG_PREFIX + "/variable/declare",
90                 new GenericBooleanRule(Variable.class"setDeclare"));
91         digester.addCallMethod(TAG_PREFIX + "/variable/scope""setScope", 0);
92
93         digester.addRule(TAG_PREFIX + "/attribute"new TagAttributeRule());
94         digester.addCallMethod(TAG_PREFIX + "/attribute/description""setDescription", 0);
95         digester.addCallMethod(TAG_PREFIX + "/attribute/name""setName", 0);
96         digester.addRule(TAG_PREFIX + "/attribute/required",
97                 new GenericBooleanRule(Attribute.class"setRequired"));
98         digester.addRule(TAG_PREFIX + "/attribute/rtexprvalue",
99                 new GenericBooleanRule(Attribute.class"setRequestTime"));
100         digester.addCallMethod(TAG_PREFIX + "/attribute/type""setType", 0);
101         digester.addCallMethod(TAG_PREFIX + "/attribute/deferred-value""setDeferredValue");
102         digester.addCallMethod(TAG_PREFIX + "/attribute/deferred-value/type",
103                 "setExpectedTypeName", 0);
104         digester.addCallMethod(TAG_PREFIX + "/attribute/deferred-method""setDeferredMethod");
105         digester.addCallMethod(TAG_PREFIX + "/attribute/deferred-method/method-signature",
106                 "setMethodSignature", 0);
107         digester.addRule(TAG_PREFIX + "/attribute/fragment",
108                 new GenericBooleanRule(Attribute.class"setFragment"));
109
110         digester.addRule(TAG_PREFIX + "/dynamic-attributes",
111                 new GenericBooleanRule(TagXml.class"setDynamicAttributes"));
112         digester.addSetNext(TAG_PREFIX, "addTag", TagXml.class.getName());
113
114         // tag-file
115         digester.addObjectCreate(TAGFILE_PREFIX, TagFileXml.class.getName());
116         addDescriptionGroup(digester, TAGFILE_PREFIX);
117         digester.addCallMethod(TAGFILE_PREFIX + "/name""setName", 0);
118         digester.addCallMethod(TAGFILE_PREFIX + "/path""setPath", 0);
119         digester.addSetNext(TAGFILE_PREFIX, "addTagFile", TagFileXml.class.getName());
120
121         // function
122         digester.addCallMethod(FUNCTION_PREFIX, "addFunction", 3);
123         digester.addCallParam(FUNCTION_PREFIX + "/name", 0);
124         digester.addCallParam(FUNCTION_PREFIX + "/function-class", 1);
125         digester.addCallParam(FUNCTION_PREFIX + "/function-signature", 2);
126     }
127
128     private void addDescriptionGroup(Digester digester, String prefix) {
129         digester.addCallMethod(prefix + "/info""setInfo", 0);
130         digester.addCallMethod(prefix + "small-icon""setSmallIcon", 0);
131         digester.addCallMethod(prefix + "large-icon""setLargeIcon", 0);
132
133         digester.addCallMethod(prefix + "/description""setInfo", 0);
134         digester.addCallMethod(prefix + "/display-name""setDisplayName", 0);
135         digester.addCallMethod(prefix + "/icon/small-icon""setSmallIcon", 0);
136         digester.addCallMethod(prefix + "/icon/large-icon""setLargeIcon", 0);
137     }
138
139     private static class TagAttributeRule extends Rule {
140         @Override
141         public void begin(String namespace, String name, Attributes attributes) throws Exception {
142             TaglibXml taglibXml = (TaglibXml) digester.peek(digester.getCount() - 1);
143             digester.push(new Attribute("1.2".equals(taglibXml.getJspVersion())));
144         }
145
146         @Override
147         public void end(String namespace, String name) throws Exception {
148             Attribute attribute = (Attribute) digester.pop();
149             TagXml tag = (TagXml) digester.peek();
150             tag.getAttributes().add(attribute.toTagAttributeInfo());
151         }
152     }
153
154     public static class Attribute {
155         private final boolean allowShortNames;
156         private String name;
157         private boolean required;
158         private String type;
159         private boolean requestTime;
160         private boolean fragment;
161         private String description;
162         private boolean deferredValue;
163         private boolean deferredMethod;
164         private String expectedTypeName;
165         private String methodSignature;
166
167         private Attribute(boolean allowShortNames) {
168             this.allowShortNames = allowShortNames;
169         }
170
171         public void setName(String name) {
172             this.name = name;
173         }
174
175         public void setRequired(boolean required) {
176             this.required = required;
177         }
178
179         public void setType(String type) {
180             if (allowShortNames) {
181                 switch (type) {
182                     case "Boolean":
183                         this.type = "java.lang.Boolean";
184                         break;
185                     case "Character":
186                         this.type = "java.lang.Character";
187                         break;
188                     case "Byte":
189                         this.type = "java.lang.Byte";
190                         break;
191                     case "Short":
192                         this.type = "java.lang.Short";
193                         break;
194                     case "Integer":
195                         this.type = "java.lang.Integer";
196                         break;
197                     case "Long":
198                         this.type = "java.lang.Long";
199                         break;
200                     case "Float":
201                         this.type = "java.lang.Float";
202                         break;
203                     case "Double":
204                         this.type = "java.lang.Double";
205                         break;
206                     case "String":
207                         this.type = "java.lang.String";
208                         break;
209                     case "Object":
210                         this.type = "java.lang.Object";
211                         break;
212                     default:
213                         this.type = type;
214                         break;
215                 }
216             } else {
217                 this.type = type;
218             }
219         }
220
221         public void setRequestTime(boolean requestTime) {
222             this.requestTime = requestTime;
223         }
224
225         public void setFragment(boolean fragment) {
226             this.fragment = fragment;
227         }
228
229         public void setDescription(String description) {
230             this.description = description;
231         }
232
233         public void setDeferredValue() {
234             this.deferredValue = true;
235         }
236
237         public void setDeferredMethod() {
238             this.deferredMethod = true;
239         }
240
241         public void setExpectedTypeName(String expectedTypeName) {
242             this.expectedTypeName = expectedTypeName;
243         }
244
245         public void setMethodSignature(String methodSignature) {
246             this.methodSignature = methodSignature;
247         }
248
249         public TagAttributeInfo toTagAttributeInfo() {
250             if (fragment) {
251                 // JSP8.5.2: for a fragment type is fixed and rexprvalue is true
252                 type = "javax.servlet.jsp.tagext.JspFragment";
253                 requestTime = true;
254             } else if (deferredValue) {
255                 type = "javax.el.ValueExpression";
256                 if (expectedTypeName == null) {
257                     expectedTypeName = "java.lang.Object";
258                 }
259             } else if (deferredMethod) {
260                 type = "javax.el.MethodExpression";
261                 if (methodSignature == null) {
262                     methodSignature = "java.lang.Object method()";
263                 }
264             }
265
266             // According to JSP spec, for static values (those determined at
267             // translation time) the type is fixed at java.lang.String.
268             if (!requestTime && type == null) {
269                 type = "java.lang.String";
270             }
271
272             return new TagAttributeInfo(
273                     name,
274                     required,
275                     type,
276                     requestTime,
277                     fragment,
278                     description,
279                     deferredValue,
280                     deferredMethod,
281                     expectedTypeName,
282                     methodSignature);
283         }
284     }
285
286     private static class ScriptVariableRule extends Rule {
287         @Override
288         public void begin(String namespace, String name, Attributes attributes) throws Exception {
289             digester.push(new Variable());
290         }
291
292         @Override
293         public void end(String namespace, String name) throws Exception {
294             Variable variable = (Variable) digester.pop();
295             TagXml tag = (TagXml) digester.peek();
296             tag.getVariables().add(variable.toTagVariableInfo());
297         }
298     }
299
300     public static class Variable {
301         private String nameGiven;
302         private String nameFromAttribute;
303         private String className = "java.lang.String";
304         private boolean declare = true;
305         private int scope = VariableInfo.NESTED;
306
307         public void setNameGiven(String nameGiven) {
308             this.nameGiven = nameGiven;
309         }
310
311         public void setNameFromAttribute(String nameFromAttribute) {
312             this.nameFromAttribute = nameFromAttribute;
313         }
314
315         public void setClassName(String className) {
316             this.className = className;
317         }
318
319         public void setDeclare(boolean declare) {
320             this.declare = declare;
321         }
322
323         public void setScope(String scopeName) {
324             switch (scopeName) {
325                 case "NESTED":
326                     scope = VariableInfo.NESTED;
327                     break;
328                 case "AT_BEGIN":
329                     scope = VariableInfo.AT_BEGIN;
330                     break;
331                 case "AT_END":
332                     scope = VariableInfo.AT_END;
333                     break;
334             }
335         }
336
337         public TagVariableInfo toTagVariableInfo() {
338             return new TagVariableInfo(nameGiven, nameFromAttribute, className, declare, scope);
339         }
340     }
341
342     private static class GenericBooleanRule extends Rule {
343         private final Method setter;
344
345         private GenericBooleanRule(Class<?> type, String setterName) {
346             try {
347                 this.setter = type.getMethod(setterName, Boolean.TYPE);
348             } catch (NoSuchMethodException e) {
349                 throw new IllegalArgumentException(e);
350             }
351         }
352
353         @Override
354         public void body(String namespace, String name, String text) throws Exception {
355             if(null != text)
356                 text = text.trim();
357             boolean value = "true".equalsIgnoreCase(text) || "yes".equalsIgnoreCase(text);
358             setter.invoke(digester.peek(), Boolean.valueOf(value));
359         }
360     }
361 }
362