001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * *** 008 * 009 * Community License: GPL 3.0 010 * 011 * This file is free software: you can redistribute it and/or modify 012 * it under the terms of the GNU General Public License as published 013 * by the Free Software Foundation, either version 3 of the License, 014 * or (at your option) any later version. 015 * 016 * This file is distributed in the hope that it will be useful, but 017 * WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 019 * GNU General Public License for more details. 020 * 021 * You should have received a copy of the GNU General Public License 022 * along with this program. If not, see <http://www.gnu.org/licenses/>. 023 * 024 * *** 025 * 026 * Available Commercial License: GraniteDS SLA 1.0 027 * 028 * This is the appropriate option if you are creating proprietary 029 * applications and you are not prepared to distribute and share the 030 * source code of your application under the GPL v3 license. 031 * 032 * Please visit http://www.granitedataservices.com/license for more 033 * details. 034 */ 035package org.granite.client.tide.impl; 036 037import java.lang.reflect.InvocationHandler; 038import java.lang.reflect.Method; 039import java.util.Arrays; 040import java.util.List; 041import java.util.concurrent.Future; 042 043import org.granite.client.messaging.RemoteAlias; 044import org.granite.client.messaging.events.FaultEvent; 045import org.granite.client.messaging.events.IssueEvent; 046import org.granite.client.messaging.events.ResultEvent; 047import org.granite.client.tide.Context; 048import org.granite.client.tide.ContextAware; 049import org.granite.client.tide.NameAware; 050import org.granite.client.tide.PropertyHolder; 051import org.granite.client.tide.server.ArgumentPreprocessor; 052import org.granite.client.tide.server.Component; 053import org.granite.client.tide.server.ComponentListener; 054import org.granite.client.tide.server.InvocationInterceptor; 055import org.granite.client.tide.server.ServerSession; 056import org.granite.client.tide.server.TideResponder; 057import org.granite.client.tide.server.TideResultEvent; 058import org.granite.logging.Logger; 059 060/** 061 * Default implementation of remote component proxy 062 * Generated typesafe remote service proxies should extend this class 063 * 064 * Component proxies are meant to be defined in a DI container (Spring/CDI) or directly in the Tide context 065 * <pre> 066 * {@code 067 * Component myComponent = tideContext.set("myComponent", new ComponentImpl(serverSession)); 068 * myComponent.call("myMethod", arg1, arg2); 069 * } 070 * </pre> 071 * 072 * @author William DRAI 073 */ 074public class ComponentImpl implements Component, ContextAware, NameAware, InvocationHandler { 075 076 private static final Logger log = Logger.getLogger(ComponentImpl.class); 077 078 079 private String name; 080 private Context context; 081 private final ServerSession serverSession; 082 083 084 /** 085 * Default constructor necessary for testing and CDI proxying... 086 */ 087 public ComponentImpl() { 088 this.serverSession = null; 089 } 090 091 /** 092 * Create a new proxy attached to the specified server session 093 * @param serverSession server session 094 */ 095 public ComponentImpl(ServerSession serverSession) { 096 this.serverSession = serverSession; 097 } 098 099 /** 100 * Set the remote name of the component 101 * By default the component will use its name in its owning context as the remote name 102 * @param name name 103 */ 104 public void setName(String name) { 105 this.name = name; 106 } 107 /** 108 * Remote name of the component 109 * @return name 110 */ 111 public String getName() { 112 return name; 113 } 114 115 /** 116 * Set the context where the component is set 117 * @param context Tide context 118 */ 119 public void setContext(Context context) { 120 this.context = context; 121 } 122 123 /** 124 * Context where the component is set 125 * @return Tide context 126 */ 127 protected Context getContext() { 128 return context; 129 } 130 131 /** 132 * Server session to which the component is attached 133 * @return server session 134 */ 135 protected ServerSession getServerSession() { 136 return serverSession; 137 } 138 139 140 @SuppressWarnings("unchecked") 141 public <T> Future<T> call(String operation, Object... args) { 142 Context context = this.context; 143 144 if (args != null && args.length > 0 && args[0] instanceof Context) { 145 context = (Context)args[0]; 146 Object[] newArgs = new Object[args.length-1]; 147 for (int i = 1; i < args.length-1; i++) 148 newArgs[i-1] = args[i]; 149 args = newArgs; 150 } 151 152 return (Future<T>)callComponent(context, operation, args); 153 } 154 155 156 @Override 157 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 158 if (!method.getDeclaringClass().isAnnotationPresent(RemoteAlias.class)) 159 return method.invoke(proxy, args); 160 161 return callComponent(getContext(), method.getName(), args); 162 } 163 164 165 /** 166 * Calls a remote component 167 * 168 * @param context the source context 169 * @param operation name of the called metho 170 * @param args method arguments 171 * 172 * @return future returning the result of the call 173 */ 174 @SuppressWarnings("unchecked") 175 protected <T> Future<T> callComponent(Context context, String operation, Object[] args) { 176 context.checkValid(); 177 178 log.debug("callComponent %s.%s", getName(), operation); 179 180 TideResponder<T> responder = null; 181 if (args != null && args.length > 0 && args[args.length-1] instanceof TideResponder) { 182 responder = (TideResponder<T>)args[args.length-1]; 183 Object[] newArgs = new Object[args.length-1]; 184 for (int i = 0; i < args.length-1; i++) 185 newArgs[i] = args[i]; 186 args = newArgs; 187 } 188 189 // Force generation of uids by merging all arguments in the current context 190 context.getEntityManager().initMerge(); 191 List<Object> argsList = Arrays.asList(args); 192 for (int i = 0; i < args.length; i++) { 193 if (argsList.get(i) instanceof PropertyHolder) 194 argsList.set(i, ((PropertyHolder)args[i]).getObject()); 195 } 196 argsList = (List<Object>)context.getEntityManager().mergeExternalData(argsList); 197 for (int i = 0; i < args.length; i++) 198 args[i] = argsList.get(i); 199 200 Method method = null; 201 // TODO: improve method matching 202 for (Method m : getClass().getMethods()) { 203 if (m.getName().equals(operation) && m.getParameterTypes().length == args.length) { 204 method = m; 205 break; 206 } 207 } 208 if (method != null) { 209 // Call argument preprocessors if necessary before sending arguments to server 210 ArgumentPreprocessor[] apps = context.allByType(ArgumentPreprocessor.class); 211 if (apps != null) { 212 for (ArgumentPreprocessor app : apps) 213 args = app.preprocess(method, args); 214 } 215 } 216 217 return invoke(context, operation, args, responder); 218 } 219 220 /** 221 * Execute the invocation of the remote component 222 * @param context the source context 223 * @param operation method name 224 * @param args method arguments 225 * @param tideResponder Tide responder for the remote call 226 * @param <T> expected type of result 227 * @return future triggered asynchronously when response is received 228 */ 229 @SuppressWarnings({"unchecked", "rawtypes"}) 230 protected <T> Future<T> invoke(Context context, String operation, Object[] args, TideResponder<T> tideResponder) { 231 log.debug("invokeComponent %s > %s.%s", context.getContextId(), getName() != null ? getName() : getClass().getName(), operation); 232 233 ComponentListener.Handler handler = new ComponentListener.Handler<T>() { 234 @Override 235 public Runnable result(Context context, ResultEvent event, Object info, String componentName, 236 String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) { 237 return new ResultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener); 238 } 239 240 @Override 241 public Runnable fault(Context context, FaultEvent event, Object info, String componentName, 242 String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) { 243 return new FaultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener); 244 } 245 246 @Override 247 public Runnable issue(Context context, IssueEvent event, Object info, String componentName, 248 String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) { 249 return new FaultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener); 250 } 251 }; 252 ComponentListener<T> componentListener = new ComponentListenerImpl<T>(context, handler, this, operation, args, null, tideResponder); 253 254 InvocationInterceptor[] interceptors = context.allByType(InvocationInterceptor.class); 255 if (interceptors != null) { 256 for (InvocationInterceptor interceptor : interceptors) 257 interceptor.beforeInvocation(context, this, operation, args, componentListener); 258 } 259 260 context.getContextManager().destroyFinishedContexts(); 261 262// // Force generation of uids by merging all arguments in the current context 263// for (int i = 0; i < args.length; i++) { 264// if (args[i] instanceof PropertyHolder) 265// args[i] = ((PropertyHolder)args[i]).getObject(); 266// args[i] = entityManager.mergeExternal(args[i], null); 267// } 268// 269// // Call argument preprocessors before sending arguments to server 270// var method:Method = Type.forInstance(component).getInstanceMethodNoCache(op); 271// for each (var app:IArgumentPreprocessor in allByType(IArgumentPreprocessor, true)) 272// componentResponder.args = app.preprocess(method, args); 273 274 return componentListener.invoke(serverSession); 275 } 276 277 /** 278 * Create a result event for this component 279 * @param result result to wrap in an event 280 * @param <T> expected type of the result 281 * @return result event 282 */ 283 public <T> TideResultEvent<T> newResultEvent(T result) { 284 return new TideResultEvent<T>(getContext(), getServerSession(), null, result); 285 } 286 287}