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

com.sun.faces.config.JavaClassScanningAnnotationScanner Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.faces.config;

import java.net.URI;
import com.sun.faces.util.FacesLogger;

import javax.servlet.ServletContext;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * This class is responsible for scanning the class file bytes of
 * classes contained within the web application for any of the known
 * Faces configuration Annotations:
 * 
    *
  • javax.faces.component.FacesBehavior
  • *
  • javax.faces.render.FacesBehaviorRenderer
  • *
  • javax.faces.component.FacesComponent
  • *
  • javax.faces.convert.FacesConverter
  • *
  • javax.faces.validator.FacesValidator
  • *
  • javax.faces.render.FacesRenderer
  • *
  • javax.faces.bean.ManagedBean
  • *
  • javax.faces.event.NamedEvent
  • *
*/ public class JavaClassScanningAnnotationScanner extends AnnotationScanner { private static final Logger LOGGER = FacesLogger.CONFIG.getLogger(); // Matcher.group(1) == the URL to the JAR file itself. // Matcher.group(2) == the name of the JAR. private static final Pattern JAR_PATTERN = Pattern.compile("(.*/(\\S*\\.jar)).*"); private static final String WEB_INF_CLASSES = "/WEB-INF/classes/"; private ClassFile classFileScanner; // ------------------------------------------------------------ Constructors /** * Creates a new AnnotationScanner instance. * * @param sc the ServletContext for the application to be * scanned */ public JavaClassScanningAnnotationScanner(ServletContext sc) { super(sc); classFileScanner = new ClassFile(); } // ---------------------------------------------------------- Public Methods /** * @return a Map of classes mapped to a specific annotation type. * If no annotations are present, or the application is considered * metadata-complete null will be returned. */ @Override public Map,Set>> getAnnotatedClasses(Set uris) { Set classList = new HashSet(); processWebInfClasses(sc, classList); processClasspath(uris, classList); processScripts(classList); return processClassList(classList); } // --------------------------------------------------------- Private Methods /** * Scans for annotations on classes within JAR files on the classpath. * * @param uris to a faces-config documents that allow us to refer to * unique jar files on the classpath * @param classList the Set to which annotated classes * will be added */ private void processClasspath(Set uris, Set classList) { for (URI uri : uris) { try { Matcher m = JAR_PATTERN.matcher(uri.toString()); if (m.matches()) { String jarName = m.group(2); if (!processJar(jarName)) { continue; } StringBuilder sb = new StringBuilder(32); String us = m.group(1); if (!us.startsWith("jar:")) { sb.append("jar:"); } if (us.startsWith("zip:")) { sb.append("file:").append(us.substring(4)); } else if (us.startsWith("bundle:")) { sb.append("file:").append(us.substring(7)); } else { sb.append(us); } sb.append("!/"); URL u = new URL(sb.toString()); JarFile jarFile = ((JarURLConnection) u.openConnection()).getJarFile(); processJarEntries(jarFile, ((getClasspathPackages() != null) ? getClasspathPackages().get(jarName) : null), classList); } else { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Unable to match URL to a jar file: " + uri .toString()); } } } catch (Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to process annotations for url, {0}. Reason: " + e.toString(), new Object[]{uri}); LOGGER.log(Level.SEVERE, "", e); } } } } /** * Called by {@link ConstantPoolInfo} when processing the bytes of the * class file. * * @param value the String value as provided from {@link ConstantPoolInfo} * @return true if the value is one of the known * Faces annotations, otherwise false */ private static boolean isAnnotation(String value) { return FACES_ANNOTATIONS.contains(value); } /** * Process the entries in the provided JarFile looking for * class files that may be annotated with any of the Faces configuration * annotations. * * @param jarFile the JAR to process * @param allowedPackages the packages that should be scanned within the jar * @param classList the Set to which annotated classes * will be added */ private void processJarEntries(JarFile jarFile, String[] allowedPackages, Set classList) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Scanning JAR {0} for annotations...", jarFile.getName()); } for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) { JarEntry entry = entries.nextElement(); if (entry.isDirectory()) { continue; } String name = entry.getName(); if (name.startsWith("META-INF")) { continue; } if (name.endsWith(".class")) { String cname = convertToClassName(name); if (!processClass(cname, allowedPackages)) { continue; } ReadableByteChannel channel = null; try { channel = Channels.newChannel(jarFile.getInputStream(entry)); if (classFileScanner.containsAnnotation(channel)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "[JAR] Found annotated Class: {0}", cname); } classList.add(cname); } } catch (IOException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unexpected exception scanning JAR {0} for annotations", jarFile.getName()); LOGGER.log(Level.SEVERE, e.toString(), e); } } finally { if (channel != null) { try { channel.close(); } catch (IOException ignored) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, ignored.toString(), ignored); } } } } } } } /** * Scan WEB-INF/classes for classes that may be annotated * with any of the Faces configuration annotations. * * @param sc the ServletContext for the application being * scanned * @param classList the Set to which annotated classes * will be added */ private void processWebInfClasses(ServletContext sc, Set classList) { processWebInfClasses(sc, WEB_INF_CLASSES, classList); } /** * Scan WEB-INF/classes for classes that may be annotated * with any of the Faces configuration annotations. * * @param sc the ServletContext for the application being * scanned * @param path the path to start the scan from * @param classList the Set to which annotated classes * will be added */ private void processWebInfClasses(ServletContext sc, String path, Set classList) { //noinspection unchecked Set paths = sc.getResourcePaths(path); processWebInfClasses(sc, paths, classList); } /** * Scan WEB-INF/classes for classes that may be annotated * with any of the Faces configuration annotations. * * @param sc the ServletContext for the application being * scanned * @param paths a set of paths to process * @param classList the Set to which annotated classes * will be added */ private void processWebInfClasses(ServletContext sc, Set paths, Set classList) { if (paths != null && !paths.isEmpty()) { for (String pathElement : paths) { if (pathElement.endsWith("/")) { processWebInfClasses(sc, pathElement, classList); } else { if (pathElement.endsWith(".class")) { String cname = convertToClassName(WEB_INF_CLASSES, pathElement); if (!processClass(cname)) { continue; } if (containsAnnotation(sc, pathElement)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "[WEB-INF/classes] Found annotated Class: {0}", cname); } classList.add(cname); } } } } } } /** * @param sc the ServletContext for the application being * scanned * @param pathElement the full path to the classfile to be scanned * @return true if the class contains one of the Faces * configuration annotations */ private boolean containsAnnotation(ServletContext sc, String pathElement) { ReadableByteChannel channel = null; try { URL url = sc.getResource(pathElement); channel = Channels.newChannel(url.openStream()); return classFileScanner.containsAnnotation(channel); } catch (MalformedURLException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.toString(), e); } } catch (IOException ioe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, ioe.toString(), ioe); } } finally { if (channel != null) { try { channel.close(); } catch (IOException ignored) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, ignored.toString(), ignored); } } } } return false; } /** * Utility method for converting paths to fully qualified class names. * * @param pathEntry a path entry to a class file * * @return a fully qualfied class name using dot notation */ private String convertToClassName(String pathEntry) { return convertToClassName(null, pathEntry); } /** * Utility method for converting paths to fully qualified class names. * * @param prefix the prefix that should be stripped from the class name * before converting it * @param pathEntry a path to a class file * * @return a fully qualfied class name using dot notation */ private String convertToClassName(String prefix, String pathEntry) { String className = pathEntry; if (prefix != null) { // remove the prefix className = className.substring(prefix.length()); } // remove the .class suffix className = className.substring(0, (className.length() - 6)); return className.replace('/', '.'); } // ----------------------------------------------------------- Inner Classes /** * This class is encapsulating binary .class file information as defined at * http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html *

* This is used by the annotation frameworks to quickly scan .class files * for the presence of annotations. This avoid the annotation framework * having to load each .class file in the class loader. *

* Taken from the GlassFish V2 source base. */ @SuppressWarnings({"UnusedDeclaration"}) private static final class ClassFile { private static final int magic = 0xCAFEBABE; public static final int ACC_PUBLIC = 0x1; public static final int ACC_PRIVATE = 0x2; public static final int ACC_PROTECTED = 0x4; public static final int ACC_STATIC = 0x8; public static final int ACC_FINAL = 0x10; public static final int ACC_SYNCHRONIZED = 0x20; public static final int ACC_THREADSAFE = 0x40; public static final int ACC_TRANSIENT = 0x80; public static final int ACC_NATIVE = 0x100; public static final int ACC_INTERFACE = 0x200; public static final int ACC_ABSTRACT = 0x400; public short majorVersion; public short minorVersion; public ConstantPoolInfo constantPool[]; public short accessFlags; public ConstantPoolInfo thisClass; public ConstantPoolInfo superClass; public ConstantPoolInfo interfaces[]; /** * bunch of stuff I really don't care too much for now. *

* FieldInfo fields[]; MethodInfo methods[]; * AttributeInfo attributes[]; */ ByteBuffer header; ConstantPoolInfo constantPoolInfo = new ConstantPoolInfo(); // ------------------------------------------------------------ Constructors /** * Creates a new instance of ClassFile */ public ClassFile() { header = ByteBuffer.allocate(12000); } // ---------------------------------------------------------- Public Methods public void setConstantPoolInfo(ConstantPoolInfo poolInfo) { constantPoolInfo = poolInfo; } /** * Read the input channel and initialize instance data structure. * * @param in a ReadableByteChannel that provides the bytes * of the classfile * * @return true if the bytes representing this classfile include * one of the annotations we're looking for. * * @throws IOException if an I/O error occurs while reading the class */ public boolean containsAnnotation(ReadableByteChannel in) throws IOException { /** * this is the .class file layout * ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } **/ header.clear(); long read = (long) in.read(header); if (read == -1) { return false; } header.rewind(); if (header.getInt() != magic) { return false; } minorVersion = header.getShort(); majorVersion = header.getShort(); int constantPoolSize = header.getShort(); return constantPoolInfo .containsAnnotation(constantPoolSize, header, in); } } // END ClassFile private static class ConstantPoolInfo { private static final Logger LOGGER = FacesLogger.CONFIG.getLogger(); public static final byte CLASS = 7; public static final int FIELDREF = 9; public static final int METHODREF = 10; public static final int STRING = 8; public static final int INTEGER = 3; public static final int FLOAT = 4; public static final int LONG = 5; public static final int DOUBLE = 6; public static final int INTERFACEMETHODREF = 11; public static final int NAMEANDTYPE = 12; public static final int ASCIZ = 1; public static final int UNICODE = 2; byte[] bytes = new byte[Short.MAX_VALUE]; // ------------------------------------------------------------ Constructors /** * Creates a new instance of ConstantPoolInfo */ public ConstantPoolInfo() { } // ---------------------------------------------------------- Public Methods /** * Read the input channel and initialize instance data structure. * * @param constantPoolSize the constant pool size for this class file * @param buffer the ByteBuffer used to store the bytes from in * @param in ReadableByteChannel from which the class file bytes are * read * * @return true if the bytes representing this classfile include * one of the annotations we're looking for. * * @throws IOException if an I/O error occurs while reading the class */ public boolean containsAnnotation(int constantPoolSize, final ByteBuffer buffer, final ReadableByteChannel in) throws IOException { for (int i = 1; i < constantPoolSize; i++) { if (!refill(buffer, in, 1)) { return true; } final byte type = buffer.get(); switch (type) { case ASCIZ: case UNICODE: if (!refill(buffer, in, 2)) { return true; } final short length = buffer.getShort(); if (length < 0 || length > Short.MAX_VALUE) { return true; } if (length > buffer.capacity()) { return true; } if (!refill(buffer, in, length)) { return true; } buffer.get(bytes, 0, length); /* to speed up the process, I am comparing the first few * bytes to Ljava since all annotations are in the java * package, the reduces dramatically the number or String * construction */ if (bytes[0] == 'L' && bytes[1] == 'j' && bytes[2] == 'a') { String stringValue; if (type == ASCIZ) { stringValue = new String(bytes, 0, length, "US-ASCII"); } else { stringValue = new String(bytes, 0, length); } if (JavaClassScanningAnnotationScanner.isAnnotation(stringValue)) { return true; } } break; case CLASS: case STRING: if (!refill(buffer, in, 2)) { return true; } buffer.getShort(); break; case FIELDREF: case METHODREF: case INTERFACEMETHODREF: case INTEGER: case FLOAT: if (!refill(buffer, in, 4)) { return true; } buffer.position(buffer.position() + 4); break; case LONG: case DOUBLE: if (!refill(buffer, in, 8)) { return true; } buffer.position(buffer.position() + 8); // for long, and double, they use 2 constantPool i++; break; case NAMEANDTYPE: if (!refill(buffer, in, 4)) { return true; } buffer.getShort(); buffer.getShort(); break; default: if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unknow type constant pool {0} at position {1}", new Object[]{type, i}); } break; } } return false; } // ----------------------------------------------------- Private Methods private boolean refill(ByteBuffer buffer, ReadableByteChannel in, int requestLen) throws IOException { int cap = buffer.capacity(); if (buffer.position() + requestLen > cap) { buffer.compact(); int read = in.read(buffer); if (read < 0) { return false; } buffer.rewind(); } return true; } } // END ConstantPoolInfo }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy