/*
* Copyright (C) 2003-2011 eXo Platform SAS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.etk.kernel.container.management;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.etk.common.utils.AnnotationIntrospector;
import org.etk.kernel.management.annotations.Impact;
import org.etk.kernel.management.annotations.ImpactType;
import org.etk.kernel.management.annotations.Managed;
import org.etk.kernel.management.annotations.ManagedDescription;
import org.etk.kernel.management.annotations.ManagedName;
import org.etk.kernel.management.spi.ManagedMethodMetaData;
import org.etk.kernel.management.spi.ManagedMethodParameterMetaData;
import org.etk.kernel.management.spi.ManagedParameterMetaData;
import org.etk.kernel.management.spi.ManagedPropertyMetaData;
import org.etk.kernel.management.spi.ManagedTypeMetaData;
/**
* Created by The eXo Platform SAS
* Author : eXoPlatform
* exo@exoplatform.com
* Jul 28, 2011
*/
/**
* <p>A class that build the management view meta data of a specified class.</p>
* <p>The following rules do apply to the class from which meta data are constructed:
* <ul>
* <li>The class must be annotated by {@link org.exoplatform.management.annotations.Managed}</li>
* <li>The class may be annoated by {@link org.exoplatform.management.annotations.ManagedDescription}</li>
* <li>Any property described by its getter and/or setter getter annotated by {@link org.exoplatform.management.annotations.Managed} is exposed as an attribute/li>
* <li>Any property providing an annotated getter is readable</li>
* <li>Any property providing an annotated setter is writable</li>
* <li>Any getter/setter annotated by {@link org.exoplatform.management.annotations.ManagedName} redefines the attribute name</li>
* <li>Any getter/setter annotated by {@link org.exoplatform.management.annotations.ManagedDescription} defines the attribute description</li>
* <li>When corresponding getter/setter redefines the attribute name, the value must be the same otherwhise
* an exception is thrown at built time</li>
* <li>Any method annotated by {@link org.exoplatform.management.annotations.Managed} is exposed as a management operation</li>
* <li>Any method annotated by {@link org.exoplatform.management.annotations.ManagedDescription} defines the operation description</li>
* <li>Any non setter/getter method annotated by {@link org.exoplatform.management.annotations.ManagedName} causes a built time exception</li>
* <li>Any method argument annotated by {@link org.exoplatform.management.annotations.ManagedName} defines the management name of the corresponding operation parameter</li>
* <li>Any method argument annotated by {@link org.exoplatform.management.annotations.ManagedDescription} defines the management description of the corresponding operation parameter</li>
* </ul>
* </p>
*
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public class MetaDataBuilder
{
private static enum OperationType {
SET, GET, OP
}
private Class clazz;
private boolean buildable;
/**
* Create a new builder.
*
* @param clazz the clazz
* @throws IllegalArgumentException if the class is null or is not annotated by {@link org.exoplatform.management.annotations.Managed}
*/
public MetaDataBuilder(Class clazz) throws IllegalArgumentException
{
if (clazz == null)
{
throw new NullPointerException();
}
//
Managed mb = AnnotationIntrospector.resolveClassAnnotations(clazz, Managed.class);
//
this.clazz = clazz;
this.buildable = mb != null;
}
public boolean isBuildable()
{
return buildable;
}
/**
* Build the info.
*
* @return returns the info
* @throws IllegalStateException raised by any build time issue
*/
public ManagedTypeMetaData build() throws IllegalStateException
{
if (!buildable)
{
throw new IllegalStateException("Class " + clazz.getName() + " does not contain management annotation");
}
//
ManagedDescription typeDescriptionAnn =
AnnotationIntrospector.resolveClassAnnotations(clazz, ManagedDescription.class);
String typeDescription = typeDescriptionAnn != null ? typeDescriptionAnn.value() : null;
//
Map<Method, Managed> managedMethods = AnnotationIntrospector.resolveMethodAnnotations(clazz, Managed.class);
Map<Method, ManagedName> methodNames = AnnotationIntrospector.resolveMethodAnnotations(clazz, ManagedName.class);
Map<Method, ManagedDescription> methodDescriptions =
AnnotationIntrospector.resolveMethodAnnotations(clazz, ManagedDescription.class);
//
Map<Method, ManagedMethodMetaData> bilto = new HashMap<Method, ManagedMethodMetaData>();
for (Map.Entry<Method, Managed> entry : managedMethods.entrySet())
{
Method method = entry.getKey();
//
ManagedDescription methodDescriptionAnn = methodDescriptions.get(method);
String methodDescription = methodDescriptionAnn != null ? methodDescriptionAnn.value() : null;
//
Impact impactAnn = method.getAnnotation(Impact.class);
ImpactType impactType = impactAnn != null ? impactAnn.value() : ImpactType.WRITE;
// Build the default mbean info
ManagedMethodMetaData managedMethod = new ManagedMethodMetaData(method, impactType);
managedMethod.setDescription(methodDescription);
// Overload with annotations meta data
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++)
{
ManagedMethodParameterMetaData mmpMD = new ManagedMethodParameterMetaData(i);
for (Annotation parameterAnnotation : parameterAnnotations[i])
{
if (parameterAnnotation instanceof ManagedName)
{
mmpMD.setName(((ManagedName)parameterAnnotation).value());
}
else if (parameterAnnotation instanceof ManagedDescription)
{
mmpMD.setDescription(((ManagedDescription)parameterAnnotation).value());
}
}
managedMethod.addParameter(mmpMD);
}
//
bilto.put(method, managedMethod);
}
//
ManagedTypeMetaData managedType = new ManagedTypeMetaData(clazz);
managedType.setDescription(typeDescription);
//
Map<String, ManagedMethodMetaData> setters = new HashMap<String, ManagedMethodMetaData>();
Map<String, ManagedMethodMetaData> getters = new HashMap<String, ManagedMethodMetaData>();
for (Map.Entry<Method, ManagedMethodMetaData> entry : bilto.entrySet())
{
Method method = entry.getKey();
//
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
//
OperationType type = OperationType.OP;
Integer index = null;
if (method.getReturnType() == void.class)
{
if (parameterTypes.length == 1 && methodName.startsWith("set") && methodName.length() > 4)
{
type = OperationType.SET;
index = 3;
}
}
else
{
if (parameterTypes.length == 0)
{
type = OperationType.GET;
if (methodName.startsWith("get") && methodName.length() > 3)
{
index = 3;
}
else if (methodName.startsWith("is") && methodName.length() > 2)
{
index = 2;
}
}
}
// Put in the correct map if it is an attribute
if (index != null)
{
String attributeName = methodName.substring(index);
//
Map<String, ManagedMethodMetaData> map = type == OperationType.SET ? setters : getters;
ManagedMethodMetaData previous = map.put(attributeName, entry.getValue());
if (previous != null)
{
throw new IllegalArgumentException("Duplicate attribute " + type + " " + previous + " and " + method);
}
}
else
{
ManagedName managedName = methodNames.get(method);
if (managedName != null)
{
throw new IllegalArgumentException("Managed operation " + method.getName()
+ " cannot be annoated with @" + ManagedName.class.getName() + " with value " + managedName.value());
}
//
managedType.addMethod(entry.getValue());
}
}
// Process attributes
Set<String> attributeNames = new HashSet<String>();
attributeNames.addAll(getters.keySet());
attributeNames.addAll(setters.keySet());
for (String attributeName : attributeNames)
{
ManagedMethodMetaData managedGetter = getters.get(attributeName);
ManagedMethodMetaData managedSetter = setters.get(attributeName);
String propertyDescription = null;
ManagedName getterName = null;
ManagedName setterName = null;
String getterDescription = null;
String setterDescription = null;
Method getter = null;
Method setter = null;
ManagedParameterMetaData mpm = null;
if (managedGetter != null)
{
getter = managedGetter.getMethod();
getterName = methodNames.get(getter);
getterDescription = managedGetter.getDescription();
propertyDescription = getterDescription;
}
if (managedSetter != null)
{
setter = managedSetter.getMethod();
setterName = methodNames.get(setter);
setterDescription = managedSetter.getDescription();
if (propertyDescription == null)
{
propertyDescription = setterDescription;
}
mpm = managedSetter.getParameters().iterator().next();
}
// Consistency check
if (getterName != null)
{
if (setterName != null)
{
if (!getterName.value().equals(setterName.value()))
{
throw new IllegalArgumentException("Getter name=" + getterName.value()
+ " does not match the setter name=" + setterName.value());
}
}
attributeName = getterName.value();
}
else if (setterName != null)
{
attributeName = setterName.value();
}
//
ManagedPropertyMetaData managedProperty =
new ManagedPropertyMetaData(attributeName, getter, getterDescription, setter, setterDescription, mpm);
managedProperty.setDescription(propertyDescription);
//
ManagedPropertyMetaData previousManagedProperty = managedType.getProperty(managedProperty.getName());
if (previousManagedProperty != null)
{
throw new IllegalArgumentException("The same property was declared twice old=" + previousManagedProperty
+ " new=" + managedProperty);
}
//
managedType.addProperty(managedProperty);
}
//
return managedType;
}
}