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}