001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * bstefanescu 011 */ 012package org.nuxeo.ecm.automation.core.impl; 013 014import java.lang.reflect.Method; 015import java.lang.reflect.Type; 016import java.util.Iterator; 017import java.util.Map; 018 019import org.nuxeo.ecm.automation.OperationContext; 020import org.nuxeo.ecm.automation.OperationException; 021import org.nuxeo.ecm.automation.OperationType; 022import org.nuxeo.ecm.automation.OutputCollector; 023import org.nuxeo.ecm.automation.core.annotations.OperationMethod; 024 025/** 026 * A method proxy which accept as input only iterable inputs. At invocation time it iterates over the input elements and 027 * invoke the real method using the current input element as the input. 028 * <p> 029 * The result is collected into a {@link OutputCollector} as specified by the {@link OperationMethod} annotation. 030 * <p> 031 * This proxy is used (instead of the default {@link InvokableMethod}) when an operation is defining an output collector 032 * in the {@link OperationMethod} annotation so that iterable inputs are automatically handled by the chain executor. 033 * <p> 034 * This specialized implementation is declaring the same consume type as its non-iterable counterpart. But at runtime it 035 * consumes any Iterable of the cosume type. 036 * <p> 037 * To correctly generate the operation documentation the {@link OperationTypeImpl} is checking if the method is iterable 038 * or not through {@link #isIterable()} to declare the correct consume type. 039 * 040 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 041 */ 042public class InvokableIteratorMethod extends InvokableMethod { 043 044 @SuppressWarnings("rawtypes") 045 protected Class<? extends OutputCollector> collector; 046 047 public InvokableIteratorMethod(OperationType op, Method method, OperationMethod anno) { 048 super(op, method, anno); 049 collector = anno.collector(); 050 if (collector == OutputCollector.class) { 051 throw new IllegalArgumentException("Not an iterable method"); 052 } 053 // check the collector match the method signature - to early detect invalid 054 // operation definitions. 055 if (consume == Void.TYPE) { 056 throw new IllegalArgumentException("An iterable method must have an argument"); 057 } 058 Type[] ctypes = IterableInputHelper.findCollectorTypes(collector); 059 if (!((Class<?>) ctypes[0]).isAssignableFrom(produce)) { 060 throw new IllegalArgumentException("The collector used on " + method 061 + " doesn't match the method return type"); 062 } 063 // must modify the produced type to fit the real produced type. 064 try { 065 produce = (Class<?>) ctypes[1]; 066 } catch (ArrayIndexOutOfBoundsException e) { 067 throw new IllegalStateException("Invalid output collector: " + collector + ". No getOutput method found."); 068 } 069 // the consumed type is not used in chain compilation so we let it as is 070 // for now. 071 } 072 073 @Override 074 public boolean isIterable() { 075 return true; 076 } 077 078 @Override 079 public int inputMatch(Class<?> in) { 080 Class<?> iterableIn = IterableInputHelper.getIterableType(in); 081 if (iterableIn != null) { 082 return super.inputMatch(iterableIn); 083 } 084 return 0; 085 } 086 087 @SuppressWarnings({ "rawtypes", "unchecked" }) 088 @Override 089 protected Object doInvoke(OperationContext ctx, Map<String, Object> args, Object input) throws OperationException, 090 ReflectiveOperationException { 091 if (!(input instanceof Iterable)) { 092 throw new IllegalStateException("An iterable method was called in a non iterable context"); 093 } 094 OutputCollector list = collector.newInstance(); 095 Iterable<?> iterable = (Iterable<?>) input; 096 Iterator<?> it = iterable.iterator(); 097 while (it.hasNext()) { 098 Object in = it.next(); 099 // update context to use as input the current entry 100 ctx.setInput(in); 101 list.collect(ctx, super.doInvoke(ctx, args, in)); 102 } 103 return list.getOutput(); 104 } 105 106}