/*
* 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.ComponentRegistry;
import org.infinispan.factories.annotations.ComponentName;
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 java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This class contains all of the metadata and implications expressed via the {@link Scope}, {@link SurvivesRestarts},
* {@link DefaultFactoryFor}, {@link ComponentName}, {@link Inject}, {@link Start} and {@link Stop} annotations. Instead
* of scanning for these annotations and working out dependency chains at runtime "on-demand", since Infinispan 5.1, this
* process now happens offline, at build-time.
* <p />
* When compiling Infinispan, components and their dependency chains are inspected and the information expressed by the
* annotations above are denormalized and a series of {@link ComponentMetadata} objects are created and persisted in the
* Infinispan jar.
* <p />
* This metadata is then read in by the {@link ComponentMetadataRepo} at runtime, and used by the {@link ComponentRegistry}
* and other factory-like classes to bootstrap an Infinispan node.
* <p />
* Also see {@link ManageableComponentMetadata} for components that also expose JMX information.
* @author Manik Surtani
* @since 5.1
* @see ManageableComponentMetadata
* @see ComponentMetadataRepo
*/
public class ComponentMetadata implements Serializable {
public static final InjectMetadata[] EMPTY_INJECT_METHODS = {};
public static final PrioritizedMethodMetadata[] EMPTY_PRIORITIZED_METHODS = {};
private String name;
private transient Map<String, String> dependencies;
private InjectMetadata[] injectMetadata;
private PrioritizedMethodMetadata[] startMethods;
private PrioritizedMethodMetadata[] stopMethods;
private boolean globalScope = false;
private boolean survivesRestarts = false;
private transient Class<?> clazz;
ComponentMetadata() {
globalScope = false;
survivesRestarts = true;
}
public ComponentMetadata(Class<?> component, List<Method> injectMethods, List<Method> startMethods, List<Method> stopMethods, boolean global, boolean survivesRestarts) {
clazz = component;
name = component.getName();
globalScope = global;
this.survivesRestarts = survivesRestarts;
if (startMethods != null && !startMethods.isEmpty()) {
this.startMethods = new PrioritizedMethodMetadata[startMethods.size()];
int i=0;
for (Method m : startMethods) {
Start s = m.getAnnotation(Start.class);
this.startMethods[i++] = new PrioritizedMethodMetadata(m.getName(), s.priority());
}
}
if (stopMethods != null && !stopMethods.isEmpty()) {
this.stopMethods = new PrioritizedMethodMetadata[stopMethods.size()];
int i=0;
for (Method m : stopMethods) {
Stop s = m.getAnnotation(Stop.class);
this.stopMethods[i++] = new PrioritizedMethodMetadata(m.getName(), s.priority());
}
}
if (injectMethods != null && !injectMethods.isEmpty()) {
this.injectMetadata = new InjectMetadata[injectMethods.size()];
this.dependencies = new HashMap<String, String>(injectMethods.size() * 2);
int j=0;
for (Method m : injectMethods) {
List<String> params = new LinkedList<String>();
InjectMetadata injectMetadata = new InjectMetadata(m.getName());
Class<?>[] parameterTypes = m.getParameterTypes();
// Add this to our dependencies map
Annotation[][] annotations = m.getParameterAnnotations();
for (int i=0; i<parameterTypes.length; i++) {
String componentName = findComponentName(annotations, i);
String parameterType = parameterTypes[i].getName();
params.add(parameterType);
if (componentName == null) {
dependencies.put(parameterType, parameterType);
} else {
injectMetadata.addParameterName(i, componentName);
dependencies.put(componentName, parameterType);
}
}
injectMetadata.parameters = params.toArray(new String[params.size()]);
this.injectMetadata[j++] = injectMetadata;
}
}
}
private String findComponentName(Annotation[][] annotations, int position) {
if (annotations != null && annotations.length > position) {
Annotation[] paramAnnotations = annotations[position];
if (paramAnnotations != null) {
for (Annotation a: paramAnnotations) {
if (a instanceof ComponentName) {
return ((ComponentName) a).value();
}
}
}
}
return null;
}
public String getName() {
return name;
}
public Map<String, String> getDependencies() {
return dependencies;
}
public InjectMetadata[] getInjectMethods() {
if (injectMetadata == null) return EMPTY_INJECT_METHODS;
return injectMetadata;
}
public PrioritizedMethodMetadata[] getStartMethods() {
if (startMethods == null) return EMPTY_PRIORITIZED_METHODS;
return startMethods;
}
public PrioritizedMethodMetadata[] getStopMethods() {
if (stopMethods == null) return EMPTY_PRIORITIZED_METHODS;
return stopMethods;
}
public boolean isGlobalScope() {
return globalScope;
}
public boolean isSurvivesRestarts() {
return survivesRestarts;
}
public boolean isManageable() {
return false;
}
public Class<?> getClazz() {
return clazz;
}
public ManageableComponentMetadata toManageableComponentMetadata() {
throw new UnsupportedOperationException("This component is not manageable!");
}
@Override
public String toString() {
return "ComponentMetadata{" +
"name='" + name + '\'' +
", dependencies=" + dependencies +
", injectMetadata=" + Arrays.toString(injectMetadata) +
", startMethods=" + Arrays.toString(startMethods) +
", stopMethods=" + Arrays.toString(stopMethods) +
", globalScope=" + globalScope +
", survivesRestarts=" + survivesRestarts +
'}';
}
/**
* This class encapsulates metadata on a prioritized method, such as one annotated with {@link Start} or {@link @Stop}
*/
public static class PrioritizedMethodMetadata implements Serializable {
String methodName;
transient Method method;
int priority;
public PrioritizedMethodMetadata(String methodName, int priority) {
this.methodName = methodName;
this.priority = priority;
}
public String getMethodName() {
return methodName;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public int getPriority() {
return priority;
}
}
/**
* This class encapsulates metadata on an inject method, such as one annotated with {@link Inject}
*/
public static class InjectMetadata implements Serializable {
//To avoid mismatches during development like as created by Maven vs IDE compiled classes:
private static final long serialVersionUID = 4848856551345751894L;
String methodName;
transient Method method;
String[] parameters;
transient Class<?>[] parameterClasses;
Map<Integer, String> parameterNames;
private InjectMetadata(String methodName) {
this.methodName = methodName;
}
public String getMethodName() {
return methodName;
}
public String[] getParameters() {
return parameters;
}
public String getParameterName(int subscript) {
String name = parameterNames == null ? null : parameterNames.get(subscript);
return name == null ? parameters[subscript] : name;
}
public boolean isParameterNameSet(int subscript) {
return parameterNames != null && parameterNames.containsKey(subscript);
}
void addParameterName(int subscript, String name) {
if (parameterNames == null) parameterNames = new HashMap<Integer, String>(1);
parameterNames.put(subscript, name);
}
public synchronized Method getMethod() {
return method;
}
public synchronized void setMethod(Method method) {
this.method = method;
}
public synchronized Class<?>[] getParameterClasses() {
return parameterClasses;
}
public synchronized void setParameterClasses(Class<?>[] parameterClasses) {
this.parameterClasses = parameterClasses;
}
}
}