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 Collections.addAll(urlSet, urls);
251
252 return urlSet;
253 }
254
255 @Override
256 public Set<URL> getBaseUrls(ExternalContext context) throws IOException
257 {
258 String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
259 jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
260 if (ContainerUtils.isRunningOnGoogleAppEngine(context) &&
261 jarFilesToScanParam != null &&
262 jarFilesToScanParam.length() > 0)
263 {
264 Set<URL> urlSet = new HashSet<URL>();
265
266
267
268 Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
269 while (resources.hasMoreElements())
270 {
271 urlSet.add(resources.nextElement());
272 }
273
274 Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
275 context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
276 if (urlsGAE != null)
277 {
278 urlSet.addAll(urlsGAE);
279 }
280 return urlSet;
281 }
282 else
283 {
284 return getBaseUrls();
285 }
286 }
287
288 protected Collection<Class<?>> getAnnotatedMetaInfClasses(ExternalContext ctx, Set<URL> urls)
289 {
290 if (urls != null && !urls.isEmpty())
291 {
292 List<Class<?>> list = new ArrayList<Class<?>>();
293 for (URL url : urls)
294 {
295 try
296 {
297 JarFile jarFile = getJarFile(url);
298 if (jarFile != null)
299 {
300 archiveClasses(jarFile, list);
301 }
302 }
303 catch(IOException e)
304 {
305 log.log(Level.SEVERE, "cannot scan jar file for annotations:"+url, e);
306 }
307 }
308 return list;
309 }
310 return Collections.emptyList();
311 }
312
313 protected Collection<Class<?>> getGAEAnnotatedMetaInfClasses(ExternalContext context, String filter)
314 {
315 if (!filter.equals("none"))
316 {
317 String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
318 Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
319 if (paths != null)
320 {
321 List<Class<?>> list = new ArrayList<Class<?>>();
322 for (Object pathObject : paths)
323 {
324 String path = (String) pathObject;
325 if (path.endsWith(".jar") && GAEUtils.wildcardMatch(path, jarFilesToScan, GAEUtils.WEB_LIB_PREFIX))
326 {
327
328
329
330 try
331 {
332 URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/");
333 JarFile jarFile = JarUtils.getJarFile(jarUrl);
334 if (jarFile != null)
335 {
336 archiveClasses(jarFile, list);
337 }
338 }
339 catch(IOException e)
340 {
341 log.log(Level.SEVERE,
342 "IOException when reading jar file for annotations using filter: "+filter, e);
343 }
344 }
345 }
346 return list;
347 }
348 }
349 return Collections.emptyList();
350 }
351
352 protected Collection<Class<?>> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url)
353 {
354 return Collections.emptyList();
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 }
374
375 protected Collection<Class<?>> getAnnotatedWebInfClasses(ExternalContext ctx) throws IOException
376 {
377 String scanPackages = ctx.getInitParameter(SCAN_PACKAGES);
378 if (scanPackages != null)
379 {
380 try
381 {
382 return packageClasses(ctx, scanPackages);
383 }
384 catch (ClassNotFoundException e)
385 {
386 throw new FacesException(e);
387 }
388 catch (IOException e)
389 {
390 throw new FacesException(e);
391 }
392 }
393 else
394 {
395 return webClasses(ctx);
396 }
397 }
398
399
400
401
402
403
404
405
406
407
408 private List<Class<?>> packageClasses(final ExternalContext externalContext,
409 final String scanPackages) throws ClassNotFoundException, IOException
410 {
411
412 List<Class<?>> list = new ArrayList<Class<?>>();
413
414 String[] scanPackageTokens = scanPackages.split(",");
415 for (String scanPackageToken : scanPackageTokens)
416 {
417 if (scanPackageToken.toLowerCase().endsWith(".jar"))
418 {
419 URL jarResource = externalContext.getResource(WEB_LIB_PREFIX
420 + scanPackageToken);
421 String jarURLString = "jar:" + jarResource.toString() + "!/";
422 URL url = new URL(jarURLString);
423 JarFile jarFile = ((JarURLConnection) url.openConnection())
424 .getJarFile();
425
426 archiveClasses(jarFile, list);
427 }
428 else
429 {
430 List<Class> list2 = new ArrayList<Class>();
431 _PackageInfo.getInstance().getClasses(list2, scanPackageToken);
432 for (Class c : list2)
433 {
434 list.add(c);
435 }
436 }
437 }
438 return list;
439 }
440
441
442
443
444
445
446
447
448
449
450
451 private List<Class<?>> archiveClasses(JarFile jar, List<Class<?>> list)
452 {
453
454 ClassLoader loader = ClassUtils.getContextClassLoader();
455 if (loader == null)
456 {
457 loader = this.getClass().getClassLoader();
458 }
459 Enumeration<JarEntry> entries = jar.entries();
460 while (entries.hasMoreElements())
461 {
462 JarEntry entry = entries.nextElement();
463 if (entry.isDirectory())
464 {
465 continue;
466 }
467 String name = entry.getName();
468 if (name.startsWith("META-INF/"))
469 {
470 continue;
471 }
472 if (!name.endsWith(".class"))
473 {
474 continue;
475 }
476
477 DataInputStream in = null;
478 boolean couldContainAnnotation = false;
479 try
480 {
481 in = new DataInputStream(jar.getInputStream(entry));
482 couldContainAnnotation = _filter
483 .couldContainAnnotationsOnClassDef(in,
484 byteCodeAnnotationsNames);
485 }
486 catch (IOException e)
487 {
488
489
490
491
492 couldContainAnnotation = true;
493 if (log.isLoggable(Level.FINE))
494 {
495 log.fine("IOException when filtering class " + name
496 + " for annotations");
497 }
498 }
499 finally
500 {
501 if (in != null)
502 {
503 try
504 {
505 in.close();
506 }
507 catch (IOException e)
508 {
509
510 }
511 }
512 }
513
514 if (couldContainAnnotation)
515 {
516 name = name.substring(0, name.length() - 6);
517 Class<?> clazz = null;
518 try
519 {
520 clazz = loader.loadClass(name.replace('/', '.'));
521 }
522 catch (NoClassDefFoundError e)
523 {
524
525 }
526 catch (Exception e)
527 {
528
529 }
530 if (clazz != null)
531 {
532 list.add(clazz);
533 }
534 }
535 }
536 return list;
537
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551 private List<Class<?>> webClasses(ExternalContext externalContext)
552 {
553 List<Class<?>> list = new ArrayList<Class<?>>();
554 webClasses(externalContext, WEB_CLASSES_PREFIX, list);
555 return list;
556 }
557
558
559
560
561
562
563
564
565
566
567
568
569 private void webClasses(ExternalContext externalContext, String prefix,
570 List<Class<?>> list)
571 {
572
573 ClassLoader loader = getClassLoader();
574
575 Set<String> paths = externalContext.getResourcePaths(prefix);
576 if(paths == null)
577 {
578 return;
579 }
580 if (log.isLoggable(Level.FINEST))
581 {
582 log.finest("webClasses(" + prefix + ") - Received " + paths.size()
583 + " paths to check");
584 }
585
586 String path = null;
587
588 if (paths.isEmpty())
589 {
590 if (log.isLoggable(Level.WARNING))
591 {
592 log
593 .warning("AnnotationConfigurator does not found classes "
594 + "for annotations in "
595 + prefix
596 + " ."
597 + " This could happen because maven jetty plugin is used"
598 + " (goal jetty:run). Try configure "
599 + SCAN_PACKAGES + " init parameter "
600 + "or use jetty:run-exploded instead.");
601 }
602 }
603 else
604 {
605 for (Object pathObject : paths)
606 {
607 path = (String) pathObject;
608 if (path.endsWith("/"))
609 {
610 webClasses(externalContext, path, list);
611 }
612 else if (path.endsWith(".class"))
613 {
614 DataInputStream in = null;
615 boolean couldContainAnnotation = false;
616 try
617 {
618 in = new DataInputStream(externalContext
619 .getResourceAsStream(path));
620 couldContainAnnotation = _filter
621 .couldContainAnnotationsOnClassDef(in,
622 byteCodeAnnotationsNames);
623 }
624 catch (IOException e)
625 {
626
627
628
629
630 couldContainAnnotation = true;
631 if (log.isLoggable(Level.FINE))
632 {
633 log.fine("IOException when filtering class " + path
634 + " for annotations");
635 }
636 }
637 finally
638 {
639 if (in != null)
640 {
641 try
642 {
643 in.close();
644 }
645 catch (IOException e)
646 {
647
648 }
649 }
650 }
651
652 if (couldContainAnnotation)
653 {
654
655 path = path.substring(WEB_CLASSES_PREFIX.length());
656 path = path.substring(0, path.length() - 6);
657 path = path.replace('/', '.');
658
659 Class<?> clazz = null;
660 try
661 {
662 clazz = loader.loadClass(path);
663 }
664 catch (NoClassDefFoundError e)
665 {
666
667 }
668 catch (Exception e)
669 {
670
671 }
672 if (clazz != null)
673 {
674 list.add(clazz);
675 }
676 }
677 }
678 }
679 }
680 }
681
682 private JarFile getJarFile(URL url) throws IOException
683 {
684 URLConnection conn = url.openConnection();
685 conn.setUseCaches(false);
686 conn.setDefaultUseCaches(false);
687
688 JarFile jarFile;
689 if (conn instanceof JarURLConnection)
690 {
691 jarFile = ((JarURLConnection) conn).getJarFile();
692 }
693 else
694 {
695 jarFile = _getAlternativeJarFile(url);
696 }
697 return jarFile;
698 }
699
700
701
702
703
704
705
706
707
708 private static JarFile _getAlternativeJarFile(URL url) throws IOException
709 {
710 String urlFile = url.getFile();
711
712
713 int separatorIndex = urlFile.indexOf("!/");
714
715
716 if (separatorIndex == -1)
717 {
718 separatorIndex = urlFile.indexOf('!');
719 }
720
721 if (separatorIndex != -1)
722 {
723 String jarFileUrl = urlFile.substring(0, separatorIndex);
724
725 if (jarFileUrl.startsWith("file:"))
726 {
727 jarFileUrl = jarFileUrl.substring("file:".length());
728 }
729
730 return new JarFile(jarFileUrl);
731 }
732
733 return null;
734 }
735
736 private ClassLoader getClassLoader()
737 {
738 ClassLoader loader = ClassUtils.getContextClassLoader();
739 if (loader == null)
740 {
741 loader = this.getClass().getClassLoader();
742 }
743 return loader;
744 }
745
746 private void processClass(Map<Class<? extends Annotation>,Set<Class<?>>> map, Class<?> clazz)
747 {
748 Annotation[] annotations = clazz.getAnnotations();
749 for (Annotation anno : annotations)
750 {
751 Class<? extends Annotation> annotationClass = anno.annotationType();
752 if (JSF_ANNOTATION_CLASSES.contains(annotationClass))
753 {
754 Set<Class<?>> set = map.get(annotationClass);
755 if (set == null)
756 {
757 set = new HashSet<Class<?>>();
758 set.add(clazz);
759 map.put(annotationClass, set);
760 }
761 else
762 {
763 set.add(clazz);
764 }
765
766 }
767 }
768 }
769 }