001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.pipeline.stage;
019    
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    
023    import org.apache.commons.pipeline.StageException;
024    
025    /**
026     * <p>
027     * Provide this Stage with a class and a static method name and it will
028     * dynamically look up the appropriate method to call based on the object type.
029     * If the object type is an array, it will assume that the method that needs to
030     * be called contains the method signature as described by the objects in the
031     * array. The object returned from the method call will be exqueued.
032     * </p>
033     *
034     * <p>
035     * The resulting object will be exqueued on the main pipeline if it is not null.
036     * If it is null, we will try to place the original object on the branch
037     * specified by the nullResultBranchKey property. The default for this value is
038     * "nullResult".
039     * </p>
040     */
041    public class DynamicLookupStaticMethodStage extends BaseStage {
042        
043        // Branch upon which the original objects will be enqueued if the defined
044        // Method returned a null result.
045        private String nullResultBranchKey;
046        
047        // Name of the method to call to process the object.
048        private String methodName;
049        
050        // Class containing the method.
051        private Class clazz;
052        
053        /**
054         * Creates a new instance of DynamicLookupStaticMethodStage
055         *
056         * @param clazz
057         *            The class that defines the static method that will be used to
058         *            process objects.
059         * @param methodName
060         *            The name of the method. This method may be overloaded.
061         */
062        public DynamicLookupStaticMethodStage(Class clazz, String methodName) {
063            if (clazz == null) throw new IllegalArgumentException("Argument 'clazz' can not be null.");
064            if (methodName == null) throw new IllegalArgumentException("Argument 'methodName' can not be null.");
065            
066            this.clazz = clazz;
067            this.methodName = methodName;
068        }
069        
070        /**
071         * Creates a new DynamicLookupStaticMethodStage for the specified class and
072         * static method.
073         *
074         * @param className
075         *            The fully qualified class name of the class in which the
076         *            static method that will be used to process objects is defined.
077         * @param methodName
078         *            The name of the method. This method may be overloaded.
079         * @throws ClassNotFoundException
080         *             if the specified class cannot be loaded.
081         */
082        public DynamicLookupStaticMethodStage(String className, String methodName) throws ClassNotFoundException {
083            this(Thread.currentThread().getContextClassLoader().loadClass(className), methodName);
084        }
085        
086        /**
087         * <p>
088         * Finds the appropriate method overloading for the method specified by
089         * {@link #getMethodName() methodName}, calls it to process the object, and
090         * exqueues any returned object. If the returned object is null, the
091         * original object is enqueued on the branch specified by the
092         * nullResultBranchKey property.
093         * </p>
094         *
095         * @param obj
096         *            The object to process.
097         */
098        public void process(Object obj) throws StageException {
099            Class[] argTypes;
100            if (obj.getClass().isArray()) {
101                Object[] objs = (Object[]) obj;
102                argTypes = new Class[objs.length];
103                for (int i = 0; i < objs.length; i++) {
104                    argTypes[i] = objs[i].getClass();
105                }
106            } else {
107                argTypes = new Class[] {obj.getClass()};
108            }
109            
110            try {
111                Method method = this.clazz.getMethod(methodName, argTypes);
112                
113                // due to the way that varargs work, we need to ensure that we get
114                // the correct compile-time overloading of method.invoke()
115                Object result = obj.getClass().isArray() ? method.invoke(null, (Object[]) obj) : method.invoke(null, obj);
116                if (result != null){
117                    this.emit(result);
118                } else if (this.nullResultBranchKey != null) {
119                    this.context.getBranchFeeder(this.nullResultBranchKey).feed(obj);
120                }
121            } catch (NoSuchMethodException e){
122                StringBuilder message = new StringBuilder("No acceptable method " + methodName + " found with argument(s) of type: [ ");
123                for (Class<?> clazz : argTypes) message.append(clazz.getName()).append(" ");
124                message.append("]");
125                
126                throw new StageException(this, message.toString() ,e);
127            } catch (IllegalAccessException e){
128                throw new StageException(this, e);
129            } catch (InvocationTargetException e){
130                throw new StageException(this, e);
131            }
132        }
133        
134        /** Returns the name of the method to be executed. */
135        public String getMethodName(){
136            return this.methodName;
137        }
138        
139        /** Returns the class containing the method to be executed */
140        public Class getMethodClass(){
141            return this.clazz;
142        }
143        
144        /**
145         * Getter for property nullResultBranchKey.
146         *
147         * @return Value of property nullResultBranchKey.
148         */
149        public String getNullResultBranchKey() {
150            return this.nullResultBranchKey;
151        }
152        
153        /**
154         * Setter for property nullResultBranchKey.
155         *
156         * @param nullResultBranchKey
157         *            New value of property nullResultBranchKey.
158         */
159        public void setNullResultBranchKey(String nullResultBranchKey) {
160            this.nullResultBranchKey = nullResultBranchKey;
161        }
162        
163    }