001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004
005 This file is part of Granite Data Services.
006
007 Granite Data Services is free software; you can redistribute it and/or modify
008 it under the terms of the GNU Library General Public License as published by
009 the Free Software Foundation; either version 2 of the License, or (at your
010 option) any later version.
011
012 Granite Data Services is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 for more details.
016
017 You should have received a copy of the GNU Library General Public License
018 along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020
021 package org.granite.client.tide.impl;
022
023 import java.lang.reflect.InvocationHandler;
024 import java.lang.reflect.Method;
025 import java.util.Arrays;
026 import java.util.List;
027 import java.util.concurrent.Future;
028
029 import org.granite.client.messaging.RemoteService;
030 import org.granite.client.messaging.channel.ResponseMessageFuture;
031 import org.granite.client.messaging.events.FaultEvent;
032 import org.granite.client.messaging.events.ResultEvent;
033 import org.granite.client.tide.Context;
034 import org.granite.client.tide.ContextAware;
035 import org.granite.client.tide.NameAware;
036 import org.granite.client.tide.PropertyHolder;
037 import org.granite.client.tide.server.ArgumentPreprocessor;
038 import org.granite.client.tide.server.Component;
039 import org.granite.client.tide.server.ComponentListener;
040 import org.granite.client.tide.server.InvocationInterceptor;
041 import org.granite.client.tide.server.ServerSession;
042 import org.granite.client.tide.server.TideResponder;
043 import org.granite.client.tide.server.TrackingContext;
044 import org.granite.logging.Logger;
045 import org.granite.messaging.amf.RemoteClass;
046 import org.granite.tide.invocation.InvocationCall;
047
048 /**
049 * @author William DRAI
050 */
051 public class ComponentImpl implements Component, ContextAware, NameAware, InvocationHandler {
052
053 private static final Logger log = Logger.getLogger(ComponentImpl.class);
054
055
056 private String name;
057 private Context context;
058 private final ServerSession serverSession;
059
060
061 public ComponentImpl() {
062 this.serverSession = null;
063 }
064
065 public ComponentImpl(ServerSession serverSession) {
066 this.serverSession = serverSession;
067 }
068
069
070 public void setName(String name) {
071 this.name = name;
072 }
073 public String getName() {
074 return name;
075 }
076
077 public void setContext(Context context) {
078 this.context = context;
079 }
080 protected Context getContext() {
081 return context;
082 }
083
084 protected ServerSession getServerSession() {
085 return serverSession;
086 }
087
088
089 @SuppressWarnings("unchecked")
090 public <T> Future<T> call(String operation, Object... args) {
091 Context context = this.context;
092
093 if (args != null && args.length > 0 && args[0] instanceof Context) {
094 context = (Context)args[0];
095 Object[] newArgs = new Object[args.length-1];
096 for (int i = 1; i < args.length-1; i++)
097 newArgs[i-1] = args[i];
098 args = newArgs;
099 }
100
101 return (Future<T>)callComponent(context, operation, args, false);
102 }
103
104
105 @Override
106 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
107 if (!method.getDeclaringClass().isAnnotationPresent(RemoteClass.class))
108 return method.invoke(proxy, args);
109
110 return callComponent(getContext(), method.getName(), args, false);
111 }
112
113
114 /**
115 * Calls a remote component
116 *
117 * @param component the target component
118 * @param op name of the called metho
119 * @param arg method parameters
120 * @param withContext add context sync data to call
121 *
122 * @return the operation token
123 */
124 @SuppressWarnings("unchecked")
125 protected <T> Future<T> callComponent(Context context, String operation, Object[] args, boolean withContext) {
126 context.checkValid();
127
128 log.debug("callComponent %s.%s", getName(), operation);
129
130 TideResponder<T> responder = null;
131 if (args != null && args.length > 0 && args[args.length-1] instanceof TideResponder) {
132 responder = (TideResponder<T>)args[args.length-1];
133 Object[] newArgs = new Object[args.length-1];
134 for (int i = 0; i < args.length-1; i++)
135 newArgs[i] = args[i];
136 args = newArgs;
137 }
138
139 // Force generation of uids by merging all arguments in the current context
140 context.getEntityManager().initMerge();
141 List<Object> argsList = Arrays.asList(args);
142 for (int i = 0; i < args.length; i++) {
143 if (argsList.get(i) instanceof PropertyHolder)
144 argsList.set(i, ((PropertyHolder)args[i]).getObject());
145 }
146 argsList = (List<Object>)context.getEntityManager().mergeExternalData(argsList);
147 for (int i = 0; i < args.length; i++)
148 args[i] = argsList.get(i);
149
150 Method method = null;
151 // TODO: improve method matching
152 for (Method m : getClass().getMethods()) {
153 if (m.getName().equals(operation) && m.getParameterTypes().length == args.length) {
154 method = m;
155 break;
156 }
157 }
158 if (method != null) {
159 // Call argument preprocessors if necessary before sending arguments to server
160 ArgumentPreprocessor[] apps = context.allByType(ArgumentPreprocessor.class);
161 if (apps != null) {
162 for (ArgumentPreprocessor app : apps)
163 args = app.preprocess(method, args);
164 }
165 }
166
167 TrackingContext trackingContext = serverSession.getTrackingContext();
168 Future<T> future = null;
169 boolean saveTracking = trackingContext.isEnabled();
170 try {
171 trackingContext.setEnabled(false);
172 future = invoke(context, this, operation, args, responder, withContext, null);
173 }
174 finally {
175 trackingContext.setEnabled(saveTracking);
176 }
177
178 if (withContext)
179 trackingContext.clearUpdates(true);
180
181 // TODO: conversation contexts
182 serverSession.trackCall();
183 // if (remoteConversation != null)
184 // remoteConversation.call();
185
186 return future;
187 }
188
189
190 @SuppressWarnings({"unchecked", "rawtypes"})
191 public <T> Future<T> invoke(Context context, Component component, String operation, Object[] args, TideResponder<T> tideResponder,
192 boolean withContext, ComponentListener.Handler<T> handler) {
193 log.debug("invokeComponent %s > %s.%s", context.getContextId(), component.getName() != null ? component.getName() : component.getClass().getName(), operation);
194
195 ComponentListener.Handler h = handler != null ? handler : new ComponentListener.Handler<T>() {
196 @Override
197 public Runnable result(Context context, ResultEvent event, Object info, String componentName,
198 String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) {
199 return new ResultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener);
200 }
201
202 @Override
203 public Runnable fault(Context context, FaultEvent event, Object info, String componentName,
204 String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) {
205 return new FaultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener);
206 }
207 };
208 ComponentListener<T> componentListener = new ComponentListenerImpl<T>(context, h, component, operation, args, null, tideResponder);
209
210 InvocationInterceptor[] interceptors = context.allByType(InvocationInterceptor.class);
211 if (interceptors != null) {
212 for (InvocationInterceptor interceptor : interceptors)
213 interceptor.beforeInvocation(context, component, operation, args, componentListener);
214 }
215
216 context.getContextManager().destroyFinishedContexts();
217
218 // // Force generation of uids by merging all arguments in the current context
219 // for (int i = 0; i < args.length; i++) {
220 // if (args[i] instanceof PropertyHolder)
221 // args[i] = ((PropertyHolder)args[i]).getObject();
222 // args[i] = entityManager.mergeExternal(args[i], null);
223 // }
224 //
225 // // Call argument preprocessors before sending arguments to server
226 // var method:Method = Type.forInstance(component).getInstanceMethodNoCache(op);
227 // for each (var app:IArgumentPreprocessor in allByType(IArgumentPreprocessor, true))
228 // componentResponder.args = app.preprocess(method, args);
229
230 Object[] call = new Object[5];
231 call[0] = componentListener.getComponent().getName();
232 String componentClassName = null;
233 if (componentListener.getComponent().getClass() != ComponentImpl.class) {
234 RemoteClass remoteClass = componentListener.getComponent().getClass().getAnnotation(RemoteClass.class);
235 componentClassName = remoteClass != null ? remoteClass.value() : componentListener.getComponent().getClass().getName();
236 }
237 call[1] = componentClassName;
238 call[2] = componentListener.getOperation();
239 call[3] = componentListener.getArgs();
240 call[4] = new InvocationCall();
241
242 RemoteService ro = serverSession.getRemoteService();
243 ResponseMessageFuture rmf = ro.newInvocation("invokeComponent", call).addListener(componentListener).invoke();
244
245 serverSession.checkWaitForLogout();
246
247 return new FutureResult<T>(rmf, componentListener);
248 }
249 }