001    /*
002     * Copyright (C) 2006-2007 the original author or authors.
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    
017    package org.codehaus.gmaven.runtime.support;
018    
019    import org.codehaus.gmaven.feature.ComponentException;
020    import org.codehaus.gmaven.feature.Configuration;
021    import org.codehaus.gmaven.feature.Feature;
022    import org.codehaus.gmaven.feature.support.ComponentSupport;
023    import org.codehaus.gmaven.runtime.ClassFactory;
024    import org.codehaus.gmaven.runtime.ScriptExecutor;
025    import org.codehaus.gmaven.runtime.util.Callable;
026    import org.codehaus.gmaven.runtime.util.ClassSource;
027    import org.codehaus.gmaven.runtime.util.MagicAttribute;
028    import org.codehaus.gmaven.runtime.util.ResourceLoader;
029    
030    import java.lang.reflect.InvocationTargetException;
031    import java.lang.reflect.Method;
032    import java.util.Arrays;
033    import java.util.Iterator;
034    
035    /**
036     * Support for {@link ScriptExecutor} component implementations.
037     *
038     * @version $Id: ScriptExecutorSupport.java 83 2009-12-11 11:01:28Z user57 $
039     * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
040     */
041    public abstract class ScriptExecutorSupport
042        extends ComponentSupport
043        implements ScriptExecutor
044    {
045        protected ScriptExecutorSupport(final Feature feature, final Configuration config) {
046            super(feature, config);
047        }
048    
049        protected ScriptExecutorSupport(final Feature feature) {
050            super(feature);
051        }
052    
053        protected abstract ClassFactory getClassFactory();
054    
055        public Object execute(final ClassSource source, final ClassLoader classLoader, final ResourceLoader resourceLoader, final Configuration context) throws Exception {
056            assert source != null;
057            assert classLoader != null;
058            // resourceLoader may be null
059            // context may be null
060    
061            // Create/load the class
062            Class type = getClassFactory().create(source, classLoader, resourceLoader);
063    
064            // Create a new instance
065            Object target = type.newInstance();
066    
067            // Apply the execution context if we have one
068            if (context != null) {
069                applyContext(target, context);
070            }
071    
072            // And then execute
073            return execute(target);
074        }
075    
076        protected abstract Object createClosure(Callable target);
077    
078        protected abstract Object createMagicAttribute(MagicAttribute attr);
079    
080        //
081        // NOTE: Using reflection here to invoke setProperty() and run() to avoid evil class loading problems.
082        //
083    
084        protected void applyContext(final Object target, final Configuration context) {
085            assert target != null;
086            assert context != null;
087    
088            Method setter = lookupMethod(target.getClass(), "setProperty", new Class[] { String.class, Object.class });
089    
090            log.debug("Setting context:");
091    
092            // Install the context for the script
093            for (Iterator iter = context.names().iterator(); iter.hasNext();) {
094                String name = (String) iter.next();
095                Object value = context.get(name);
096    
097                // Adapt Callable instances into Closures
098                if (value instanceof Callable) {
099                    Callable c = (Callable) value;
100    
101                    value = createClosure(c);
102                }
103    
104                // Handle some uber hacks
105                else if (value instanceof MagicAttribute) {
106                    MagicAttribute attr = (MagicAttribute) value;
107    
108                    value = createMagicAttribute(attr);
109                }
110    
111                if (log.isDebugEnabled()) {
112                    if (value != null) {
113                        log.debug("    {} -> {} ({})", new Object[] { name, value, value.getClass() });
114                    }
115                    else {
116                        log.debug("    {} -> {}", name, value);
117                    }
118                }
119                
120                invokeMethod(target, setter, new Object[] { name, value });
121            }
122        }
123    
124        protected Object execute(final Object target) {
125            assert target != null;
126    
127            Method run = lookupMethod(target.getClass(), "run", new Class[0]);
128    
129            //
130            // TODO: Look at Groovysh to allow for more flexible script execution
131            //
132    
133            return invokeMethod(target, run, new Object[0]);
134        }
135    
136        //
137        // Reflection Helpers
138        //
139    
140        protected Method lookupMethod(final Class type, final String name, final Class[] sig) {
141            assert type != null;
142            assert name != null;
143            assert sig != null;
144    
145            Method method;
146    
147            try {
148                method = type.getMethod(name, sig);
149            }
150            catch (Exception e) {
151                throw new ComponentException("Failed to lookup method '" + name + "()' on: " + type, e);
152            }
153    
154            return method;
155        }
156    
157        protected Object invokeMethod(final Object target, final Method method, final Object[] args) {
158            assert target != null;
159            assert method != null;
160            assert args != null;
161    
162            if (log.isTraceEnabled()) {
163                log.trace("Invoking {} on {} with {}", new Object[] {method, target, Arrays.asList(args)});
164            }
165    
166            try {
167                return method.invoke(target, args);
168            }
169            catch (InvocationTargetException e) {
170                // Unroll ITE to reduce the trace noise
171                throw new ComponentException(e.getTargetException());
172            }
173            catch (Exception e) {
174                throw new ComponentException("Failed to invoke method '" + method + "' on target: " + target, e);
175            }
176        }
177    }