/*
* Copyright 2011 Red Hat, Inc. and/or its affiliates.
*
* 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
package org.infinispan.factories.components;
import org.infinispan.factories.KnownComponentNames;
import org.infinispan.factories.annotations.DefaultFactoryFor;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.factories.annotations.Stop;
import org.infinispan.factories.annotations.SurvivesRestarts;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.ScopeDetector;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.jmx.annotations.ManagedOperation;
import org.infinispan.util.ReflectionUtil;
import org.infinispan.util.Util;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* A utility class used by the Infinispan build process to scan metadata and persist it. Should be used by all
* Infinispan modules that define components decorated with {@link Inject}, {@link Start}, {@link Stop}, etc.
*
* @author Manik Surtani
* @see ComponentMetadataRepo
* @see ComponentMetadata
* @since 5.1
*/
public class ComponentMetadataPersister extends ComponentMetadataRepo {
/**
* Usage: ComponentMetadataPersister [path containing .class files to scan] [output file to generate]
*/
public static void main(String[] args) throws ClassNotFoundException, IOException {
// When run off the command-line or a build script, this program takes in two arguments: the path containing
// class files to scan, and the output file to generate.
long startTime = System.nanoTime();
String path = args[0];
String outputFile = args[1];
System.out.printf(" [ComponentMetadataPersister] Starting component metadata generation. Scanning classes in %s%n", path);
// Wipe any stale data from memory first
FACTORIES.clear();
COMPONENT_METADATA_MAP.clear();
File f = new File(path);
process(path, f);
// Test that all dependencies now exist in the component metadata map.
Map<String, String> dependencies = new HashMap<String, String>(128);
for (ComponentMetadata md : COMPONENT_METADATA_MAP.values()) {
if (md.getDependencies() != null) dependencies.putAll(md.getDependencies());
}
ClassLoader cl = ComponentMetadataRepo.class.getClassLoader();
for (String s : dependencies.keySet()) {
if (!COMPONENT_METADATA_MAP.containsKey(s)) {
// See if anything we already have is assignable from here.
try {
Class<?> dependencyType = Util.loadClass(s, cl);
ComponentMetadata equivalent = null;
for (ComponentMetadata cm : COMPONENT_METADATA_MAP.values()) {
if (dependencyType.isAssignableFrom(cm.getClazz())) {
equivalent = cm;
break;
}
}
if (equivalent != null) COMPONENT_METADATA_MAP.put(s, equivalent);
} catch (Exception e) {
}
}
}
if (Boolean.getBoolean("infinispan.isCoreModule")) {
// Perform this sanity check
boolean hasErrors = false;
for (Map.Entry<String, String> e : dependencies.entrySet()) {
if (!COMPONENT_METADATA_MAP.containsKey(e.getKey())) {
if (!hasFactory(e.getKey()) && !hasFactory(e.getValue()) && !KnownComponentNames.ALL_KNOWN_COMPONENT_NAMES.contains(e.getKey())) {
System.out.printf(" [ComponentMetadataPersister] **** WARNING!!! Missing components or factories for dependency on %s%n", e.getKey());
hasErrors = true;
}
}
}
if (hasErrors && Boolean.getBoolean("infinispan.isCoreModule"))
throw new RuntimeException("Could not pass sanity check of all annotated components and their respective factories/dependencies.");
}
writeMetadata(outputFile);
System.out.printf(" [ComponentMetadataPersister] %s components and %s factories analyzed and persisted in %s.%n%n", COMPONENT_METADATA_MAP.size(), FACTORIES.size(), Util.prettyPrintTime(System.nanoTime() - startTime, TimeUnit.NANOSECONDS));
}
private static boolean hasFactory(String name) {
return FACTORIES.containsKey(name);
}
private static void process(String path, File f) throws ClassNotFoundException {
if (f.isDirectory()) {
for (File child : f.listFiles()) process(path, child);
} else if (isValidClassFile(f)) {
// Process this class file.
String fqcn = extractFqcn(path, f);
processClass(ComponentMetadataRepo.class.getClassLoader().loadClass(fqcn), fqcn);
}
}
private static boolean isValidClassFile(File f) {
// Valid classes end with .class
return f.getName().endsWith(".class");
}
private static void processClass(Class<?> clazz, String className) {
// Look for a @MBean annotation first.
MBean mbean = ReflectionUtil.getAnnotation(clazz, MBean.class);
boolean survivesRestarts;
boolean isGlobal;
// Could still be a valid component.
isGlobal = ScopeDetector.detectScope(clazz) == Scopes.GLOBAL;
survivesRestarts = ReflectionUtil.getAnnotation(clazz, SurvivesRestarts.class) != null;
List<Method> injectMethods = ReflectionUtil.getAllMethods(clazz, Inject.class);
List<Method> startMethods = ReflectionUtil.getAllMethods(clazz, Start.class);
List<Method> stopMethods = ReflectionUtil.getAllMethods(clazz, Stop.class);
ComponentMetadata metadata = null;
if (mbean != null) {
List<Method> managedAttributeMethods = ReflectionUtil.getAllMethods(clazz, ManagedAttribute.class);
List<Field> managedAttributeFields = ReflectionUtil.getAnnotatedFields(clazz, ManagedAttribute.class);
List<Method> managedOperationMethods = ReflectionUtil.getAllMethods(clazz, ManagedOperation.class);
metadata = new ManageableComponentMetadata(clazz, injectMethods, startMethods, stopMethods, isGlobal,
survivesRestarts, managedAttributeFields, managedAttributeMethods,
managedOperationMethods, mbean);
} else if (!injectMethods.isEmpty() || !startMethods.isEmpty() || !stopMethods.isEmpty()
|| isGlobal || survivesRestarts || ReflectionUtil.isAnnotationPresent(clazz, Scope.class)) {
// Then this still is a component!
metadata = new ComponentMetadata(clazz, injectMethods, startMethods, stopMethods, isGlobal, survivesRestarts);
}
if (metadata != null) {
COMPONENT_METADATA_MAP.put(metadata.getName(), metadata);
}
// and also lets check if this class is a factory for anything.
DefaultFactoryFor dff = ReflectionUtil.getAnnotation(clazz, DefaultFactoryFor.class);
if (dff != null) {
for (Class<?> target : dff.classes()) FACTORIES.put(target.getName(), className);
}
}
private static String extractFqcn(String path, File f) {
return f.getAbsolutePath().replace(path, "").replace(File.separator, ".").replace(".class", "").replaceFirst("\\.+", "");
}
private static void writeMetadata(String metadataFile) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(metadataFile);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(bufferedOutputStream);
objectOutputStream.writeObject(COMPONENT_METADATA_MAP);
objectOutputStream.writeObject(FACTORIES);
objectOutputStream.flush();
objectOutputStream.close();
bufferedOutputStream.flush();
bufferedOutputStream.close();
fileOutputStream.flush();
fileOutputStream.close();
System.out.printf(" [ComponentMetadataPersister] Persisted metadata in %s%n", metadataFile);
}
}