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