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