001/*
002 *  Copyright 2013 Anyware Services
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 */
016package org.ametys.core.ui;
017
018import java.lang.reflect.Method;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.avalon.framework.parameters.Parameters;
023import org.apache.avalon.framework.thread.ThreadSafe;
024import org.apache.cocoon.acting.ServiceableAction;
025import org.apache.cocoon.environment.ObjectModelHelper;
026import org.apache.cocoon.environment.Redirector;
027import org.apache.cocoon.environment.Request;
028import org.apache.cocoon.environment.SourceResolver;
029import org.apache.commons.lang3.ClassUtils;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.lang3.reflect.MethodUtils;
032
033import org.ametys.core.cocoon.JSonReader;
034import org.ametys.runtime.plugin.ExtensionPoint;
035
036/**
037 * Action executing remote method calls coming from client-side elements.<br>
038 * Called methods should be annotated with {@link Callable}.<br>
039 */
040public class ExecuteClientCallsAction extends ServiceableAction implements ThreadSafe
041{
042    @SuppressWarnings("unchecked")
043    @Override
044    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
045    {
046        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
047
048        // Find the corresponding object, either a component or an extension
049        String role = (String) jsParameters.get("role");
050        
051        if (role == null)
052        {
053            throw new IllegalArgumentException("Component role should be present.");
054        }
055        
056        Object object;
057        
058        if (!manager.hasService(role))
059        {
060            throw new IllegalArgumentException("The role '" + role + "' does not correspond to a valid component.");
061        }
062        
063        Object component = manager.lookup(role);
064        
065        if (component instanceof ExtensionPoint)
066        {
067            ExtensionPoint extPoint = (ExtensionPoint) component;
068            
069            String id = (String) jsParameters.get("id");
070            
071            if (id == null)
072            {
073                object = component;
074            }
075            else
076            {
077                object = extPoint.getExtension(id);
078                
079                if (object == null)
080                {
081                    throw new IllegalArgumentException("The id '" + id + "' does not correspond to a valid extension for point " + role);
082                }
083            }
084        }
085        else
086        {
087            object = component;
088        }
089        
090        // Find the corresponding method
091        String methodName = (String) jsParameters.get("methodName");
092        List<Object> params = (List<Object>) jsParameters.get("parameters");
093        
094        if (methodName == null)
095        {
096            throw new IllegalArgumentException("No method name present, cannot execute server side code.");
097        }
098        
099        Class[] paramClass;
100        Object[] paramValues;
101        if (params == null)
102        {
103            paramClass = new Class[0];
104            paramValues = new Object[0];
105        }
106        else
107        {
108            paramValues = params.toArray();
109            paramClass = ClassUtils.toClass(paramValues);
110        }
111        
112        Class<? extends Object> clazz = object.getClass();
113        Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, paramClass);
114        
115        if (method == null)
116        {
117            throw new IllegalArgumentException("No method with signature " + methodName + "(" + StringUtils.join(paramClass, ", ").replaceAll("class ", "") + ") present in class " + clazz.getName() + ".");
118        }
119        
120        Object result = _executeMethod(method, object, paramValues);
121        
122        Request request = ObjectModelHelper.getRequest(objectModel);
123        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
124        
125        return EMPTY_MAP;
126    }
127    
128    /**
129     * Execute the method set in the client call
130     * @param method The method
131     * @param object The object which has the method
132     * @param paramValues The method parameters
133     * @return The result
134     * @throws Exception If an error occurred
135     */
136    protected Object _executeMethod(Method method, Object object, Object[] paramValues) throws Exception
137    {
138        Object result = null;
139        if (method.isAnnotationPresent(Callable.class))
140        {
141            result = method.invoke(object, paramValues);
142        }
143        else
144        {
145            throw new IllegalArgumentException("Trying to call a non-callable method: " + method.toGenericString() + ".");
146        }
147        
148        return result;
149    }
150}