1 /***
2 * Copyright 2004 Fabrizio Giustina.
3 *
4 * Licensed under the Artistic License; you may not use this file
5 * except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://maven-taglib.sourceforge.net/license.html
9 *
10 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
11 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
12 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13 */
14 package net.sf.maventaglib.checker;
15
16 import javax.servlet.jsp.tagext.TagExtraInfo;
17 import javax.servlet.jsp.tagext.TagSupport;
18
19 import org.apache.commons.beanutils.PropertyUtils;
20 import org.apache.commons.lang.StringUtils;
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.dom4j.Document;
24 import org.dom4j.Element;
25 import org.dom4j.tree.DefaultDocument;
26 import org.dom4j.tree.DefaultElement;
27
28
29 /***
30 * Validates tag handler classes fount in tlds.
31 * @author Fabrizio Giustina
32 * @version $Revision $ ($Author $)
33 */
34 public class TldChecker
35 {
36
37 /***
38 * logger.
39 */
40 private static Log log = LogFactory.getLog(TldChecker.class);
41
42 /***
43 * Check the given tld. Assure then:
44 * <ul>
45 * <li>Any tag class is loadable</li>
46 * <li>the tag class has a setter for any of the declared attribute</li>
47 * <li>the type declared in the dtd for an attribute (if any) matches the type accepted by the getter</li>
48 * </ul>
49 * @param tlds list of Tld to check.
50 * @return Document containing validation results
51 */
52 public Document check(Tld[] tlds)
53 {
54 Document doc = new DefaultDocument();
55 Element document = new DefaultElement("document");
56
57 doc.add(document);
58
59 for (int j = 0; j < tlds.length; j++)
60 {
61 Element tldNode = checkTld(tlds[j]);
62 document.add(tldNode);
63 }
64
65 return doc;
66 }
67
68 /***
69 * Checks a single tld and returns validation results.
70 * @param tld Tld
71 * @return tld element
72 */
73 private Element checkTld(Tld tld)
74 {
75
76 Element section = new DefaultElement("tld");
77 section.addAttribute("name", tld.getName());
78 section.addAttribute("file", tld.getFilename());
79 Tag[] tags = tld.getTags();
80 if (tags != null)
81 {
82 for (int j = 0; j < tags.length; j++)
83 {
84 Element tagNode = checkTag(tags[j]);
85 section.add(tagNode);
86 }
87 }
88
89 return section;
90 }
91
92 /***
93 * Checks a single tag and returns validation results.
94 * @param tag Tag
95 * @return tag element
96 */
97 private Element checkTag(Tag tag)
98 {
99
100 Element subsection = new DefaultElement("tag");
101
102 String className = tag.getTagClass();
103 subsection.addAttribute("name", tag.getName());
104
105 Element tagImpl = new DefaultElement("tagclass");
106 tagImpl.addAttribute("name", className);
107
108 subsection.add(tagImpl);
109 Object tagObject = null;
110
111 try
112 {
113 Class tagClass = Class.forName(className);
114
115 if (!TagSupport.class.isAssignableFrom(tagClass))
116 {
117 tagImpl.addAttribute("extend", "error");
118 }
119
120 try
121 {
122 tagObject = tagClass.newInstance();
123 }
124 catch (Throwable e)
125 {
126 tagImpl.addAttribute("loadable", "error");
127 }
128 }
129 catch (ClassNotFoundException e)
130 {
131 tagImpl.addAttribute("found", "error");
132 tagImpl.addAttribute("extend", "error");
133 tagImpl.addAttribute("loadable", "error");
134 }
135
136 if (tag.getTeiClass() != null)
137 {
138 Element teiImpl = checkTeiClass(tag.getTeiClass());
139 subsection.add(teiImpl);
140 }
141
142 TagAttribute[] attributes = tag.getAttributes();
143
144 if (tagObject != null && attributes.length > 0)
145 {
146 Element attributesNode = new DefaultElement("attributes");
147 subsection.add(attributesNode);
148
149 for (int j = 0; j < attributes.length; j++)
150 {
151 Element attributeNode = checkAttribute(tagObject, attributes[j]);
152 attributesNode.add(attributeNode);
153 }
154 }
155
156 return subsection;
157 }
158
159 /***
160 * Check a declared TagExtraInfo class.
161 * @param className TEI class name
162 * @return teiclass Element
163 */
164 private Element checkTeiClass(String className)
165 {
166 Element teiImpl = new DefaultElement("teiclass");
167 teiImpl.addAttribute("name", className);
168
169 Class teiClass = null;
170 try
171 {
172 teiClass = Class.forName(className);
173
174 if (!TagExtraInfo.class.isAssignableFrom(teiClass))
175 {
176 teiImpl.addAttribute("extend", "error");
177 }
178
179 try
180 {
181 teiClass.newInstance();
182 }
183 catch (Throwable e)
184 {
185 teiImpl.addAttribute("loadable", "error");
186 }
187 }
188 catch (ClassNotFoundException e)
189 {
190 teiImpl.addAttribute("found", "error");
191 teiImpl.addAttribute("extend", "error");
192 teiImpl.addAttribute("loadable", "error");
193 }
194 return teiImpl;
195 }
196
197 /***
198 * Checks a single attribute and returns validation results.
199 * @param tag tag handler instance
200 * @param attribute TagAttribute
201 * @return attribute element
202 */
203 private Element checkAttribute(Object tag, TagAttribute attribute)
204 {
205 String tldType = attribute.getAttributeType();
206 String tldName = attribute.getAttributeName();
207
208 Element attributeNode = new DefaultElement("attribute");
209 attributeNode.addAttribute("tldname", tldName);
210 attributeNode.addAttribute("tldtype", StringUtils.defaultString(tldType));
211
212 if (!PropertyUtils.isWriteable(tag, tldName))
213 {
214 Element errorNode = new DefaultElement("error");
215 errorNode.addAttribute("level", "error");
216 errorNode.addText("Setter not found");
217 attributeNode.add(errorNode);
218 return attributeNode;
219 }
220
221 Class tagType = null;
222 try
223 {
224 tagType = PropertyUtils.getPropertyType(tag, tldName);
225 }
226 catch (Throwable e)
227 {
228
229 }
230
231 if (tldType != null && tagType != null)
232 {
233 Class tldTypeClass = getClassFromName(tldType);
234
235 if (!tagType.isAssignableFrom(tldTypeClass))
236 {
237
238
239 Element errorNode = new DefaultElement("error");
240 errorNode.addAttribute("level", "error");
241 errorNode.addText("Error in attribute type: tld declares ["
242 + tldType
243 + "], class declares ["
244 + tagType.getName()
245 + "]");
246 attributeNode.add(errorNode);
247 }
248
249 }
250
251 attributeNode.addAttribute("tagtype", tagType == null ? "" : tagType.getName());
252
253 if (tldType != null && !tldType.equals(tagType.getName()))
254 {
255 Element errorNode = new DefaultElement("error");
256 errorNode.addAttribute("level", "warning");
257 errorNode.addText("Inexact match: tld declares ["
258 + tldType
259 + "], class declares ["
260 + tagType.getName()
261 + "]");
262 attributeNode.add(errorNode);
263 }
264 else if (tldType == null && !java.lang.String.class.equals(tagType))
265 {
266 Element errorNode = new DefaultElement("error");
267 errorNode.addAttribute("level", "info");
268 errorNode.addText("Attribute type different from String and not declared in tld.");
269 attributeNode.add(errorNode);
270 }
271
272 return attributeNode;
273
274 }
275
276 /***
277 * returns a class from its name, handling primitives.
278 * @param className clss name
279 * @return Class istantiated using Class.forName or the matching primitive.
280 */
281 private Class getClassFromName(String className)
282 {
283
284 Class tldTypeClass = null;
285
286 if ("int".equals(className))
287 {
288 tldTypeClass = int.class;
289 }
290 else if ("long".equals(className))
291 {
292 tldTypeClass = long.class;
293 }
294 else if ("double".equals(className))
295 {
296 tldTypeClass = double.class;
297 }
298 else if ("boolean".equals(className))
299 {
300 tldTypeClass = boolean.class;
301 }
302 else if ("char".equals(className))
303 {
304 tldTypeClass = char.class;
305 }
306 else if ("byte".equals(className))
307 {
308 tldTypeClass = byte.class;
309 }
310
311 if (tldTypeClass == null)
312 {
313
314 try
315 {
316 tldTypeClass = Class.forName(className);
317 }
318 catch (ClassNotFoundException e)
319 {
320 log.error("unable to find class [" + className + "] declared in 'type' attribute");
321 }
322 }
323 return tldTypeClass;
324 }
325
326 }