http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/716e03b5/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ReflectionUtils.java ---------------------------------------------------------------------- diff --cc nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ReflectionUtils.java index 0000000,e15e00a..a8a4596 mode 000000,100644..100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ReflectionUtils.java +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ReflectionUtils.java @@@ -1,0 -1,157 +1,285 @@@ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.nifi.util; + + import java.lang.annotation.Annotation; + import java.lang.reflect.InvocationTargetException; + import java.lang.reflect.Method; ++import java.util.ArrayList; ++import java.util.List; ++ ++import org.apache.nifi.logging.ProcessorLog; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class ReflectionUtils { + + private final static Logger LOG = LoggerFactory.getLogger(ReflectionUtils.class); + + /** + * Invokes all methods on the given instance that have been annotated with + * the given Annotation. If the signature of the method that is defined in + * <code>instance</code> uses 1 or more parameters, those parameters must be + * specified by the <code>args</code> parameter. However, if more arguments + * are supplied by the <code>args</code> parameter than needed, the extra + * arguments will be ignored. + * + * @param annotation + * @param instance + * @param args + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public static void invokeMethodsWithAnnotation(final Class<? extends Annotation> annotation, final Object instance, final Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { - try { - for (final Method method : instance.getClass().getMethods()) { - if (method.isAnnotationPresent(annotation)) { - final boolean isAccessible = method.isAccessible(); - method.setAccessible(true); - - try { - final Class<?>[] argumentTypes = method.getParameterTypes(); - if (argumentTypes.length > args.length) { - throw new IllegalArgumentException(String.format("Unable to invoke method %1$s on %2$s because method expects %3$s parameters but only %4$s were given", - method.getName(), instance, argumentTypes.length, args.length)); - } ++ invokeMethodsWithAnnotation(annotation, null, instance, args); ++ } ++ + - for (int i = 0; i < argumentTypes.length; i++) { - final Class<?> argType = argumentTypes[i]; - if (!argType.isAssignableFrom(args[i].getClass())) { - throw new IllegalArgumentException(String.format( - "Unable to invoke method %1$s on %2$s because method parameter %3$s is expected to be of type %4$s but argument passed was of type %5$s", - method.getName(), instance, i, argType, args[i].getClass())); ++ /** ++ * Invokes all methods on the given instance that have been annotated with ++ * the given preferredAnnotation and if no such method exists will invoke all ++ * methods on the given instance that have been annotated with the given ++ * alternateAnnotation, if any exists. If the signature of the method that is defined in ++ * <code>instance</code> uses 1 or more parameters, those parameters must be ++ * specified by the <code>args</code> parameter. However, if more arguments ++ * are supplied by the <code>args</code> parameter than needed, the extra ++ * arguments will be ignored. ++ * ++ * @param preferredAnnotation ++ * @param alternateAnnotation ++ * @param instance ++ * @param args ++ * @throws InvocationTargetException ++ * @throws IllegalArgumentException ++ * @throws IllegalAccessException ++ */ ++ public static void invokeMethodsWithAnnotation(final Class<? extends Annotation> preferredAnnotation, final Class<? extends Annotation> alternateAnnotation, final Object instance, final Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { ++ final List<Class<? extends Annotation>> annotationClasses = new ArrayList<>(alternateAnnotation == null ? 1 : 2); ++ annotationClasses.add(preferredAnnotation); ++ if ( alternateAnnotation != null ) { ++ annotationClasses.add(alternateAnnotation); ++ } ++ ++ boolean annotationFound = false; ++ for ( final Class<? extends Annotation> annotationClass : annotationClasses ) { ++ if ( annotationFound ) { ++ break; ++ } ++ ++ try { ++ for (final Method method : instance.getClass().getMethods()) { ++ if (method.isAnnotationPresent(annotationClass)) { ++ annotationFound = true; ++ final boolean isAccessible = method.isAccessible(); ++ method.setAccessible(true); ++ ++ try { ++ final Class<?>[] argumentTypes = method.getParameterTypes(); ++ if (argumentTypes.length > args.length) { ++ throw new IllegalArgumentException(String.format("Unable to invoke method %1$s on %2$s because method expects %3$s parameters but only %4$s were given", ++ method.getName(), instance, argumentTypes.length, args.length)); + } - } - - if (argumentTypes.length == args.length) { - method.invoke(instance, args); - } else { - final Object[] argsToPass = new Object[argumentTypes.length]; - for (int i = 0; i < argsToPass.length; i++) { - argsToPass[i] = args[i]; ++ ++ for (int i = 0; i < argumentTypes.length; i++) { ++ final Class<?> argType = argumentTypes[i]; ++ if (!argType.isAssignableFrom(args[i].getClass())) { ++ throw new IllegalArgumentException(String.format( ++ "Unable to invoke method %1$s on %2$s because method parameter %3$s is expected to be of type %4$s but argument passed was of type %5$s", ++ method.getName(), instance, i, argType, args[i].getClass())); ++ } ++ } ++ ++ if (argumentTypes.length == args.length) { ++ method.invoke(instance, args); ++ } else { ++ final Object[] argsToPass = new Object[argumentTypes.length]; ++ for (int i = 0; i < argsToPass.length; i++) { ++ argsToPass[i] = args[i]; ++ } ++ ++ method.invoke(instance, argsToPass); ++ } ++ } finally { ++ if (!isAccessible) { ++ method.setAccessible(false); + } - - method.invoke(instance, argsToPass); - } - } finally { - if (!isAccessible) { - method.setAccessible(false); + } + } + } - } - } catch (final InvocationTargetException ite) { - if ( ite.getCause() instanceof RuntimeException ) { - throw (RuntimeException) ite.getCause(); - } else { - throw ite; ++ } catch (final InvocationTargetException ite) { ++ if ( ite.getCause() instanceof RuntimeException ) { ++ throw (RuntimeException) ite.getCause(); ++ } else { ++ throw ite; ++ } + } + } + } + ++ + /** + * Invokes all methods on the given instance that have been annotated with + * the given Annotation. If the signature of the method that is defined in + * <code>instance</code> uses 1 or more parameters, those parameters must be + * specified by the <code>args</code> parameter. However, if more arguments + * are supplied by the <code>args</code> parameter than needed, the extra + * arguments will be ignored. + * + * @param annotation + * @param instance + * @param args + * @return <code>true</code> if all appropriate methods were invoked and + * returned without throwing an Exception, <code>false</code> if one of the + * methods threw an Exception or could not be invoked; if <code>false</code> + * is returned, an error will have been logged. + */ + public static boolean quietlyInvokeMethodsWithAnnotation(final Class<? extends Annotation> annotation, final Object instance, final Object... args) { - for (final Method method : instance.getClass().getMethods()) { - if (method.isAnnotationPresent(annotation)) { - final boolean isAccessible = method.isAccessible(); - method.setAccessible(true); - - try { - final Class<?>[] argumentTypes = method.getParameterTypes(); - if (argumentTypes.length > args.length) { - LOG.error("Unable to invoke method {} on {} because method expects {} parameters but only {} were given", - new Object[]{method.getName(), instance, argumentTypes.length, args.length}); - return false; - } - - for (int i = 0; i < argumentTypes.length; i++) { - final Class<?> argType = argumentTypes[i]; - if (!argType.isAssignableFrom(args[i].getClass())) { - LOG.error("Unable to invoke method {} on {} because method parameter {} is expected to be of type {} but argument passed was of type {}", - new Object[]{method.getName(), instance, i, argType, args[i].getClass()}); ++ return quietlyInvokeMethodsWithAnnotation(annotation, null, instance, null, args); ++ } ++ ++ ++ /** ++ * Invokes all methods on the given instance that have been annotated with ++ * the given Annotation. If the signature of the method that is defined in ++ * <code>instance</code> uses 1 or more parameters, those parameters must be ++ * specified by the <code>args</code> parameter. However, if more arguments ++ * are supplied by the <code>args</code> parameter than needed, the extra ++ * arguments will be ignored. ++ * ++ * @param annotation ++ * @param instance ++ * @param args ++ * @return <code>true</code> if all appropriate methods were invoked and ++ * returned without throwing an Exception, <code>false</code> if one of the ++ * methods threw an Exception or could not be invoked; if <code>false</code> ++ * is returned, an error will have been logged. ++ */ ++ public static boolean quietlyInvokeMethodsWithAnnotation(final Class<? extends Annotation> annotation, final Object instance, final ProcessorLog logger, final Object... args) { ++ return quietlyInvokeMethodsWithAnnotation(annotation, null, instance, logger, args); ++ } ++ ++ ++ /** ++ * Invokes all methods on the given instance that have been annotated with ++ * the given preferredAnnotation and if no such method exists will invoke all methods ++ * on the given instance that have been annotated with the given ++ * alternateAnnotation, if any exists. If the signature of the method that is defined in ++ * <code>instance</code> uses 1 or more parameters, those parameters must be ++ * specified by the <code>args</code> parameter. However, if more arguments ++ * are supplied by the <code>args</code> parameter than needed, the extra ++ * arguments will be ignored. ++ * ++ * @param preferredAnnotation ++ * @param alternateAnnotation ++ * @param instance ++ * @param logger the ProcessorLog to use for logging any errors. If null, will use own logger, but that will not generate bulletins ++ * or easily tie to the Processor's log messages. ++ * @param args ++ * @return <code>true</code> if all appropriate methods were invoked and ++ * returned without throwing an Exception, <code>false</code> if one of the ++ * methods threw an Exception or could not be invoked; if <code>false</code> ++ * is returned, an error will have been logged. ++ */ ++ public static boolean quietlyInvokeMethodsWithAnnotation(final Class<? extends Annotation> preferredAnnotation, final Class<? extends Annotation> alternateAnnotation, final Object instance, final ProcessorLog logger, final Object... args) { ++ final List<Class<? extends Annotation>> annotationClasses = new ArrayList<>(alternateAnnotation == null ? 1 : 2); ++ annotationClasses.add(preferredAnnotation); ++ if ( alternateAnnotation != null ) { ++ annotationClasses.add(alternateAnnotation); ++ } ++ ++ boolean annotationFound = false; ++ for ( final Class<? extends Annotation> annotationClass : annotationClasses ) { ++ if ( annotationFound ) { ++ break; ++ } ++ ++ for (final Method method : instance.getClass().getMethods()) { ++ if (method.isAnnotationPresent(annotationClass)) { ++ annotationFound = true; ++ ++ final boolean isAccessible = method.isAccessible(); ++ method.setAccessible(true); ++ ++ try { ++ final Class<?>[] argumentTypes = method.getParameterTypes(); ++ if (argumentTypes.length > args.length) { ++ if ( logger == null ) { ++ LOG.error("Unable to invoke method {} on {} because method expects {} parameters but only {} were given", ++ new Object[]{method.getName(), instance, argumentTypes.length, args.length}); ++ } else { ++ logger.error("Unable to invoke method {} on {} because method expects {} parameters but only {} were given", ++ new Object[]{method.getName(), instance, argumentTypes.length, args.length}); ++ } ++ + return false; + } - } - - try { - if (argumentTypes.length == args.length) { - method.invoke(instance, args); - } else { - final Object[] argsToPass = new Object[argumentTypes.length]; - for (int i = 0; i < argsToPass.length; i++) { - argsToPass[i] = args[i]; ++ ++ for (int i = 0; i < argumentTypes.length; i++) { ++ final Class<?> argType = argumentTypes[i]; ++ if (!argType.isAssignableFrom(args[i].getClass())) { ++ if ( logger == null ) { ++ LOG.error("Unable to invoke method {} on {} because method parameter {} is expected to be of type {} but argument passed was of type {}", ++ new Object[]{method.getName(), instance, i, argType, args[i].getClass()}); ++ } else { ++ logger.error("Unable to invoke method {} on {} because method parameter {} is expected to be of type {} but argument passed was of type {}", ++ new Object[]{method.getName(), instance, i, argType, args[i].getClass()}); ++ } ++ ++ return false; + } - - method.invoke(instance, argsToPass); + } - } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException t) { - LOG.error("Unable to invoke method {} on {} due to {}", new Object[]{method.getName(), instance, t}); - LOG.error("", t); - return false; - } - } finally { - if (!isAccessible) { - method.setAccessible(false); ++ ++ try { ++ if (argumentTypes.length == args.length) { ++ method.invoke(instance, args); ++ } else { ++ final Object[] argsToPass = new Object[argumentTypes.length]; ++ for (int i = 0; i < argsToPass.length; i++) { ++ argsToPass[i] = args[i]; ++ } ++ ++ method.invoke(instance, argsToPass); ++ } ++ } catch (final InvocationTargetException ite) { ++ if ( logger == null ) { ++ LOG.error("Unable to invoke method {} on {} due to {}", new Object[]{method.getName(), instance, ite.getCause()}); ++ LOG.error("", ite.getCause()); ++ } else { ++ logger.error("Unable to invoke method {} on {} due to {}", new Object[]{method.getName(), instance, ite.getCause()}); ++ } ++ } catch (final IllegalAccessException | IllegalArgumentException t) { ++ if ( logger == null ) { ++ LOG.error("Unable to invoke method {} on {} due to {}", new Object[]{method.getName(), instance, t}); ++ LOG.error("", t); ++ } else { ++ logger.error("Unable to invoke method {} on {} due to {}", new Object[]{method.getName(), instance, t}); ++ } ++ ++ return false; ++ } ++ } finally { ++ if (!isAccessible) { ++ method.setAccessible(false); ++ } + } + } + } + } + return true; + } + }
http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/716e03b5/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/StubAttributeLoggerProcessor.java ---------------------------------------------------------------------- diff --cc nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/StubAttributeLoggerProcessor.java index 0000000,73d38e8..d49db29 mode 000000,100644..100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/StubAttributeLoggerProcessor.java +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/StubAttributeLoggerProcessor.java @@@ -1,0 -1,111 +1,111 @@@ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.nifi.test.processors; + + import java.util.ArrayList; + import java.util.Collections; + import java.util.HashSet; + import java.util.List; + import java.util.Set; + ++import org.apache.nifi.annotation.behavior.SideEffectFree; + import org.apache.nifi.components.PropertyDescriptor; + import org.apache.nifi.processor.AbstractProcessor; + import org.apache.nifi.processor.ProcessContext; + import org.apache.nifi.processor.ProcessSession; + import org.apache.nifi.processor.ProcessorInitializationContext; + import org.apache.nifi.processor.Relationship; -import org.apache.nifi.processor.annotation.SideEffectFree; + + @SideEffectFree + public class StubAttributeLoggerProcessor extends AbstractProcessor { + + //@formatter:off + public static final PropertyDescriptor LOG_LEVEL = new PropertyDescriptor.Builder() + .required(false) + .description("log.level") + .defaultValue("debug") + .name("log.level") + .allowableValues("trace", "info", "warn", "debug", "error") + .sensitive(false) + .build(); + public static final PropertyDescriptor ATTRIBUTES_TO_LOG_CSV = new PropertyDescriptor.Builder() + .required(false) + .description("attributes.to.log.csv") + .name("attributes.to.log.csv") + .sensitive(false) + .build(); + public static final PropertyDescriptor ATTRIBUTES_TO_IGNORE_CSV = new PropertyDescriptor.Builder() + .required(false) + .description("attributes.to.ignore.csv") + .name("attributes.to.ignore.csv") + .sensitive(false) + .build(); + public static final PropertyDescriptor LOG_PAYLOAD = new PropertyDescriptor.Builder() + .required(false) + .description("log.payload") + .defaultValue("false") + .allowableValues("true", "false") + .name("log.payload") + .sensitive(false) + .build(); + // @formatter:on + + public static enum DebugLevels { + + trace, info, warn, debug, error + } + + public static final long ONE_MB = 1024 * 1024; + private final Set<Relationship> relationships; + public static final Relationship REL_SUCCESS = new Relationship.Builder().description("success").name("success").build(); + + private final List<PropertyDescriptor> supportedDescriptors; + + public StubAttributeLoggerProcessor() { + // relationships + final Set<Relationship> procRels = new HashSet<>(); + procRels.add(REL_SUCCESS); + relationships = Collections.unmodifiableSet(procRels); + + // descriptors + final List<PropertyDescriptor> supDescriptors = new ArrayList<>(); + supDescriptors.add(ATTRIBUTES_TO_IGNORE_CSV); + supDescriptors.add(ATTRIBUTES_TO_LOG_CSV); + supDescriptors.add(LOG_PAYLOAD); + supDescriptors.add(LOG_LEVEL); + supportedDescriptors = Collections.unmodifiableList(supDescriptors); + } + + @Override + public Set<Relationship> getRelationships() { + return relationships; + } + + @Override + protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { + return supportedDescriptors; + } + + @Override + public void onTrigger(final ProcessContext context, final ProcessSession session) { + } + + @Override + protected void init(final ProcessorInitializationContext context) { + } + + }
