All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.config.annotation.DefaultAnnotationProvider Maven / Gradle / Ivy

Go to download

The private implementation classes of the Apache MyFaces Core JSF-2.0 Implementation

There is a newer version: 4.1.0-RC2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.myfaces.config.annotation;

import java.io.DataInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.FacesException;
import javax.faces.bean.ManagedBean;
import javax.faces.component.FacesComponent;
import javax.faces.component.behavior.FacesBehavior;
import javax.faces.context.ExternalContext;
import javax.faces.convert.FacesConverter;
import javax.faces.event.NamedEvent;
import javax.faces.render.FacesBehaviorRenderer;
import javax.faces.render.FacesRenderer;
import javax.faces.validator.FacesValidator;

import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
import org.apache.myfaces.shared.util.ClassUtils;
import org.apache.myfaces.spi.AnnotationProvider;
import org.apache.myfaces.spi.AnnotationProviderFactory;
import org.apache.myfaces.view.facelets.util.Classpath;

/**
 * 
 * @since 2.0.2
 * @author Leonardo Uribe
 */
public class DefaultAnnotationProvider extends AnnotationProvider
{
    private static final Logger log = Logger.getLogger(DefaultAnnotationProvider.class.getName());
    
    /**
     * Servlet context init parameter which defines which packages to scan
     * for beans, separated by commas.
     */
    @JSFWebConfigParam(since="2.0")
    public static final String SCAN_PACKAGES = "org.apache.myfaces.annotation.SCAN_PACKAGES";

