package rocks.inspectit.agent.java.config.impl; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.springframework.stereotype.Component; import rocks.inspectit.agent.java.config.IPropertyAccessor; import rocks.inspectit.agent.java.config.PropertyAccessException; import rocks.inspectit.shared.all.communication.data.ParameterContentData; import rocks.inspectit.shared.all.instrumentation.config.impl.PropertyPath; import rocks.inspectit.shared.all.instrumentation.config.impl.PropertyPathStart; import rocks.inspectit.shared.all.spring.logger.Log; /** * This class is used to programmatically build the path to access a specific method parameter or a * field of a class. * * @author Patrice Bouillet * @author Stefan Siegl * */ @Component public class PropertyAccessor implements IPropertyAccessor { /** * The logger of this class. */ @Log Logger log; /** * Static null value for return value capturing in case the returned value was <code>null</code> * . */ private static final String NULL_VALUE = "null"; /** * An array containing the names of all methods that might be called by the PropertyAccessor. * Names should not include the brackets. */ private static final String[] ALLOWED_METHODS = new String[] { "size", "length" }; /** * {@inheritDoc} */ @Override public String getPropertyContent(PropertyPathStart propertyPathStart, Object clazz, Object[] parameters, Object returnValue) throws PropertyAccessException { if (null == propertyPathStart) { throw new PropertyAccessException("Property path start cannot be null!"); } if (null == propertyPathStart.getContentType()) { throw new PropertyAccessException("Content type is not defined."); } switch (propertyPathStart.getContentType()) { case FIELD: if (null == clazz) { throw new PropertyAccessException("Class reference cannot be null!"); } return getPropertyContent(propertyPathStart.getPathToContinue(), clazz); case PARAM: if (null == parameters) { throw new PropertyAccessException("Parameter array reference cannot be null!"); } if (propertyPathStart.getSignaturePosition() >= parameters.length) { throw new PropertyAccessException("Signature position out of range!"); } return getPropertyContent(propertyPathStart.getPathToContinue(), parameters[propertyPathStart.getSignaturePosition()]); case RETURN: // we will not throw an exception here as the return value of a method can sometimes be // null. If we throw an exception, this will lead to the removal of the path and thus no // return value of this property accessor will be captured afterwards. if (null == returnValue) { return NULL_VALUE; } else { return getPropertyContent(propertyPathStart.getPathToContinue(), returnValue); } default: throw new PropertyAccessException("Missing handler for type " + propertyPathStart.getContentType()); } } /** * Checks whether or not the method may be called within the parameter storage algorithm. * * @param method * The method name to check for. * @return <code>true</code> if the method is accepted. */ private boolean isAcceptedMethod(String method) { for (String allowed : ALLOWED_METHODS) { if (allowed.equals(method)) { return true; } } return false; } /** * Inner static recursive method to go along the given path. * * @see PropertyPath * * @param propertyPath * The path to follow. * @param object * The object to analyze. * @return The {@link String} representation of the field or parameter followed by the path. * @throws PropertyAccessException * This exception is thrown whenever something unexpectedly happens while accessing * a property. */ private String getPropertyContent(PropertyPath propertyPath, Object object) throws PropertyAccessException { if (null == object) { return "null"; } if (null == propertyPath) { // end of the path to follow, return the String representation of // the object return object.toString(); } Class<?> c; if (object instanceof Class) { // This check is needed when a static class is passed to this // method. c = (Class<?>) object; } else { c = object.getClass(); } // We need to differ between calls of methods and the navigation of // properties of an object. This differentiation is integrated to // force the user to add () to the method to be called, thus the // user is aware what he is doing and no unwanted method calls are // performed. if (propertyPath.isMethodCall()) { // strip the "()" from the path to find the method String methodName = propertyPath.getName().substring(0, propertyPath.getName().length() - 2); // check if this method may be called if (!isAcceptedMethod(methodName)) { throw new PropertyAccessException("Method " + methodName + " MAY not be called!"); } // special handling for the length method of Array objects // Array objects do not inherit from the static Array class, thus // trying to retrieve the method by reflection is not possible if ("length".equals(methodName)) { if (object.getClass().isArray()) { // ensure that we are really // dealing with an array return getPropertyContent(propertyPath.getPathToContinue(), Integer.valueOf(Array.getLength(object))); } else { log.error("Trying to access the lenght() method for a non array type"); throw new PropertyAccessException("Trying to access the length() method for a non array type"); } } do { // we are iterating using getDeclaredMethods as this call will // also provide the default access and protected methods which // the // call to getMethods() will not Method[] methods = c.getDeclaredMethods(); for (Method method : methods) { if (methodName.equals(method.getName())) { // We are only calling methods that do not take an // argument if (method.getParameterTypes().length != 0) { if (log.isDebugEnabled()) { log.debug("Skipping matching method " + method.getName() + " as it is not a no argument method"); } continue; } try { Object result = method.invoke(object, (Object[]) null); return getPropertyContent(propertyPath.getPathToContinue(), result); } catch (IllegalArgumentException e) { log.error(e.getMessage()); throw new PropertyAccessException("Illegal Argument Exception!", e); } catch (IllegalAccessException e) { log.error(e.getMessage()); throw new PropertyAccessException("IllegalAccessException!", e); } catch (InvocationTargetException e) { log.error(e.getMessage()); throw new PropertyAccessException("InvocationTargetException!", e); } } } c = c.getSuperclass(); } while (c != Object.class); } else { // We are dealing with a property navigation and not an method // call do { Field[] fields = c.getDeclaredFields(); for (Field field : fields) { if (propertyPath.getName().equals(field.getName())) { try { field.setAccessible(true); Object fieldObject = field.get(object); return getPropertyContent(propertyPath.getPathToContinue(), fieldObject); } catch (SecurityException e) { log.error(e.getMessage()); throw new PropertyAccessException("Security Exception was thrown while accessing a field!", e); } catch (IllegalArgumentException e) { log.error(e.getMessage()); throw new PropertyAccessException("Illegal Argument Exception!", e); } catch (IllegalAccessException e) { log.error(e.getMessage()); throw new PropertyAccessException("Illegal Access Exception!", e); } } } c = c.getSuperclass(); } while (c != Object.class); } throw new PropertyAccessException("Property or method " + propertyPath.getName() + " cannot be found in class " + object.getClass() + "!"); } /** * {@inheritDoc} */ @Override public List<ParameterContentData> getParameterContentData(List<PropertyPathStart> propertyAccessorList, Object clazz, Object[] parameters, Object returnValue) { List<ParameterContentData> parameterContentData = new ArrayList<ParameterContentData>(); for (PropertyPathStart start : propertyAccessorList) { try { String content = this.getPropertyContent(start, clazz, parameters, returnValue); ParameterContentData paramContentData = new ParameterContentData(); paramContentData.setContent(content); paramContentData.setContentType(start.getContentType()); paramContentData.setName(start.getName()); paramContentData.setSignaturePosition(start.getSignaturePosition()); parameterContentData.add(paramContentData); } catch (PropertyAccessException e) { if (log.isErrorEnabled()) { log.error("Cannot access the property: " + start + " for class " + clazz + ". Will be removed from the list to prevent further errors! (" + e.getMessage() + ")"); } propertyAccessorList.remove(start); // iterator.remove(); // Unsupported exception. Iterator can't make changes, since // iterating over a snapshot. } } return parameterContentData; } }