package rocks.inspectit.server.diagnosis.engine.rule; import static com.google.common.base.Preconditions.checkNotNull; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Collections; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import rocks.inspectit.server.diagnosis.engine.rule.annotation.Action; import rocks.inspectit.server.diagnosis.engine.rule.exception.RuleDefinitionException; import rocks.inspectit.server.diagnosis.engine.rule.exception.RuleExecutionException; import rocks.inspectit.server.diagnosis.engine.tag.Tag; import rocks.inspectit.server.diagnosis.engine.tag.Tags; import rocks.inspectit.server.diagnosis.engine.util.ReflectionUtils; /** * Represents the action method of a rule. An <code>ActionMethod</code> reflects the {@link Action} * annotation. * * @author Claudio Waldvogel, Alexander Wert * @see Action */ public class ActionMethod { /** * The {@link Method} to be invoked. */ private final Method method; /** * The tag type this action produces. * * @see Action#resultTag() */ private final String resultTag; /** * The output Quantity. * * @see Action#resultQuantity() */ private final Action.Quantity resultQuantity; /** * Default Constructor. * * @param method * The method to be invoked * @param resultTag * The type of tags this action produces * @param resultQuantity * The result Quantity * @throws RuleDefinitionException * If the {@link ActionMethod} is invalid. The action method must be public, with * zero arguments and void return type. */ public ActionMethod(Method method, String resultTag, Action.Quantity resultQuantity) throws RuleDefinitionException { this.method = checkNotNull(method, "The method must not be null."); this.resultTag = checkNotNull(resultTag, "The result tag must not be null."); this.resultQuantity = checkNotNull(resultQuantity, "The output quantity must not be null."); validate(); } // ------------------------------------------------------------- // Methods: RuleExecution // ------------------------------------------------------------- /** * Executes the action. Throws: RuleExecutionException in case of any error * * @param context * The current executing {@link ExecutionContext} * @return A collection of {@link Tags}s * @throws RuleExecutionException * If rule execution fails with an exception. * @see ExecutionContext * @see Tag */ public Collection<Tag> execute(ExecutionContext context) throws RuleExecutionException { try { Object result = ReflectionUtils.invokeMethod(getMethod(), context.getInstance()); return transform(result, context); } catch (Exception e) { throw new RuleExecutionException("Failed to invoke action method (" + getMethod().getName() + ")", context, e); } } /** * Validates the properties of the {@link ActionMethod}. * * @throws RuleDefinitionException * If the {@link ActionMethod} is invalid. The action method must be public, with * zero arguments and void return type. */ private void validate() throws RuleDefinitionException { boolean valid = Modifier.isPublic(method.getModifiers()); Class<?> returnType = method.getReturnType(); valid = valid && !returnType.equals(Void.class); valid = valid && (method.getParameterTypes().length == 0); if (!valid) { String msg = method.getDeclaringClass().getName() + " defines an invalid action method with name: " + method.getName(); msg += "\nValid action methods are public with a non void return type and zero arguments (e.g. public" + " String action())"; throw new RuleDefinitionException(msg); } // ensure proper return type in case of MULTIPLE outputQuantity if (Action.Quantity.MULTIPLE.equals(resultQuantity)) { if (!returnType.isArray() && !Iterable.class.isAssignableFrom(returnType)) { throw new RuleDefinitionException(method.getDeclaringClass().getName() + " defines an MULTIPLE outputQuantity, but return type is neither Array nor Collection."); } } } /** * Transforms the result of a rule to a collection of {@link Tag}s. How the result is * transformed is controlled by the #resultQuantity property. * * @param result * The result to be transformed * @param context * The {@link ExecutionContext} enclosing this execution * @return A collection of {@link Tag}s * @throws RuleExecutionException * If rule return type does not match. * @see Tag * @see ExecutionContext */ private Collection<Tag> transform(Object result, ExecutionContext context) throws RuleExecutionException { if (result == null) { return Collections.emptyList(); } else { Collection<Tag> transformed = Lists.newArrayList(); switch (getResultQuantity()) { case MULTIPLE: Object[] values; if (result.getClass().isArray()) { values = getObjectArray(result); } else if (result instanceof Iterable<?>) { values = Iterables.toArray((Iterable<?>) result, Object.class); } else { throw new RuleExecutionException("If resultQuantity is MULTIPLE ensure that either an Array or a Collection is defined as return value", context); } transformed.addAll(Tags.tags(getResultTag(), context.getRuleInput().getRoot(), values)); break; case SINGLE: default: transformed.add(Tags.tag(getResultTag(), result, context.getRuleInput().getRoot())); } return transformed; } } /** * Converts the result to an Object array depending on the component type of the array. * * @param result * Object representing the result array. * @return Object array */ private Object[] getObjectArray(Object result) { if (result.getClass().getComponentType().isPrimitive()) { int length = Array.getLength(result); Object[] array = new Object[length]; for (int i = 0; i < length; ++i) { array[i] = Array.get(result, i); } return array; } else { return (Object[]) result; } } // -------------------------------------------------------------u // Methods: Accessors // ------------------------------------------------------------- /** * Gets {@link #method}. * * @return {@link #method} */ public Method getMethod() { return method; } /** * Gets {@link #resultTag}. * * @return {@link #resultTag} */ public String getResultTag() { return resultTag; } /** * Gets {@link #resultQuantity}. * * @return {@link #resultQuantity} */ public Action.Quantity getResultQuantity() { return resultQuantity; } // ------------------------------------------------------------- // Methods: Generated // ------------------------------------------------------------- /** * {@inheritDoc} */ @Override public String toString() { return "ActionMethod{" + "method=" + method + ", resultTag='" + resultTag + '\'' + ", resultQuantity=" + resultQuantity + '}'; } /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = (prime * result) + ((this.method == null) ? 0 : this.method.hashCode()); result = (prime * result) + ((this.resultQuantity == null) ? 0 : this.resultQuantity.hashCode()); result = (prime * result) + ((this.resultTag == null) ? 0 : this.resultTag.hashCode()); return result; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ActionMethod other = (ActionMethod) obj; if (this.method == null) { if (other.method != null) { return false; } } else if (!this.method.equals(other.method)) { return false; } if (this.resultQuantity != other.resultQuantity) { return false; } if (this.resultTag == null) { if (other.resultTag != null) { return false; } } else if (!this.resultTag.equals(other.resultTag)) { return false; } return true; } }