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    }