/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * *** * * Community License: GPL 3.0 * * This file is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * *** * * Available Commercial License: GraniteDS SLA 1.0 * * This is the appropriate option if you are creating proprietary * applications and you are not prepared to distribute and share the * source code of your application under the GPL v3 license. * * Please visit http://www.granitedataservices.com/license for more * details. */ package org.granite.client.tide.impl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.concurrent.Future; import org.granite.client.messaging.RemoteAlias; import org.granite.client.messaging.events.FaultEvent; import org.granite.client.messaging.events.IssueEvent; import org.granite.client.messaging.events.ResultEvent; import org.granite.client.tide.Context; import org.granite.client.tide.ContextAware; import org.granite.client.tide.NameAware; import org.granite.client.tide.data.spi.MergeContext; import org.granite.client.tide.server.ArgumentPreprocessor; import org.granite.client.tide.server.Component; import org.granite.client.tide.server.ComponentListener; import org.granite.client.tide.server.InvocationInterceptor; import org.granite.client.tide.server.ServerSession; import org.granite.client.tide.server.TideResponder; import org.granite.client.tide.server.TideResultEvent; import org.granite.client.util.PropertyHolder; import org.granite.logging.Logger; /** * Default implementation of remote component proxy * Generated typesafe remote service proxies should extend this class * * Component proxies are meant to be defined in a DI container (Spring/CDI) or directly in the Tide context * <pre> * {@code * Component myComponent = tideContext.set("myComponent", new ComponentImpl(serverSession)); * myComponent.call("myMethod", arg1, arg2); * } * </pre> * * @author William DRAI */ public class ComponentImpl implements Component, ContextAware, NameAware, InvocationHandler { private static final Logger log = Logger.getLogger(ComponentImpl.class); private String name; private Context context; private final ServerSession serverSession; /** * Default constructor necessary for testing and CDI proxying... */ public ComponentImpl() { this.serverSession = null; } /** * Create a new proxy attached to the specified server session * @param serverSession server session */ public ComponentImpl(ServerSession serverSession) { this.serverSession = serverSession; } /** * Set the remote name of the component * By default the component will use its name in its owning context as the remote name * @param name name */ public void setName(String name) { this.name = name; } /** * Remote name of the component * @return name */ public String getName() { return name; } /** * Set the context where the component is set * @param context Tide context */ public void setContext(Context context) { this.context = context; } /** * Context where the component is set * @return Tide context */ protected Context getContext() { return context; } /** * Server session to which the component is attached * @return server session */ protected ServerSession getServerSession() { return serverSession; } @SuppressWarnings("unchecked") public <T> Future<T> call(String operation, Object... args) { Context context = this.context; if (args != null && args.length > 0 && args[0] instanceof Context) { context = (Context)args[0]; Object[] newArgs = new Object[args.length-1]; for (int i = 1; i < args.length-1; i++) newArgs[i-1] = args[i]; args = newArgs; } return (Future<T>)callComponent(context, operation, args); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (!method.getDeclaringClass().isAnnotationPresent(RemoteAlias.class)) return method.invoke(proxy, args); return callComponent(getContext(), method.getName(), args); } /** * Calls a remote component * * @param context the source context * @param operation name of the called metho * @param args method arguments * * @return future returning the result of the call */ @SuppressWarnings("unchecked") protected <T> Future<T> callComponent(Context context, String operation, Object[] args) { context.checkValid(); log.debug("callComponent %s.%s", getName(), operation); TideResponder<T> responder = null; if (args != null && args.length > 0 && args[args.length-1] instanceof TideResponder) { responder = (TideResponder<T>)args[args.length-1]; Object[] newArgs = new Object[args.length-1]; for (int i = 0; i < args.length-1; i++) newArgs[i] = args[i]; args = newArgs; } // Force generation of uids by merging all arguments in the current context MergeContext mergeContext = context.getEntityManager().initMerge(serverSession); List<Object> argsList = Arrays.asList(args); for (int i = 0; i < args.length; i++) { if (argsList.get(i) instanceof PropertyHolder) argsList.set(i, ((PropertyHolder)args[i]).getObject()); } argsList = (List<Object>)context.getEntityManager().mergeExternal(mergeContext, argsList, null, null, null, false); for (int i = 0; i < args.length; i++) args[i] = argsList.get(i); Method method = null; // TODO: improve method matching for (Method m : getClass().getMethods()) { if (m.getName().equals(operation) && m.getParameterTypes().length == args.length) { method = m; break; } } if (method != null) { // Call argument preprocessors if necessary before sending arguments to server ArgumentPreprocessor[] apps = context.allByType(ArgumentPreprocessor.class); if (apps != null) { for (ArgumentPreprocessor app : apps) args = app.preprocess(serverSession, method, args); } } return invoke(context, operation, args, responder); } /** * Execute the invocation of the remote component * @param context the source context * @param operation method name * @param args method arguments * @param tideResponder Tide responder for the remote call * @param <T> expected type of result * @return future triggered asynchronously when response is received */ @SuppressWarnings({"unchecked", "rawtypes"}) protected <T> Future<T> invoke(Context context, String operation, Object[] args, TideResponder<T> tideResponder) { log.debug("invokeComponent %s > %s.%s", context.getContextId(), getName() != null ? getName() : getClass().getName(), operation); ComponentListener.Handler handler = new ComponentListener.Handler<T>() { @Override public Runnable result(Context context, ResultEvent event, Object info, String componentName, String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) { return new ResultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener); } @Override public Runnable fault(Context context, FaultEvent event, Object info, String componentName, String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) { return new FaultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener); } @Override public Runnable issue(Context context, IssueEvent event, Object info, String componentName, String operation, TideResponder<T> tideResponder, ComponentListener<T> componentListener) { return new FaultHandler(serverSession, context, componentName, operation, event, info, tideResponder, componentListener); } }; ComponentListener<T> componentListener = new ComponentListenerImpl<T>(context, handler, this, operation, args, null, tideResponder); InvocationInterceptor[] interceptors = context.allByType(InvocationInterceptor.class); if (interceptors != null) { for (InvocationInterceptor interceptor : interceptors) interceptor.beforeInvocation(context, this, operation, args, componentListener); } context.getContextManager().destroyFinishedContexts(); // // Force generation of uids by merging all arguments in the current context // for (int i = 0; i < args.length; i++) { // if (args[i] instanceof PropertyHolder) // args[i] = ((PropertyHolder)args[i]).getObject(); // args[i] = entityManager.mergeExternal(args[i], null); // } // // // Call argument preprocessors before sending arguments to server // var method:Method = Type.forInstance(component).getInstanceMethodNoCache(op); // for each (var app:IArgumentPreprocessor in allByType(IArgumentPreprocessor, true)) // componentResponder.args = app.preprocess(method, args); return componentListener.invoke(serverSession); } /** * Create a result event for this component * @param result result to wrap in an event * @param <T> expected type of the result * @return result event */ public <T> TideResultEvent<T> newResultEvent(T result) { return new TideResultEvent<T>(getContext(), getServerSession(), null, result); } }