1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.config.annotation;
20
21 import java.io.DataInputStream;
22 import java.io.IOException;
23 import java.lang.annotation.Annotation;
24 import java.net.JarURLConnection;
25 import java.net.URL;
26 import java.net.URLConnection;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.jar.JarEntry;
37 import java.util.jar.JarFile;
38 import java.util.logging.Level;
39 import java.util.logging.Logger;
40
41 import javax.faces.FacesException;
42 import javax.faces.bean.ManagedBean;
43 import javax.faces.component.FacesComponent;
44 import javax.faces.component.behavior.FacesBehavior;
45 import javax.faces.context.ExternalContext;
46 import javax.faces.convert.FacesConverter;
47 import javax.faces.event.NamedEvent;
48 import javax.faces.render.FacesBehaviorRenderer;
49 import javax.faces.render.FacesRenderer;
50 import javax.faces.validator.FacesValidator;
51
52 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
53 import org.apache.myfaces.shared.config.MyfacesConfig;
54 import org.apache.myfaces.shared.util.ClassUtils;
55 import org.apache.myfaces.spi.AnnotationProvider;
56 import org.apache.myfaces.spi.AnnotationProviderFactory;
57 import org.apache.myfaces.util.ContainerUtils;
58 import org.apache.myfaces.config.util.GAEUtils;
59 import org.apache.myfaces.config.util.JarUtils;
60 import org.apache.myfaces.shared.util.StringUtils;
61 import org.apache.myfaces.view.facelets.util.Classpath;
62
63
64
65
66
67
68 public class DefaultAnnotationProvider extends AnnotationProvider
69 {
70 private static final Logger log = Logger.getLogger(DefaultAnnotationProvider.class.getName());
71
72
73
74
75
76 @JSFWebConfigParam(since="2.0")
77 public static final String SCAN_PACKAGES = "org.apache.myfaces.annotation.SCAN_PACKAGES";
78
79
80
81
82
83 private static final String WEB_CLASSES_PREFIX = "/WEB-INF/classes/";
84
85
86
87
88
89 private static final String WEB_LIB_PREFIX = "/WEB-INF/lib/";
90
91 private static final String META_INF_PREFIX = "META-INF/";
92
93 private static final String FACES_CONFIG_SUFFIX = ".faces-config.xml";
94
95 private static final String STANDARD_FACES_CONFIG_RESOURCE = "META-INF/standard-faces-config.xml";
96
97
98
99
100
101 private static final String FACES_CONFIG_IMPLICIT = "META-INF/faces-config.xml";
102
103 private final _ClassByteCodeAnnotationFilter _filter;
104
105
106
107
108
109 private static Set<String> byteCodeAnnotationsNames;
110
111 static
112 {
113 Set<String> bcan = new HashSet<String>(10, 1f);
114 bcan.add("Ljavax/faces/component/FacesComponent;");
115 bcan.add("Ljavax/faces/component/behavior/FacesBehavior;");
116 bcan.add("Ljavax/faces/convert/FacesConverter;");
117 bcan.add("Ljavax/faces/validator/FacesValidator;");
118 bcan.add("Ljavax/faces/render/FacesRenderer;");
119 bcan.add("Ljavax/faces/bean/ManagedBean;");
120 bcan.add("Ljavax/faces/event/NamedEvent;");
121
122
123 bcan.add("Ljavax/faces/render/FacesBehaviorRenderer;");
124
125 byteCodeAnnotationsNames = Collections.unmodifiableSet(bcan);
126 }
127
128 private static final Set<Class<? extends Annotation>> JSF_ANNOTATION_CLASSES;
129
130 static
131 {
132 Set<Class<? extends Annotation>> bcan = new HashSet<Class<? extends Annotation>>(10, 1f);
133 bcan.add(FacesComponent.class);
134 bcan.add(FacesBehavior.class);
135 bcan.add(FacesConverter.class);
136 bcan.add(FacesValidator.class);
137 bcan.add(FacesRenderer.class);
138 bcan.add(ManagedBean.class);
139 bcan.add(NamedEvent.class);
140 bcan.add(FacesBehaviorRenderer.class);
141 JSF_ANNOTATION_CLASSES = Collections.unmodifiableSet(bcan);
142 }
143
144 public DefaultAnnotationProvider()
145 {
146 super();
147 _filter = new _ClassByteCodeAnnotationFilter();
148 }
149
150 @Override
151 public Map<Class<? extends Annotation>, Set<Class<?>>> getAnnotatedClasses(ExternalContext ctx)
152 {
153 Map<Class<? extends Annotation>,Set<Class<?>>> map = new HashMap<Class<? extends Annotation>, Set<Class<?>>>();
154 Collection<Class<?>> classes = null;
155
156
157 try
158 {
159 classes = getAnnotatedWebInfClasses(ctx);
160 }
161 catch (IOException e)
162 {
163 throw new FacesException(e);
164 }
165
166 for (Class<?> clazz : classes)
167 {
168 processClass(map, clazz);
169 }
170
171
172 String jarAnnotationFilesToScanParam = MyfacesConfig.getCurrentInstance(ctx).getGaeJsfAnnotationsJarFiles();
173 jarAnnotationFilesToScanParam = jarAnnotationFilesToScanParam != null ?
174 jarAnnotationFilesToScanParam.trim() : null;
175 if (ContainerUtils.isRunningOnGoogleAppEngine(ctx) &&
176 jarAnnotationFilesToScanParam != null &&
177 jarAnnotationFilesToScanParam.length() > 0)
178 {
179
180
181 classes = getGAEAnnotatedMetaInfClasses(ctx, jarAnnotationFilesToScanParam);
182 }
183 else
184 {
185 try
186 {
187 AnnotationProvider provider
188 = AnnotationProviderFactory.getAnnotationProviderFactory(ctx).getAnnotationProvider(ctx);
189 classes = getAnnotatedMetaInfClasses(ctx, provider.getBaseUrls(ctx));
190 }
191 catch (IOException e)
192 {
193 throw new FacesException(e);
194 }
195 }
196
197 for (Class<?> clazz : classes)
198 {
199 processClass(map, clazz);
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218 return map;
219 }
220
221 @Override
222 public Set<URL> getBaseUrls() throws IOException
223 {
224 Set<URL> urlSet = new HashSet<URL>();
225
226
227
228 Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
229 while (resources.hasMoreElements())
230 {
231 urlSet.add(resources.nextElement());
232 }
233
234
235 URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACES_CONFIG_SUFFIX);
236 for (int i = 0; i < urls.length; i++)
237 {
238 urlSet.add(urls[i]);
239 }
240
241 return urlSet;
242 }
243
244 @Override
245 public Set<URL> getBaseUrls(ExternalContext context) throws IOException
246 {
247 String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
248 jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
249 if (ContainerUtils.isRunningOnGoogleAppEngine(context) &&
250 jarFilesToScanParam != null &&
251 jarFilesToScanParam.length() > 0)
252 {
253 Set<URL> urlSet = new HashSet<URL>();
254
255
256
257 Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
258 while (resources.hasMoreElements())
259 {
260 urlSet.add(resources.nextElement());
261 }
262
263 Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
264 context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
265 if (urlsGAE != null)
266 {
267 urlSet.addAll(urlsGAE);
268 }
269 return urlSet;
270 }
271 else
272 {
273 return getBaseUrls();
274 }
275 }
276
277 protected Collection<Class<?>> getAnnotatedMetaInfClasses(ExternalContext ctx, Set<URL> urls)
278 {
279 if (urls != null && !urls.isEmpty())
280 {
281 List<Class<?>> list = new ArrayList<Class<?>>();
282 for (URL url : urls)
283 {
284 try
285 {
286 JarFile jarFile = getJarFile(url);
287 if (jarFile != null)
288 {
289 archiveClasses(ctx, jarFile, list);
290 }
291 }
292 catch(IOException e)
293 {
294 log.log(Level.SEVERE, "cannot scan jar file for annotations:"+url, e);
295 }
296 }
297 return list;
298 }
299 return Collections.emptyList();
300 }
301
302 protected Collection<Class<?>> getGAEAnnotatedMetaInfClasses(ExternalContext context, String filter)
303 {
304 if (!filter.equals("none"))
305 {
306 String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
307 Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
308 if (paths != null)
309 {
310 List<Class<?>> list = new ArrayList<Class<?>>();
311 for (Object pathObject : paths)
312 {
313 String path = (String) pathObject;
314 if (path.endsWith(".jar") && GAEUtils.wildcardMatch(path, jarFilesToScan, GAEUtils.WEB_LIB_PREFIX))
315 {
316
317
318
319 try
320 {
321 URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/");
322 JarFile jarFile = JarUtils.getJarFile(jarUrl);
323 if (jarFile != null)
324 {
325 archiveClasses(context, jarFile, list);
326 }
327 }
328 catch(IOException e)
329 {
330 log.log(Level.SEVERE,
331 "IOException when reading jar file for annotations using filter: "+filter, e);
332 }
333 }
334 }
335 return list;
336 }
337 }
338 return Collections.emptyList();
339 }
340
341 protected Collection<Class<?>> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url)
342 {
343 return Collections.emptyList();
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362 }
363
364 protected Collection<Class<?>> getAnnotatedWebInfClasses(ExternalContext ctx) throws IOException
365 {
366 String scanPackages = ctx.getInitParameter(SCAN_PACKAGES);
367 if (scanPackages != null)
368 {
369 try
370 {
371 return packageClasses(ctx, scanPackages);
372 }
373 catch (ClassNotFoundException e)
374 {
375 throw new FacesException(e);
376 }
377 catch (IOException e)
378 {
379 throw new FacesException(e);
380 }
381 }
382 else
383 {
384 return webClasses(ctx);
385 }
386 }
387
388
389
390
391
392
393
394
395
396
397 private List<Class<?>> packageClasses(final ExternalContext externalContext,
398 final String scanPackages) throws ClassNotFoundException, IOException
399 {
400
401 List<Class<?>> list = new ArrayList<Class<?>>();
402
403 String[] scanPackageTokens = scanPackages.split(",");
404 for (String scanPackageToken : scanPackageTokens)
405 {
406 if (scanPackageToken.toLowerCase().endsWith(".jar"))
407 {
408 URL jarResource = externalContext.getResource(WEB_LIB_PREFIX
409 + scanPackageToken);
410 String jarURLString = "jar:" + jarResource.toString() + "!/";
411 URL url = new URL(jarURLString);
412 JarFile jarFile = ((JarURLConnection) url.openConnection())
413 .getJarFile();
414
415 archiveClasses(externalContext, jarFile, list);
416 }
417 else
418 {
419 List<Class> list2 = new ArrayList<Class>();
420 _PackageInfo.getInstance().getClasses(list2, scanPackageToken);
421 for (Class c : list2)
422 {
423 list.add(c);
424 }
425 }
426 }
427 return list;
428 }
429
430
431
432
433
434
435
436
437
438
439
440 private List<Class<?>> archiveClasses(ExternalContext context, JarFile jar, List<Class<?>> list)
441 {
442
443
444 ClassLoader loader = ClassUtils.getContextClassLoader();
445 if (loader == null)
446 {
447 loader = this.getClass().getClassLoader();
448 }
449 Enumeration<JarEntry> entries = jar.entries();
450 while (entries.hasMoreElements())
451 {
452 JarEntry entry = entries.nextElement();
453 if (entry.isDirectory())
454 {
455 continue;
456 }
457 String name = entry.getName();
458 if (name.startsWith("META-INF/"))
459 {
460 continue;
461 }
462 if (!name.endsWith(".class"))
463 {
464 continue;
465 }
466
467 DataInputStream in = null;
468 boolean couldContainAnnotation = false;
469 try
470 {
471 in = new DataInputStream(jar.getInputStream(entry));
472 couldContainAnnotation = _filter
473 .couldContainAnnotationsOnClassDef(in,
474 byteCodeAnnotationsNames);
475 }
476 catch (IOException e)
477 {
478
479
480
481
482 couldContainAnnotation = true;
483 if (log.isLoggable(Level.FINE))
484 {
485 log.fine("IOException when filtering class " + name
486 + " for annotations");
487 }
488 }
489 finally
490 {
491 if (in != null)
492 {
493 try
494 {
495 in.close();
496 }
497 catch (IOException e)
498 {
499
500 }
501 }
502 }
503
504 if (couldContainAnnotation)
505 {
506 name = name.substring(0, name.length() - 6);
507 Class<?> clazz = null;
508 try
509 {
510 clazz = loader.loadClass(name.replace('/', '.'));
511 }
512 catch (NoClassDefFoundError e)
513 {
514
515 }
516 catch (Exception e)
517 {
518
519 }
520 if (clazz != null)
521 {
522 list.add(clazz);
523 }
524 }
525 }
526 return list;
527
528 }
529
530
531
532
533
534
535
536
537
538
539
540
541 private List<Class<?>> webClasses(ExternalContext externalContext)
542 {
543 List<Class<?>> list = new ArrayList<Class<?>>();
544 webClasses(externalContext, WEB_CLASSES_PREFIX, list);
545 return list;
546 }
547
548
549
550
551
552
553
554
555
556
557
558
559 private void webClasses(ExternalContext externalContext, String prefix,
560 List<Class<?>> list)
561 {
562
563 ClassLoader loader = getClassLoader();
564
565 Set<String> paths = externalContext.getResourcePaths(prefix);
566 if(paths == null)
567 {
568 return;
569 }
570 if (log.isLoggable(Level.FINEST))
571 {
572 log.finest("webClasses(" + prefix + ") - Received " + paths.size()
573 + " paths to check");
574 }
575
576 String path = null;
577
578 if (paths.isEmpty())
579 {
580 if (log.isLoggable(Level.WARNING))
581 {
582 log
583 .warning("AnnotationConfigurator does not found classes "
584 + "for annotations in "
585 + prefix
586 + " ."
587 + " This could happen because maven jetty plugin is used"
588 + " (goal jetty:run). Try configure "
589 + SCAN_PACKAGES + " init parameter "
590 + "or use jetty:run-exploded instead.");
591 }
592 }
593 else
594 {
595 for (Object pathObject : paths)
596 {
597 path = (String) pathObject;
598 if (path.endsWith("/"))
599 {
600 webClasses(externalContext, path, list);
601 }
602 else if (path.endsWith(".class"))
603 {
604 DataInputStream in = null;
605 boolean couldContainAnnotation = false;
606 try
607 {
608 in = new DataInputStream(externalContext
609 .getResourceAsStream(path));
610 couldContainAnnotation = _filter
611 .couldContainAnnotationsOnClassDef(in,
612 byteCodeAnnotationsNames);
613 }
614 catch (IOException e)
615 {
616
617
618
619
620 couldContainAnnotation = true;
621 if (log.isLoggable(Level.FINE))
622 {
623 log.fine("IOException when filtering class " + path
624 + " for annotations");
625 }
626 }
627 finally
628 {
629 if (in != null)
630 {
631 try
632 {
633 in.close();
634 }
635 catch (IOException e)
636 {
637
638 }
639 }
640 }
641
642 if (couldContainAnnotation)
643 {
644
645 path = path.substring(WEB_CLASSES_PREFIX.length());
646 path = path.substring(0, path.length() - 6);
647 path = path.replace('/', '.');
648
649 Class<?> clazz = null;
650 try
651 {
652 clazz = loader.loadClass(path);
653 }
654 catch (NoClassDefFoundError e)
655 {
656
657 }
658 catch (Exception e)
659 {
660
661 }
662 if (clazz != null)
663 {
664 list.add(clazz);
665 }
666 }
667 }
668 }
669 }
670 }
671
672 private JarFile getJarFile(URL url) throws IOException
673 {
674 URLConnection conn = url.openConnection();
675 conn.setUseCaches(false);
676 conn.setDefaultUseCaches(false);
677
678 JarFile jarFile;
679 if (conn instanceof JarURLConnection)
680 {
681 jarFile = ((JarURLConnection) conn).getJarFile();
682 }
683 else
684 {
685 jarFile = _getAlternativeJarFile(url);
686 }
687 return jarFile;
688 }
689
690
691
692
693
694
695
696
697
698 private static JarFile _getAlternativeJarFile(URL url) throws IOException
699 {
700 String urlFile = url.getFile();
701
702
703 int separatorIndex = urlFile.indexOf("!/");
704
705
706 if (separatorIndex == -1)
707 {
708 separatorIndex = urlFile.indexOf('!');
709 }
710
711 if (separatorIndex != -1)
712 {
713 String jarFileUrl = urlFile.substring(0, separatorIndex);
714
715 if (jarFileUrl.startsWith("file:"))
716 {
717 jarFileUrl = jarFileUrl.substring("file:".length());
718 }
719
720 return new JarFile(jarFileUrl);
721 }
722
723 return null;
724 }
725
726 private ClassLoader getClassLoader()
727 {
728 ClassLoader loader = ClassUtils.getContextClassLoader();
729 if (loader == null)
730 {
731 loader = this.getClass().getClassLoader();
732 }
733 return loader;
734 }
735
736 private void processClass(Map<Class<? extends Annotation>,Set<Class<?>>> map, Class<?> clazz)
737 {
738 Annotation[] annotations = clazz.getAnnotations();
739 for (Annotation anno : annotations)
740 {
741 Class<? extends Annotation> annotationClass = anno.annotationType();
742 if (JSF_ANNOTATION_CLASSES.contains(annotationClass))
743 {
744 Set<Class<?>> set = map.get(annotationClass);
745 if (set == null)
746 {
747 set = new HashSet<Class<?>>();
748 set.add(clazz);
749 map.put(annotationClass, set);
750 }
751 else
752 {
753 set.add(clazz);
754 }
755
756 }
757 }
758 }
759 }