/* * Copyright © 2014 Cask Data, Inc. * * Licensed 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 co.cask.cdap.internal.app.runtime.flow; import co.cask.cdap.api.annotation.Batch; import co.cask.cdap.api.flow.flowlet.Flowlet; import co.cask.cdap.api.flow.flowlet.InputContext; import co.cask.cdap.app.queue.InputDatum; import com.google.common.base.Preconditions; import com.google.common.reflect.TypeToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; import java.util.Iterator; import javax.annotation.concurrent.NotThreadSafe; /** * Represents a {@link ProcessMethod} that invocation is done through reflection. * @param <T> Type of input accepted by this process method. */ @NotThreadSafe public final class ReflectionProcessMethod<T> implements ProcessMethod<T> { private static final Logger LOG = LoggerFactory.getLogger(ReflectionProcessMethod.class); private final Flowlet flowlet; private final Method method; private final boolean hasParam; private final boolean batch; private final boolean needsIterator; private final boolean needContext; private final int maxRetries; public static <T> ReflectionProcessMethod<T> create(Flowlet flowlet, Method method, int maxRetries) { return new ReflectionProcessMethod<>(flowlet, method, maxRetries); } private ReflectionProcessMethod(Flowlet flowlet, Method method, int maxRetries) { this.flowlet = flowlet; this.method = method; this.maxRetries = maxRetries; this.hasParam = method.getGenericParameterTypes().length > 0; this.batch = method.isAnnotationPresent(Batch.class); this.needsIterator = hasParam && TypeToken.of(method.getGenericParameterTypes()[0]).getRawType().equals(Iterator.class); this.needContext = method.getGenericParameterTypes().length == 2; if (!this.method.isAccessible()) { this.method.setAccessible(true); } } @Override public boolean needsInput() { return hasParam; } @Override public int getMaxRetries() { return maxRetries; } @SuppressWarnings("unchecked") @Override public ProcessResult<T> invoke(InputDatum<T> input) { try { Preconditions.checkState(!hasParam || input.needProcess(), "Empty input provided to method that needs input."); InputContext inputContext = input.getInputContext(); if (hasParam) { if (needsIterator) { invoke(method, input.iterator(), inputContext); } else { for (T event : input) { invoke(method, event, inputContext); } } } else { method.invoke(flowlet); } return createResult(input, null); } catch (Throwable t) { return createResult(input, t.getCause()); } } @Override public String toString() { return flowlet.getClass() + "." + method.toString(); } /** * Calls the user process method. */ private void invoke(Method method, Object event, InputContext inputContext) throws Exception { if (needContext) { method.invoke(flowlet, event, inputContext); } else { method.invoke(flowlet, event); } } @SuppressWarnings("unchecked") private ProcessResult<T> createResult(InputDatum<T> input, Throwable failureCause) { // If the method has param, then object for the result would be iterator or the first event (batch vs no-batch) T event = hasParam ? (batch ? (T) input.iterator() : input.iterator().next()) : null; return new ReflectionProcessResult<>(event, failureCause); } private static final class ReflectionProcessResult<V> implements ProcessResult<V> { private final V event; private final Throwable cause; private ReflectionProcessResult(V event, Throwable cause) { this.event = event; this.cause = cause; } @Override public V getEvent() { return event; } @Override public boolean isSuccess() { return cause == null; } @Override public Throwable getCause() { return cause; } } }