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