/*
* JBoss, Home of Professional Open Source.
* Copyright 2015, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.jpa.hibernate4;
import static org.jipijapa.JipiLogger.JPA_LOGGER;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.ejb.packaging.NamedInputStream;
import org.hibernate.ejb.packaging.Scanner;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.vfs.VFS;
import org.jboss.vfs.VirtualFile;
import org.jipijapa.plugin.spi.PersistenceUnitMetadata;
/**
* Annotation scanner for Hibernate
*
* @author Scott Marlow (forked from Ales Justin's ScannerImpl in AS6)
*/
public class HibernateAnnotationScanner implements Scanner {
private static final ThreadLocal<PersistenceUnitMetadata> PERSISTENCE_UNIT_METADATA_TLS = new ThreadLocal<PersistenceUnitMetadata>();
/** Caches, used when restarting the persistence unit service */
private static final Map<PersistenceUnitMetadata, Map<URL, Set<Package>>> PACKAGES_IN_JAR_CACHE = new HashMap<PersistenceUnitMetadata, Map<URL,Set<Package>>>();
private static final Map<PersistenceUnitMetadata, Map<URL, Map<Class<? extends Annotation>, Set<Class<?>>>>> CLASSES_IN_JAR_CACHE = new HashMap<PersistenceUnitMetadata, Map<URL, Map<Class<? extends Annotation>, Set<Class<?>>>>>();
public static void setThreadLocalPersistenceUnitMetadata(final PersistenceUnitMetadata pu) {
PERSISTENCE_UNIT_METADATA_TLS.set(pu);
}
public static void clearThreadLocalPersistenceUnitMetadata() {
PERSISTENCE_UNIT_METADATA_TLS.remove();
}
private static void cachePackages(PersistenceUnitMetadata pu, URL jarToScan, Set<Package> packages) {
synchronized (PACKAGES_IN_JAR_CACHE) {
Map<URL, Set<Package>> packagesByUrl = PACKAGES_IN_JAR_CACHE.get(pu);
if (packagesByUrl == null) {
packagesByUrl = new HashMap<URL, Set<Package>>();
PACKAGES_IN_JAR_CACHE.put(pu, packagesByUrl);
}
packagesByUrl.put(jarToScan, packages);
}
}
private static Set<Package> getCachedPackages(PersistenceUnitMetadata pu, URL jarToScan){
synchronized (PACKAGES_IN_JAR_CACHE) {
Map<URL, Set<Package>> packagesByUrl = PACKAGES_IN_JAR_CACHE.get(pu);
if (packagesByUrl == null) {
return Collections.emptySet();
}
Set<Package> packages = packagesByUrl.get(jarToScan);
if (packages == null) {
return Collections.emptySet();
}
return packages;
}
}
private static void cacheClasses(PersistenceUnitMetadata pu, URL jarToScan, Class<? extends Annotation> annotation, Set<Class<?>> classes){
synchronized (CLASSES_IN_JAR_CACHE) {
Map<URL, Map<Class<? extends Annotation>, Set<Class<?>>>> classesByURL = CLASSES_IN_JAR_CACHE.get(pu);
if (classesByURL == null) {
classesByURL = new HashMap<URL, Map<Class<? extends Annotation>, Set<Class<?>>>>();
CLASSES_IN_JAR_CACHE.put(pu, classesByURL);
}
Map<Class<? extends Annotation>, Set<Class<?>>> classesByAnnotation = classesByURL.get(jarToScan);
if (classesByAnnotation == null) {
classesByAnnotation = new HashMap<Class<? extends Annotation>, Set<Class<?>>>();
classesByURL.put(jarToScan, classesByAnnotation);
}
classesByAnnotation.put(annotation, classes);
}
}
private static Set<Class<?>> getCachedClasses(PersistenceUnitMetadata pu, URL jarToScan, Set<Class<? extends Annotation>> annotationsToLookFor){
synchronized (CLASSES_IN_JAR_CACHE) {
Map<URL, Map<Class<? extends Annotation>, Set<Class<?>>>> classesByURL = CLASSES_IN_JAR_CACHE.get(pu);
if (classesByURL == null) {
return Collections.emptySet();
}
Map<Class<? extends Annotation>, Set<Class<?>>> classesByAnnotation = classesByURL.get(jarToScan);
if (classesByAnnotation == null) {
return Collections.emptySet();
}
Set<Class<?>> classes = new HashSet<Class<?>>();
for (Class<? extends Annotation> ann : annotationsToLookFor) {
Set<Class<?>> classesForAnnotation = classesByAnnotation.get(ann);
if (classesForAnnotation != null) {
classes.addAll(classesForAnnotation);
}
}
return classes;
}
}
static void cleanup(PersistenceUnitMetadata pu) {
synchronized (CLASSES_IN_JAR_CACHE) {
CLASSES_IN_JAR_CACHE.remove(pu);
}
synchronized (PACKAGES_IN_JAR_CACHE) {
PACKAGES_IN_JAR_CACHE.remove(pu);
}
}
@Override
public Set<Package> getPackagesInJar(URL jarToScan, Set<Class<? extends Annotation>> annotationsToLookFor) {
if (jarToScan == null) {
throw JPA_LOGGER.nullVar("jarToScan");
}
JPA_LOGGER.tracef("getPackagesInJar url=%s annotations=%s", jarToScan.getPath(), annotationsToLookFor);
Set<Class<?>> resultClasses = new HashSet<Class<?>>();
PersistenceUnitMetadata pu = PERSISTENCE_UNIT_METADATA_TLS.get();
if (pu == null) {
throw JPA_LOGGER.missingPersistenceUnitMetadata();
}
if (annotationsToLookFor.size() > 0) { // Hibernate doesn't pass any annotations currently
resultClasses = getClassesInJar(jarToScan, annotationsToLookFor);
} else {
if (pu.getAnnotationIndex() != null) {
Index index = getJarFileIndex(jarToScan, pu);
if (index == null) {
JPA_LOGGER.tracef("No classes to scan for annotations in jar '%s' (jars with classes '%s')",
jarToScan, pu.getAnnotationIndex().keySet());
return new HashSet<Package>();
}
Collection<ClassInfo> allClasses = index.getKnownClasses();
for (ClassInfo classInfo : allClasses) {
String className = classInfo.name().toString();
try {
resultClasses.add(pu.cacheTempClassLoader().loadClass(className));
} catch (ClassNotFoundException e) {
JPA_LOGGER.cannotLoadEntityClass(e, className);
} catch (NoClassDefFoundError e) {
JPA_LOGGER.cannotLoadEntityClass(e, className);
}
}
}
}
if (pu.getAnnotationIndex() != null || annotationsToLookFor.size() > 0) {
Map<String, Package> uniquePackages = new HashMap<String, Package>();
for (Class<?> classWithAnnotation : resultClasses) {
Package classPackage = classWithAnnotation.getPackage();
if (classPackage != null) {
JPA_LOGGER.tracef("getPackagesInJar found package %s", classPackage);
uniquePackages.put(classPackage.getName(), classPackage);
}
}
Set<Package> packages = new HashSet<Package>(uniquePackages.values());
cachePackages(pu, jarToScan, packages);
return new HashSet<Package>(packages);
} else {
return getCachedPackages(pu, jarToScan);
}
}
private Index getJarFileIndex(final URL jarToScan, final PersistenceUnitMetadata pu) {
return pu.getAnnotationIndex().get(jarToScan);
}
@Override
public Set<Class<?>> getClassesInJar(URL jarToScan, Set<Class<? extends Annotation>> annotationsToLookFor) {
if (jarToScan == null) {
throw JPA_LOGGER.nullVar("jarToScan");
}
JPA_LOGGER.tracef("getClassesInJar url=%s annotations=%s", jarToScan.getPath(), annotationsToLookFor);
PersistenceUnitMetadata pu = PERSISTENCE_UNIT_METADATA_TLS.get();
if (pu == null) {
throw JPA_LOGGER.missingPersistenceUnitMetadata();
}
if (pu.getAnnotationIndex() != null) {
Index index = getJarFileIndex(jarToScan, pu);
if (index == null) {
JPA_LOGGER.tracef("No classes to scan for annotations in jar '%s' (jars with classes '%s')",
jarToScan, pu.getAnnotationIndex().keySet());
return new HashSet<Class<?>>();
}
if (annotationsToLookFor == null) {
throw JPA_LOGGER.nullVar("annotationsToLookFor");
}
if (annotationsToLookFor.size() == 0) {
throw JPA_LOGGER.emptyParameter("annotationsToLookFor");
}
Set<Class<?>> result = new HashSet<Class<?>>();
for (Class<? extends Annotation> annClass : annotationsToLookFor) {
DotName annotation = DotName.createSimple(annClass.getName());
List<AnnotationInstance> classesWithAnnotation = index.getAnnotations(annotation);
Set<Class<?>> classesForAnnotation = new HashSet<Class<?>>();
for (AnnotationInstance annotationInstance : classesWithAnnotation) {
// verify that the annotation target is actually a class, since some frameworks
// may generate bytecode with annotations placed on methods (see AS7-2559)
if (annotationInstance.target() instanceof ClassInfo) {
String className = annotationInstance.target().toString();
try {
JPA_LOGGER.tracef("getClassesInJar found class %s with annotation %s", className, annClass.getName());
Class<?> clazz = pu.cacheTempClassLoader().loadClass(className);
result.add(clazz);
classesForAnnotation.add(clazz);
} catch (ClassNotFoundException e) {
JPA_LOGGER.cannotLoadEntityClass(e, className);
} catch (NoClassDefFoundError e) {
JPA_LOGGER.cannotLoadEntityClass(e, className);
}
}
}
cacheClasses(pu, jarToScan, annClass, classesForAnnotation);
}
return result;
} else {
return getCachedClasses(pu, jarToScan, annotationsToLookFor);
}
}
@Override
public Set<NamedInputStream> getFilesInJar(URL jarToScan, Set<String> filePatterns) {
if (jarToScan == null)
throw JPA_LOGGER.nullVar("jarToScan");
if (filePatterns == null)
throw JPA_LOGGER.nullVar("filePatterns");
Set<NamedInputStream> result = new HashSet<NamedInputStream>();
Map<String, Set<NamedInputStream>> map;
map = new HashMap<String, Set<NamedInputStream>>();
findFiles(jarToScan, filePatterns, map, result);
return result;
}
private void findFiles(URL jarToScan, Set<String> filePatterns, Map<String, Set<NamedInputStream>> map, Set<NamedInputStream> result) {
if (filePatterns.isEmpty()) {
for (Set<NamedInputStream> nims : map.values())
result.addAll(nims);
} else {
VirtualFile root = null;
for (String pattern : filePatterns) {
Set<NamedInputStream> niss = map.get(pattern);
if (niss == null) {
if (root == null)
root = getFile(jarToScan);
try {
List<VirtualFile> children = root.getChildrenRecursively(new HibernatePatternFilter(pattern));
niss = toNIS(children);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (niss != null)
result.addAll(niss);
}
}
}
private Set<NamedInputStream> toNIS(Iterable<VirtualFile> files) {
Set<NamedInputStream> result = new HashSet<NamedInputStream>();
for (VirtualFile file : files) {
NamedInputStream nis = new HibernateVirtualFileNamedInputStream(file);
result.add(nis);
}
return result;
}
@Override
public Set<NamedInputStream> getFilesInClasspath(Set<String> filePatterns) {
throw JPA_LOGGER.notYetImplemented(); // not currently called
}
@Override
public String getUnqualifiedJarName(URL jarUrl) {
VirtualFile file = getFile(jarUrl);
return file.getName();
}
private VirtualFile getFile(URL url) {
try {
return VFS.getChild(url.toURI());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
}