1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package net.sf.maventaglib;
25
26 import java.lang.reflect.Array;
27 import java.lang.reflect.Method;
28 import java.text.MessageFormat;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Locale;
32
33 import org.apache.commons.beanutils.PropertyUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.apache.maven.doxia.sink.Sink;
36 import org.apache.maven.plugin.logging.Log;
37
38 import net.sf.maventaglib.checker.ELFunction;
39 import net.sf.maventaglib.checker.Tag;
40 import net.sf.maventaglib.checker.TagAttribute;
41 import net.sf.maventaglib.checker.Tld;
42
43
44
45
46
47
48
49 public class ValidateRenderer extends AbstractMavenTaglibReportRenderer
50 {
51
52 private static final int ICO_SUCCESS = 0;
53
54 private static final int ICO_INFO = 1;
55
56 private static final int ICO_WARNING = 2;
57
58 private static final int ICO_ERROR = 3;
59
60 private static final String IMAGE_ERROR_SRC = Messages.getString("Validate.image.error");
61
62 private static final String IMAGE_WARNING_SRC = Messages.getString("Validate.image.warning");
63
64 private static final String IMAGE_INFO_SRC = Messages.getString("Validate.image.info");
65
66 private static final String IMAGE_SUCCESS_SRC = Messages.getString("Validate.image.success");
67
68
69
70
71 private Tld[] tlds;
72
73 private Log log;
74
75 private ClassLoader projectClassLoader;
76
77
78
79
80 private Class<?> tagSupportClass;
81
82
83
84
85 private Class<?> tagExtraInfoClass;
86
87
88
89
90 private Class<?> simpleTagClass;
91
92
93
94
95
96
97
98
99
100
101 public ValidateRenderer(Sink sink, Locale locale, Tld[] tlds, Log log, ClassLoader projectClassLoader)
102 {
103 super(sink, locale);
104 this.tlds = tlds;
105 this.log = log;
106 this.projectClassLoader = projectClassLoader;
107
108 try
109 {
110 tagSupportClass = Class.forName("javax.servlet.jsp.tagext.TagSupport", true, this.projectClassLoader);
111 }
112 catch (ClassNotFoundException e)
113 {
114 log.error(Messages.getString("Validate.error.unabletoload.TagSupport"));
115 }
116 try
117 {
118 tagExtraInfoClass = Class.forName("javax.servlet.jsp.tagext.TagExtraInfo", true, this.projectClassLoader);
119 }
120 catch (ClassNotFoundException e)
121 {
122 log.error(Messages.getString("Validate.error.unabletoload.TagExtraInfo"));
123 }
124 try
125 {
126 simpleTagClass = Class.forName("javax.servlet.jsp.tagext.SimpleTag", true, this.projectClassLoader);
127 }
128 catch (ClassNotFoundException e)
129 {
130 log.debug(Messages.getString("Validate.error.unabletoload.SimpleTag"));
131 }
132
133 }
134
135
136
137
138 @Override
139 public String getTitle()
140 {
141 return getMessageString("Validate.title");
142 }
143
144
145
146
147
148
149
150
151
152
153 @Override
154 protected void renderBody()
155 {
156 sink.body();
157 startSection(getMessageString("Validate.h1"));
158 paragraph(getMessageString("Validate.into1"));
159 paragraph(getMessageString("Validate.intro2"));
160
161 sink.list();
162 for (Tld tld : tlds)
163 {
164
165 sink.listItem();
166 sink.link("#" + tld.getFilename());
167 sink.text(MessageFormat.format(getMessageString("Validate.listitem.tld"),
168 StringUtils.defaultIfEmpty(tld.getName(), tld.getShortname()), tld.getFilename() ));
169 sink.link_();
170 sink.text(getMessageString("Validate.listitem.uri") + tld.getUri());
171
172 sink.listItem_();
173 }
174 sink.list_();
175
176 endSection();
177
178 for (Tld tld : tlds)
179 {
180 checkTld(tld);
181 }
182
183 sink.body_();
184 }
185
186
187
188
189
190 private void checkTld(Tld tld)
191 {
192
193 sink.anchor(tld.getFilename());
194 sink.anchor_();
195 startSection(StringUtils.defaultIfEmpty(tld.getName(), tld.getShortname()) + " " + tld.getFilename());
196
197 doTags(tld.getTags(), tld.getShortname());
198 doFunctions(tld.getFunctions(), tld.getShortname());
199
200 endSection();
201 }
202
203
204
205
206 private void doTags(Tag[] tags, String shortname)
207 {
208 if (tags != null && tags.length > 0)
209 {
210 for (Tag tldItem : tags)
211 {
212 checkTag(shortname, tldItem);
213 }
214 }
215 }
216
217 private void doFunctions(ELFunction[] tags, String shortname)
218 {
219 if (tags != null && tags.length > 0)
220 {
221
222 startSection("EL functions");
223
224 startTable();
225
226 tableHeader(new String[]{
227 getMessageString("Validate.header.validated"), "function", getMessageString("Validate.header.class"), getMessageString("Validate.header.signature") });
228
229 for (ELFunction tldItem : tags)
230 {
231 checkFunction(shortname, tldItem);
232 }
233
234 endTable();
235
236 endSection();
237 }
238 }
239
240
241
242
243
244 private void checkFunction(String prefix, ELFunction tag)
245 {
246
247 String className = tag.getFunctionClass();
248
249 boolean found = true;
250
251 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
252 Thread.currentThread().setContextClassLoader(this.projectClassLoader);
253
254 try
255 {
256 Class<?> functionClass = Class.forName(className, true, this.projectClassLoader);
257
258 String fullSignature = tag.getFunctionSignature();
259 String paramsString = tag.getParameters();
260 String returnvalue = null;
261
262 String methodName = StringUtils.trim(StringUtils.substringBefore(fullSignature, "("));
263 if (StringUtils.contains(methodName, " "))
264 {
265 returnvalue = StringUtils.substringBefore(methodName, " ");
266 methodName = StringUtils.substringAfter(methodName, " ");
267 }
268
269 String[] params = StringUtils.split(paramsString, ",");
270
271 List<Class<?>> parClasses = new ArrayList<>(params.length);
272
273 for (String stringClass : params)
274 {
275 parClasses.add(Class.forName(StringUtils.trim(stringClass), true, this.projectClassLoader));
276 }
277
278 Method method = functionClass.getMethod(methodName, parClasses.toArray(new Class<?>[0]));
279
280 Class< ? > returnType = method.getReturnType();
281
282 if (!(returnvalue == null || returnType.getCanonicalName().equals(returnvalue)))
283 {
284 found = false;
285 }
286 }
287 catch (Throwable e)
288 {
289 found = false;
290 }
291
292 Thread.currentThread().setContextClassLoader(currentClassLoader);
293
294 sink.tableRow();
295
296 sink.tableCell();
297 figure(found ? ICO_SUCCESS : ICO_ERROR);
298 sink.tableCell_();
299
300 tableCell(prefix + ":" + tag.getName() + "()");
301 tableCell(className);
302 tableCell(tag.getFunctionSignature());
303
304 sink.tableRow_();
305
306 }
307
308
309
310
311
312 private void checkTag(String prefix, Tag tag)
313 {
314
315
316 startSection("<" + prefix + ":" + tag.getName() + ">");
317
318 String className = tag.getTagClass();
319
320 startTable();
321
322 tableHeader(new String[]{
323 getMessageString("Validate.header.found"), getMessageString("Validate.header.loadable"), getMessageString("Validate.header.extends"), getMessageString("Validate.header.class") });
324
325 boolean found = true;
326 boolean loadable = true;
327 boolean extend = true;
328
329 Object tagObject = null;
330 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
331 Thread.currentThread().setContextClassLoader(this.projectClassLoader);
332
333 try
334 {
335 Class<?> tagClass = Class.forName(className, true, this.projectClassLoader);
336
337
338 extend = tagSupportClass.isAssignableFrom(tagClass)
339 || simpleTagClass != null && simpleTagClass.isAssignableFrom(tagClass);
340
341 try
342 {
343 tagObject = tagClass.getDeclaredConstructor().newInstance();
344 }
345 catch (Throwable e)
346 {
347 loadable = false;
348 }
349
350 }
351 catch (ClassNotFoundException e)
352 {
353 found = false;
354 loadable = false;
355 extend = false;
356 }
357 catch (NoClassDefFoundError e)
358 {
359 found = false;
360 loadable = false;
361 extend = false;
362 }
363
364 Thread.currentThread().setContextClassLoader(currentClassLoader);
365
366 TagAttribute[] attributes = tag.getAttributes();
367
368 sink.tableRow();
369
370 sink.tableCell();
371 figure(found ? ICO_SUCCESS : ICO_ERROR);
372 sink.tableCell_();
373
374 sink.tableCell();
375 figure(loadable ? ICO_SUCCESS : ICO_ERROR);
376 sink.tableCell_();
377
378 sink.tableCell();
379 figure(extend ? ICO_SUCCESS : ICO_ERROR);
380 sink.tableCell_();
381
382 tableCell(className);
383
384 sink.tableRow_();
385
386 if (tag.getTeiClass() != null)
387 {
388 checkTeiClass(tag.getTeiClass());
389 }
390
391 endTable();
392
393 if (tagObject != null && attributes.length > 0)
394 {
395
396 startTable();
397 tableHeader(new String[]{
398 StringUtils.EMPTY,
399 getMessageString("Validate.header.attributename"), getMessageString("Validate.header.tlddeclares"), getMessageString("Validate.header.tagdeclares") });
400
401 for (TagAttribute attribute : attributes)
402 {
403 checkAttribute(tagObject, attribute);
404 }
405
406 endTable();
407 }
408 endSection();
409 }
410
411
412
413
414
415 private void checkTeiClass(String className)
416 {
417
418 boolean found = true;
419 boolean loadable = true;
420 boolean extend = true;
421
422 Class<?> teiClass = null;
423 try
424 {
425 teiClass = Class.forName(className, true, this.projectClassLoader);
426
427 if (tagExtraInfoClass == null || !tagExtraInfoClass.isAssignableFrom(teiClass))
428 {
429 extend = false;
430 }
431
432 try
433 {
434 teiClass.getDeclaredConstructor().newInstance();
435 }
436 catch (Throwable e)
437 {
438 loadable = false;
439 }
440 }
441 catch (ClassNotFoundException e)
442 {
443 found = false;
444 loadable = false;
445 extend = false;
446 }
447
448 catch (NoClassDefFoundError e)
449 {
450 found = false;
451 loadable = false;
452 extend = false;
453 }
454
455 sink.tableRow();
456
457 sink.tableCell();
458 figure(found ? ICO_SUCCESS : ICO_ERROR);
459 sink.tableCell_();
460
461 sink.tableCell();
462 figure(loadable ? ICO_SUCCESS : ICO_ERROR);
463 sink.tableCell_();
464
465 sink.tableCell();
466 figure(extend ? ICO_SUCCESS : ICO_ERROR);
467 sink.tableCell_();
468
469 sink.tableCell();
470 sink.text(className);
471 sink.tableCell_();
472
473 sink.tableRow_();
474 }
475
476
477
478
479
480
481 private void checkAttribute(Object tag, TagAttribute attribute)
482 {
483
484 String tldType = attribute.getType();
485 String tldName = attribute.getName();
486 Class<?> tagType = null;
487 String tagTypeName = null;
488
489 List<ValidationError> validationErrors = new ArrayList<>(3);
490
491 if (!PropertyUtils.isWriteable(tag, tldName))
492 {
493 validationErrors.add(new ValidationError(ValidationError.LEVEL_ERROR,
494 getMessageString("Validate.error.setternotfound")));
495 }
496
497
498 if (validationErrors.isEmpty())
499 {
500
501 try
502 {
503 tagType = PropertyUtils.getPropertyType(tag, tldName);
504 }
505 catch (Throwable e)
506 {
507
508 log.warn(e);
509 }
510 tagTypeName = tagType == null ? StringUtils.EMPTY : tagType.getName();
511
512 if (tldType != null && tagType != null)
513 {
514 Class<?> tldTypeClass = getClassFromName(tldType);
515
516 if (!tagType.isAssignableFrom(tldTypeClass))
517 {
518
519 validationErrors.add(new ValidationError(ValidationError.LEVEL_ERROR, MessageFormat.format(
520 getMessageString("Validate.error.attributetypemismatch"),
521 tldType, tagType.getName() )));
522 }
523 }
524 }
525
526
527 if (validationErrors.isEmpty())
528 {
529
530 if (tldType != null && !tldType.equals(tagType.getName()))
531 {
532 validationErrors.add(new ValidationError(ValidationError.LEVEL_WARNING, MessageFormat.format(
533 getMessageString("Validate.error.attributetypeinexactmatch"),
534 tldType, tagType.getName() )));
535 }
536 else if (tldType == null && !String.class.equals(tagType))
537 {
538 validationErrors.add(new ValidationError(ValidationError.LEVEL_INFO,
539 getMessageString("Validate.error.attributetype")));
540 }
541 }
542
543 sink.tableRow();
544
545 sink.tableCell();
546
547 int figure = ICO_SUCCESS;
548
549 for (ValidationError error : validationErrors)
550 {
551 if (error.getLevel() == ValidationError.LEVEL_ERROR)
552 {
553 figure = ICO_ERROR;
554 }
555 else if (figure == ICO_SUCCESS)
556 {
557 figure = ICO_WARNING;
558 }
559
560 }
561
562 figure(figure);
563 sink.tableCell_();
564
565 sink.tableCell();
566 sink.text(tldName);
567
568 for (ValidationError error : validationErrors)
569 {
570 sink.lineBreak();
571 if (error.getLevel() == ValidationError.LEVEL_ERROR)
572 {
573 sink.bold();
574 }
575 sink.text(error.getText());
576 if (error.getLevel() == ValidationError.LEVEL_ERROR)
577 {
578 sink.bold_();
579
580 }
581 }
582
583 sink.tableCell_();
584
585 sink.tableCell();
586 if (tldType != null)
587 {
588 sink.text(StringUtils.substringAfter(tldType, "java.lang."));
589 }
590 sink.tableCell_();
591
592 tableCell(StringUtils.substringAfter(tagTypeName, "java.lang."));
593
594 sink.tableRow_();
595
596 }
597
598 private void figure(int type)
599 {
600 String text;
601 String src;
602
603 switch (type)
604 {
605 case ICO_ERROR :
606 text = getMessageString("Validate.level.error");
607 src = IMAGE_ERROR_SRC;
608 break;
609 case ICO_WARNING :
610 text = getMessageString("Validate.level.warning");
611 src = IMAGE_WARNING_SRC;
612 break;
613 case ICO_INFO :
614 text = getMessageString("Validate.level.info");
615 src = IMAGE_INFO_SRC;
616 break;
617 default :
618 text = getMessageString("Validate.level.success");
619 src = IMAGE_SUCCESS_SRC;
620 break;
621 }
622
623 sink.figure();
624 sink.figureGraphics(src);
625 sink.figureCaption();
626 sink.text(text);
627 sink.figureCaption_();
628 sink.figure_();
629 }
630
631
632
633
634
635
636 private Class<?> getClassFromName(String className)
637 {
638
639 Class<?> tldTypeClass = tryGettingPrimitiveClass(className);
640
641 if (tldTypeClass == null)
642 {
643
644 try
645 {
646 if (isArrayClassName(className))
647 {
648 tldTypeClass = getArrayClass(className);
649 }
650 else
651 {
652 tldTypeClass = Class.forName(className, true, this.projectClassLoader);
653 }
654 }
655 catch (ClassNotFoundException e)
656 {
657 log.error(MessageFormat.format(Messages.getString("Validate.error.unabletofindclass"),
658 className ));
659 }
660 }
661 return tldTypeClass;
662 }
663
664 private Class<?> tryGettingPrimitiveClass(String className)
665 {
666 if ("int".equals(className))
667 {
668 return int.class;
669 }
670 if ("long".equals(className))
671 {
672 return long.class;
673 }
674 if ("double".equals(className))
675 {
676 return double.class;
677 }
678 if ("boolean".equals(className))
679 {
680 return boolean.class;
681 }
682 if ("char".equals(className))
683 {
684 return char.class;
685 }
686 if ("byte".equals(className))
687 {
688 return byte.class;
689 }
690
691 return null;
692 }
693
694 private boolean isArrayClassName(String className)
695 {
696 return className.endsWith("[]");
697 }
698
699 private Class<?> getArrayClass(String className) throws ClassNotFoundException
700 {
701 String elementClassName = StringUtils.replace(className, "[]", "");
702 Class<?> elementClass = tryGettingPrimitiveClass(elementClassName);
703 if (elementClass == null)
704 {
705 elementClass = Class.forName(elementClassName);
706 }
707 return Array.newInstance(elementClass, 0).getClass();
708 }
709
710 static class ValidationError
711 {
712
713 public static final int LEVEL_INFO = 1;
714
715 public static final int LEVEL_WARNING = 2;
716
717 public static final int LEVEL_ERROR = 3;
718
719 private int level;
720
721 private String text;
722
723
724
725
726
727 public ValidationError(int level, String text)
728 {
729 this.level = level;
730 this.text = text;
731 }
732
733
734
735
736
737 public int getLevel()
738 {
739 return this.level;
740 }
741
742
743
744
745
746 public void setLevel(int level)
747 {
748 this.level = level;
749 }
750
751
752
753
754
755 public String getText()
756 {
757 return this.text;
758 }
759
760
761
762
763
764 public void setText(String text)
765 {
766 this.text = text;
767 }
768 }
769
770 }