    /**
     * 

Prefix path used to locate web application classes for this * web application.

*/ private static final String WEB_CLASSES_PREFIX = "/WEB-INF/classes/"; /** *

Prefix path used to locate web application libraries for this * web application.

*/ private static final String WEB_LIB_PREFIX = "/WEB-INF/lib/"; private static final String META_INF_PREFIX = "META-INF/"; private static final String FACES_CONFIG_SUFFIX = ".faces-config.xml"; private static final String STANDARD_FACES_CONFIG_RESOURCE = "META-INF/standard-faces-config.xml"; /** *

Resource path used to acquire implicit resources buried * inside application JARs.

*/ private static final String FACES_CONFIG_IMPLICIT = "META-INF/faces-config.xml"; private final _ClassByteCodeAnnotationFilter _filter; /** * This set contains the annotation names that this AnnotationConfigurator is able to scan * in the format that is read from .class file. */ private static Set byteCodeAnnotationsNames; static { Set bcan = new HashSet(10, 1f); bcan.add("Ljavax/faces/component/FacesComponent;"); bcan.add("Ljavax/faces/component/behavior/FacesBehavior;"); bcan.add("Ljavax/faces/convert/FacesConverter;"); bcan.add("Ljavax/faces/validator/FacesValidator;"); bcan.add("Ljavax/faces/render/FacesRenderer;"); bcan.add("Ljavax/faces/bean/ManagedBean;"); bcan.add("Ljavax/faces/event/NamedEvent;"); //bcan.add("Ljavax/faces/event/ListenerFor;"); //bcan.add("Ljavax/faces/event/ListenersFor;"); bcan.add("Ljavax/faces/render/FacesBehaviorRenderer;"); byteCodeAnnotationsNames = Collections.unmodifiableSet(bcan); } private static Set> JSF_ANNOTATION_CLASSES; static { Set> bcan = new HashSet>(10, 1f); bcan.add(FacesComponent.class); bcan.add(FacesBehavior.class); bcan.add(FacesConverter.class); bcan.add(FacesValidator.class); bcan.add(FacesRenderer.class); bcan.add(ManagedBean.class); bcan.add(NamedEvent.class); bcan.add(FacesBehaviorRenderer.class); JSF_ANNOTATION_CLASSES = Collections.unmodifiableSet(bcan); } public DefaultAnnotationProvider() { super(); _filter = new _ClassByteCodeAnnotationFilter(); } @Override public Map, Set>> getAnnotatedClasses(ExternalContext ctx) { Map,Set>> map = new HashMap, Set>>(); Collection> classes = null; //1. Scan for annotations on /WEB-INF/classes try { classes = getAnnotatedWebInfClasses(ctx); } catch (IOException e) { throw new FacesException(e); } for (Class clazz : classes) { processClass(map, clazz); } //2. Scan for annotations on classpath try { AnnotationProvider provider = AnnotationProviderFactory.getAnnotationProviderFactory(ctx).getAnnotationProvider(ctx); classes = getAnnotatedMetaInfClasses(ctx, provider.getBaseUrls()); } catch (IOException e) { throw new FacesException(e); } for (Class clazz : classes) { processClass(map, clazz); } //3. Scan on myfaces-impl for annotations available on myfaces-impl. //Also scan jar including META-INF/standard-faces-config.xml //(myfaces-impl jar file) URL url = getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE); if (url == null) { url = getClass().getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE); } classes = getAnnotatedMyfacesImplClasses(ctx, url); for (Class clazz : classes) { processClass(map, clazz); } return map; } @Override public Set getBaseUrls() throws IOException { Set urlSet = new HashSet(); //This usually happens when maven-jetty-plugin is used //Scan jars looking for paths including META-INF/faces-config.xml Enumeration resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT); while (resources.hasMoreElements()) { urlSet.add(resources.nextElement()); } //Scan files inside META-INF ending with .faces-config.xml URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACES_CONFIG_SUFFIX); for (int i = 0; i < urls.length; i++) { urlSet.add(urls[i]); } return urlSet; } protected Collection> getAnnotatedMetaInfClasses(ExternalContext ctx, Set urls) { if (urls != null && !urls.isEmpty()) { List> list = new ArrayList>(); for (URL url : urls) { try { JarFile jarFile = getJarFile(url); if (jarFile != null) { archiveClasses(ctx, jarFile, list); } } catch(IOException e) { log.log(Level.SEVERE, "cannot scan jar file for annotations:"+url, e); } } return list; } return Collections.emptyList(); } protected Collection> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url) { return Collections.emptyList(); /* try { List> list = new ArrayList>(); JarFile jarFile = getJarFile(url); if (jarFile == null) { return list; } else { return archiveClasses(ctx, jarFile, list); } } catch(IOException e) { throw new FacesException("cannot scan jar file for annotations:"+url, e); }*/ } protected Collection> getAnnotatedWebInfClasses(ExternalContext ctx) throws IOException { String scanPackages = ctx.getInitParameter(SCAN_PACKAGES); if (scanPackages != null) { try { return packageClasses(ctx, scanPackages); } catch (ClassNotFoundException e) { throw new FacesException(e); } catch (IOException e) { throw new FacesException(e); } } else { return webClasses(ctx); } } /** *

Return a list of the classes defined within the given packages * If there are no such classes, a zero-length list will be returned.

* * @param scanPackages the package configuration * * @exception ClassNotFoundException if a located class cannot be loaded * @exception IOException if an input/output error occurs */ private List> packageClasses(final ExternalContext externalContext, final String scanPackages) throws ClassNotFoundException, IOException { List> list = new ArrayList>(); String[] scanPackageTokens = scanPackages.split(","); for (String scanPackageToken : scanPackageTokens) { if (scanPackageToken.toLowerCase().endsWith(".jar")) { URL jarResource = externalContext.getResource(WEB_LIB_PREFIX + scanPackageToken); String jarURLString = "jar:" + jarResource.toString() + "!/"; URL url = new URL(jarURLString); JarFile jarFile = ((JarURLConnection) url.openConnection()) .getJarFile(); archiveClasses(externalContext, jarFile, list); } else { List list2 = new ArrayList(); _PackageInfo.getInstance().getClasses(list2, scanPackageToken); for (Class c : list2) { list.add(c); } } } return list; } /** *

Return a list of classes to examine from the specified JAR archive. * If this archive has no classes in it, a zero-length list is returned.

* * @param context ExternalContext instance for * this application * @param jar JarFile for the archive to be scanned * * @exception ClassNotFoundException if a located class cannot be loaded */ private List> archiveClasses(ExternalContext context, JarFile jar, List> list) { // Accumulate and return a list of classes in this JAR file ClassLoader loader = ClassUtils.getContextClassLoader(); if (loader == null) { loader = this.getClass().getClassLoader(); } Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.isDirectory()) { continue; // This is a directory } String name = entry.getName(); if (name.startsWith("META-INF/")) { continue; // Attribute files } if (!name.endsWith(".class")) { continue; // This is not a class } DataInputStream in = null; boolean couldContainAnnotation = false; try { in = new DataInputStream(jar.getInputStream(entry)); couldContainAnnotation = _filter .couldContainAnnotationsOnClassDef(in, byteCodeAnnotationsNames); } catch (IOException e) { // Include this class - we can't scan this class using // the filter, but it could be valid, so we need to // load it using the classLoader. Anyway, log a debug // message. couldContainAnnotation = true; if (log.isLoggable(Level.FINE)) { log.fine("IOException when filtering class " + name + " for annotations"); } } finally { if (in != null) try { in.close(); } catch (IOException e) { // No Op } } if (couldContainAnnotation) { name = name.substring(0, name.length() - 6); // Trim ".class" Class clazz = null; try { clazz = loader.loadClass(name.replace('/', '.')); } catch (NoClassDefFoundError e) { ; // Skip this class - we cannot analyze classes we cannot load } catch (Exception e) { ; // Skip this class - we cannot analyze classes we cannot load } if (clazz != null) { list.add(clazz); } } } return list; } /** *

Return a list of the classes defined under the * /WEB-INF/classes directory of this web * application. If there are no such classes, a zero-length list * will be returned.

* * @param externalContext ExternalContext instance for * this application * * @exception ClassNotFoundException if a located class cannot be loaded */ private List> webClasses(ExternalContext externalContext) { List> list = new ArrayList>(); webClasses(externalContext, WEB_CLASSES_PREFIX, list); return list; } /** *

Add classes found in the specified directory to the specified * list, recursively calling this method when a directory is encountered.

* * @param externalContext ExternalContext instance for * this application * @param prefix Prefix specifying the "directory path" to be searched * @param list List to be appended to * * @exception ClassNotFoundException if a located class cannot be loaded */ private void webClasses(ExternalContext externalContext, String prefix, List> list) { ClassLoader loader = getClassLoader(); Set paths = externalContext.getResourcePaths(prefix); if(paths == null) { return; //need this in case there is no WEB-INF/classes directory } if (log.isLoggable(Level.FINEST)) { log.finest("webClasses(" + prefix + ") - Received " + paths.size() + " paths to check"); } String path = null; if (paths.isEmpty()) { if (log.isLoggable(Level.WARNING)) { log .warning("AnnotationConfigurator does not found classes " + "for annotations in " + prefix + " ." + " This could happen because maven jetty plugin is used" + " (goal jetty:run). Try configure " + SCAN_PACKAGES + " init parameter " + "or use jetty:run-exploded instead."); } } else { for (Object pathObject : paths) { path = (String) pathObject; if (path.endsWith("/")) { webClasses(externalContext, path, list); } else if (path.endsWith(".class")) { DataInputStream in = null; boolean couldContainAnnotation = false; try { in = new DataInputStream(externalContext .getResourceAsStream(path)); couldContainAnnotation = _filter .couldContainAnnotationsOnClassDef(in, byteCodeAnnotationsNames); } catch (IOException e) { // Include this class - we can't scan this class using // the filter, but it could be valid, so we need to // load it using the classLoader. Anyway, log a debug // message. couldContainAnnotation = true; if (log.isLoggable(Level.FINE)) { log.fine("IOException when filtering class " + path + " for annotations"); } } finally { if (in != null) try { in.close(); } catch (IOException e) { // No Op } } if (couldContainAnnotation) { //Load it and add it to list for later processing path = path.substring(WEB_CLASSES_PREFIX.length()); // Strip prefix path = path.substring(0, path.length() - 6); // Strip suffix path = path.replace('/', '.'); // Convert to FQCN Class clazz = null; try { clazz = loader.loadClass(path); } catch (NoClassDefFoundError e) { ; // Skip this class - we cannot analyze classes we cannot load } catch (Exception e) { ; // Skip this class - we cannot analyze classes we cannot load } if (clazz != null) { list.add(clazz); } } } } } } private JarFile getJarFile(URL url) throws IOException { URLConnection conn = url.openConnection(); conn.setUseCaches(false); conn.setDefaultUseCaches(false); JarFile jarFile; if (conn instanceof JarURLConnection) { jarFile = ((JarURLConnection) conn).getJarFile(); } else { jarFile = _getAlternativeJarFile(url); } return jarFile; } /** * taken from org.apache.myfaces.view.facelets.util.Classpath * * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full * solution, since an unpacked WAR or EAR will not have JAR "files" as such. */ private static JarFile _getAlternativeJarFile(URL url) throws IOException { String urlFile = url.getFile(); // Trim off any suffix - which is prefixed by "!/" on Weblogic int separatorIndex = urlFile.indexOf("!/"); // OK, didn't find that. Try the less safe "!", used on OC4J if (separatorIndex == -1) { separatorIndex = urlFile.indexOf('!'); } if (separatorIndex != -1) { String jarFileUrl = urlFile.substring(0, separatorIndex); // And trim off any "file:" prefix. if (jarFileUrl.startsWith("file:")) { jarFileUrl = jarFileUrl.substring("file:".length()); } return new JarFile(jarFileUrl); } return null; } private ClassLoader getClassLoader() { ClassLoader loader = ClassUtils.getContextClassLoader(); if (loader == null) { loader = this.getClass().getClassLoader(); } return loader; } private void processClass(Map,Set>> map, Class clazz) { Annotation[] annotations = clazz.getAnnotations(); for (Annotation anno : annotations) { Class annotationClass = anno.annotationType(); if (JSF_ANNOTATION_CLASSES.contains(annotationClass)) { Set> set = map.get(annotationClass); if (set == null) { set = new HashSet>(); set.add(clazz); map.put(annotationClass, set); } else { set.add(clazz); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy