Minor refactorings

This commit is contained in:
Papick Garcia Taboada committed 2016-01-26 08:08:19 +01:00
1 parent 56dd5a9e81
commit 6aff2799c4
28 files changed
+282 -125

No files matched your search

@@ -1,6 +1,5 @@
package com.sap.sailing.dashboards.gwt.server; package com.sap.sailing.dashboards.gwt.server;
import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -18,11 +17,11 @@ import com.sap.sailing.gwt.ui.server.ProxiedRemoteServiceServlet;
import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventService;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.ResultWrapper;
import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException;
import com.sap.sse.gwt.dispatch.client.exceptions.ServerDispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.ServerDispatchException;
import com.sap.sse.gwt.dispatch.client.rpcimpl.DispatchRPC; import com.sap.sse.gwt.dispatch.client.rpcimpl.DispatchRPC;
import com.sap.sse.gwt.dispatch.client.rpcimpl.RequestWrapper; import com.sap.sse.gwt.dispatch.client.rpcimpl.RequestWrapper;
import com.sap.sse.gwt.dispatch.client.rpcimpl.ResultWrapper;
import com.sap.sse.util.ServiceTrackerFactory; import com.sap.sse.util.ServiceTrackerFactory;
public class DashboardDispatchRPCServletImpl extends ProxiedRemoteServiceServlet implements DispatchRPC<DashboardDispatchContext> { public class DashboardDispatchRPCServletImpl extends ProxiedRemoteServiceServlet implements DispatchRPC<DashboardDispatchContext> {
@@ -56,9 +55,9 @@ public class DashboardDispatchRPCServletImpl extends ProxiedRemoteServiceServlet
logger.log(Level.WARNING, "Server exception", d); logger.log(Level.WARNING, "Server exception", d);
throw d; throw d;
} catch (Throwable t) { } catch (Throwable t) {
String serverExceptionUUID = UUID.randomUUID().toString(); ServerDispatchException dispatchException = new ServerDispatchException(t);
logger.log(Level.SEVERE, "Uncaught server exception id: " + serverExceptionUUID, t); logger.log(Level.SEVERE, "Uncaught server exception id: " + dispatchException.getExceptionId(), t);
throw new ServerDispatchException(serverExceptionUUID, t); throw dispatchException;
} finally { } finally {
long duration = System.currentTimeMillis() - start; long duration = System.currentTimeMillis() - start;
final Level logLevel; final Level logLevel;
@@ -11,7 +11,7 @@ import org.junit.Test;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchAsync; import com.sap.sse.gwt.dispatch.client.DispatchSystemAsync;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.caching.CachingDispatch; import com.sap.sse.gwt.dispatch.client.caching.CachingDispatch;
@@ -263,7 +263,7 @@ public class CacheDispatchTest {
} }
} }
private class DispatchMock implements DispatchAsync<DispatchContextMock> { private class DispatchMock implements DispatchSystemAsync<DispatchContextMock> {
@Override @Override
public <R extends Result, A extends Action<R, DispatchContextMock>> void execute(A action, public <R extends Result, A extends Action<R, DispatchContextMock>> void execute(A action,
AsyncCallback<R> callback) throws DispatchException { AsyncCallback<R> callback) throws DispatchException {
@@ -1,6 +1,5 @@
package com.sap.sailing.gwt.home.server.servlets; package com.sap.sailing.gwt.home.server.servlets;
import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -15,11 +14,11 @@ import com.sap.sailing.news.EventNewsService;
import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventService;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.ResultWrapper;
import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException;
import com.sap.sse.gwt.dispatch.client.exceptions.ServerDispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.ServerDispatchException;
import com.sap.sse.gwt.dispatch.client.rpcimpl.DispatchRPC; import com.sap.sse.gwt.dispatch.client.rpcimpl.DispatchRPC;
import com.sap.sse.gwt.dispatch.client.rpcimpl.RequestWrapper; import com.sap.sse.gwt.dispatch.client.rpcimpl.RequestWrapper;
import com.sap.sse.gwt.dispatch.client.rpcimpl.ResultWrapper;
import com.sap.sse.util.ServiceTrackerFactory; import com.sap.sse.util.ServiceTrackerFactory;
public class DispatchRPCServletImpl extends ProxiedRemoteServiceServlet implements DispatchRPC<SailingDispatchContext> { public class DispatchRPCServletImpl extends ProxiedRemoteServiceServlet implements DispatchRPC<SailingDispatchContext> {
@@ -52,9 +51,9 @@ public class DispatchRPCServletImpl extends ProxiedRemoteServiceServlet implemen
logger.log(Level.WARNING, "Server exception", d); logger.log(Level.WARNING, "Server exception", d);
throw d; throw d;
} catch (Throwable t) { } catch (Throwable t) {
String serverExceptionUUID = UUID.randomUUID().toString(); ServerDispatchException dispatchException = new ServerDispatchException(t);
logger.log(Level.SEVERE, "Uncaught server exception id: " + serverExceptionUUID, t); logger.log(Level.SEVERE, "Uncaught server exception id: " + dispatchException.getExceptionId(), t);
throw new ServerDispatchException(serverExceptionUUID, t); throw dispatchException;
} finally { } finally {
long duration = System.currentTimeMillis() - start; long duration = System.currentTimeMillis() - start;
final Level logLevel; final Level logLevel;
@@ -11,6 +11,8 @@ import java.util.Locale;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@@ -51,6 +53,8 @@ import com.sap.sse.shared.media.MediaDescriptor;
import com.sap.sse.shared.media.VideoDescriptor; import com.sap.sse.shared.media.VideoDescriptor;
public final class HomeServiceUtil { public final class HomeServiceUtil {
private static final Logger logger = Logger.getLogger(HomeServiceUtil.class.getName());
public interface EventVisitor { public interface EventVisitor {
void visit(EventBase event, boolean onRemoteServer, URL baseURL); void visit(EventBase event, boolean onRemoteServer, URL baseURL);
} }
@@ -533,7 +537,9 @@ public final class HomeServiceUtil {
try { try {
return new URL(url.getProtocol(), url.getHost(), url.getPort(), /* file */""); return new URL(url.getProtocol(), url.getHost(), url.getPort(), /* file */"");
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new ServerDispatchException(e); ServerDispatchException dispatchException = new ServerDispatchException(e);
logger.log(Level.SEVERE, "Uncaught server exception id: " + dispatchException.getExceptionId(), e);
throw dispatchException;
} }
} }
@@ -3,9 +3,29 @@ package com.sap.sse.gwt.dispatch.client;
import com.google.gwt.core.shared.GwtIncompatible; import com.google.gwt.core.shared.GwtIncompatible;
import com.google.gwt.user.client.rpc.IsSerializable; import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException;
import com.sap.sse.gwt.dispatch.client.impl.DispatchSystem;
/**
* Action interface that defines actions that can be sent to server using a {@link DispatchSystem}.
*
* The execute method will be executed on the server side and is therefore {@link GwtIncompatible}.
*
* The state defined in the action is the state that will be sent over the wire. Any method (other than execute, which
* is GwtIncompatible) can be called both on the client and on the server side.
*
* @param <R>
* @param <CTX>
*/
public interface Action<R extends Result, CTX extends DispatchContext> extends IsSerializable { public interface Action<R extends Result, CTX extends DispatchContext> extends IsSerializable {
/**
* Server side execution of the action. The execute method is GwtIncompatible and will be available only on the
* server side.
*
* @param ctx
* @return
* @throws DispatchException
*/
@GwtIncompatible @GwtIncompatible
R execute(CTX ctx) throws DispatchException; R execute(CTX ctx) throws DispatchException;
@@ -2,6 +2,11 @@ package com.sap.sse.gwt.dispatch.client;
import java.util.Collection; import java.util.Collection;
/**
* Collection result that can be reused to send a collection of {@link DTO} instances
*
* @param <T>
*/
public interface CollectionResult<T extends DTO> extends DTO, Result { public interface CollectionResult<T extends DTO> extends DTO, Result {
Collection<T> getValues(); Collection<T> getValues();
} }
@@ -1,9 +0,0 @@
package com.sap.sse.gwt.dispatch.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface DispatchAsync<CTX extends DispatchContext> {
<R extends Result, A extends Action<R, CTX>> void execute(A action, AsyncCallback<R> callback);
}
@@ -0,0 +1,23 @@
package com.sap.sse.gwt.dispatch.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* Async interface that defines the dispatch system contract.
*
* @param <CTX>
*/
public interface DispatchSystemAsync<CTX extends DispatchContext> {
/**
* Send an action to be executed on the server.
*
* @param action
* the action to be executed on the server
*
* @param callback
* the callback that will called upon success or failure.
*/
<R extends Result, A extends Action<R, CTX>> void execute(A action, AsyncCallback<R> callback);
}
@@ -2,6 +2,11 @@ package com.sap.sse.gwt.dispatch.client;
import com.google.gwt.user.client.rpc.IsSerializable; import com.google.gwt.user.client.rpc.IsSerializable;
/**
* Result marker interface for the dispatch communication pattern.
*
* Results may be reused across different actions.
*/
public interface Result extends IsSerializable { public interface Result extends IsSerializable {
} }
@@ -4,6 +4,11 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.gwt.dispatch.client.caching.HasClientCacheTotalTimeToLive; import com.sap.sse.gwt.dispatch.client.caching.HasClientCacheTotalTimeToLive;
/**
* Result implementation that holds a {@link DTO} with a defined time to live expiration.
*
* @param <T>
*/
public class ResultWithTTL<T extends DTO> implements Result, HasClientCacheTotalTimeToLive { public class ResultWithTTL<T extends DTO> implements Result, HasClientCacheTotalTimeToLive {
/** max millis the result might be loaded earlier so we do batches **/ /** max millis the result might be loaded earlier so we do batches **/
@@ -6,15 +6,15 @@ import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchAsync; import com.sap.sse.gwt.dispatch.client.DispatchSystemAsync;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
public class AutomaticBatchingDispatch<CTX extends DispatchContext> implements DispatchAsync<CTX> { public class AutomaticBatchingDispatch<CTX extends DispatchContext> implements DispatchSystemAsync<CTX> {
private static final Logger LOG = Logger.getLogger(AutomaticBatchingDispatch.class.getName()); private static final Logger LOG = Logger.getLogger(AutomaticBatchingDispatch.class.getName());
private final DispatchAsync<CTX> executionContext; private final DispatchSystemAsync<CTX> executionContext;
private DispatchCallStack<CTX> currentCallstack; private DispatchCallStack<CTX> currentCallstack;
@@ -29,7 +29,7 @@ public class AutomaticBatchingDispatch<CTX extends DispatchContext> implements D
* the underlying service implementation to use * the underlying service implementation to use
* *
*/ */
public AutomaticBatchingDispatch(DispatchAsync<CTX> service) { public AutomaticBatchingDispatch(DispatchSystemAsync<CTX> service) {
this(service, false); this(service, false);
} }
@@ -40,9 +40,10 @@ public class AutomaticBatchingDispatch<CTX extends DispatchContext> implements D
* *
* @param service * @param service
* the underlying service implementation to use * the underlying service implementation to use
* * @param processResultsScheduled
* use a {@link Scheduler} to process the results
*/ */
public AutomaticBatchingDispatch(DispatchAsync<CTX> service, boolean processResultsScheduled) { public AutomaticBatchingDispatch(DispatchSystemAsync<CTX> service, boolean processResultsScheduled) {
this.executionContext = service; this.executionContext = service;
this.processResultsScheduled = processResultsScheduled; this.processResultsScheduled = processResultsScheduled;
} }
@@ -10,20 +10,20 @@ import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException;
public class BatchAction<CTX extends DispatchContext> implements Action<BatchResult, CTX> { public final class BatchAction<CTX extends DispatchContext> implements Action<BatchResult, CTX> {
private static final Logger logger = Logger.getLogger(BatchAction.class.getName()); private static final Logger logger = Logger.getLogger(BatchAction.class.getName());
private Action<?, CTX>[] actions; private ArrayList<Action<?, CTX>> actions = new ArrayList<Action<?, CTX>>();
@SuppressWarnings("unused") protected BatchAction() {
private BatchAction() {
} }
@SafeVarargs public BatchAction(ArrayList<Action<?, CTX>> actions) {
public BatchAction(Action<?, CTX>... actions) { if (actions != null) {
this.actions = actions; this.actions.addAll(actions);
}
} }
public Action<?, CTX>[] getActions() { public ArrayList<Action<?, CTX>> getActions() {
return actions; return actions;
} }
@@ -31,7 +31,7 @@ public class BatchAction<CTX extends DispatchContext> implements Action<BatchRes
@GwtIncompatible @GwtIncompatible
public BatchResult execute(CTX ctx) throws DispatchException { public BatchResult execute(CTX ctx) throws DispatchException {
final int nrOfActions = getActions().length; final int nrOfActions = getActions().size();
final ArrayList<Result> results = new ArrayList<Result>(nrOfActions); final ArrayList<Result> results = new ArrayList<Result>(nrOfActions);
final ArrayList<DispatchException> exceptions = new ArrayList<DispatchException>(nrOfActions); final ArrayList<DispatchException> exceptions = new ArrayList<DispatchException>(nrOfActions);
@@ -1,21 +1,21 @@
package com.sap.sse.gwt.dispatch.client.batching; package com.sap.sse.gwt.dispatch.client.batching;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException;
public class BatchResult implements Result { public final class BatchResult implements Result {
private LinkedList<Result> results = new LinkedList<Result>(); private ArrayList<Result> results = new ArrayList<>();
private ArrayList<DispatchException> exceptions = new ArrayList<>();
private LinkedList<DispatchException> exceptions = new LinkedList<DispatchException>(); protected BatchResult() {
@SuppressWarnings("unused")
private BatchResult() {
} }
@GwtIncompatible
public BatchResult(List<Result> results, List<DispatchException> exceptions) { public BatchResult(List<Result> results, List<DispatchException> exceptions) {
this.results.addAll(results); this.results.addAll(results);
this.exceptions.addAll(exceptions); this.exceptions.addAll(exceptions);
@@ -37,9 +37,20 @@ public class BatchResult implements Result {
return results.get(i); return results.get(i);
} }
/**
* Retrieves the result at a given position only if it has the right type. Type safety is ensured by comparing class
* name. We cannot use isAssignableFrom because this is GWT/ client code.
*
* @param i
* result position
* @param type
* required result type
* @return result at given position
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends Result> T getResult(int i, Class<T> type) { public <T extends Result> T getResult(int i, Class<T> type) {
Object result = results.get(i); Object result = results.get(i);
if (result != null && type.getName().equals(result.getClass().getName())) if (result != null && type.getName().equals(result.getClass().getName()))
return (T) result; return (T) result;
return null; return null;
@@ -1,6 +1,6 @@
package com.sap.sse.gwt.dispatch.client.batching; package com.sap.sse.gwt.dispatch.client.batching;
import java.util.LinkedList; import java.util.ArrayList;
import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -9,7 +9,7 @@ import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
/** /**
* Convenience class used to aggregate {@link Call} instances. * Convenience class used to aggregate {@link DispatchCall} instances.
* *
* The class and all methods are private, its use is internal only. * The class and all methods are private, its use is internal only.
* *
@@ -19,7 +19,7 @@ public final class DispatchCallStack<CTX extends DispatchContext> {
/** /**
* The list containg all queued actions. * The list containg all queued actions.
*/ */
private final LinkedList<DispatchCall<?, ?, CTX>> actionQueue = new LinkedList<DispatchCall<?, ?, CTX>>(); private final ArrayList<DispatchCall<?, ?, CTX>> actionQueue = new ArrayList<DispatchCall<?, ?, CTX>>();
/** /**
* The batch action holding the all the actions build in the queue. This instance variable is used to lazy * The batch action holding the all the actions build in the queue. This instance variable is used to lazy
@@ -30,7 +30,8 @@ public final class DispatchCallStack<CTX extends DispatchContext> {
/** /**
* Add a call to the queue. Can only be called as long as the batch action has not been accessed/ initialized. * Add a call to the queue. Can only be called as long as the batch action has not been accessed/ initialized.
* *
* @param call * @param action
* @param callback
*/ */
public <A extends Action<R, CTX>, R extends Result> void addCall(A action, AsyncCallback<R> callback) { public <A extends Action<R, CTX>, R extends Result> void addCall(A action, AsyncCallback<R> callback) {
assert (this.batchAction == null); assert (this.batchAction == null);
@@ -38,16 +39,15 @@ public final class DispatchCallStack<CTX extends DispatchContext> {
} }
/** /**
* Return the aggregated calls as {@link BatchAction} instance. The instance is lazy initialized. * Return the aggregated calls as {@link BatchAction} instance. The instance is initialized lazily.
* *
* @return * @return
*/ */
public BatchAction<CTX> getBatchAction() { public BatchAction<CTX> getBatchAction() {
if (this.batchAction == null) { if (this.batchAction == null) {
@SuppressWarnings("unchecked") ArrayList<Action<?, CTX>> actions = new ArrayList<Action<?, CTX>>();
Action<?, CTX>[] actions = new Action[this.actionQueue.size()]; for (DispatchCall<?, ?, CTX> dispatchCall : this.actionQueue) {
for (int loop = 0; loop < actions.length; loop++) { actions.add(dispatchCall.getAction());
actions[loop] = this.actionQueue.get(loop).getAction();
} }
this.batchAction = new BatchAction<CTX>(actions); this.batchAction = new BatchAction<CTX>(actions);
} }
@@ -67,16 +67,16 @@ public final class DispatchCallStack<CTX extends DispatchContext> {
} }
/** /**
* Process results for the callstack. TODO: check generics usage, get rid of supress warnings * Process results for the callstack.
* *
* @param batchResult * @param batchResult
* @param processResultsScheduled
* use a scheduler to execute the calls
*/ */
public void processResult(BatchResult batchResult, boolean processResultsScheduled) { public void processResult(BatchResult batchResult, boolean processResultsScheduled) {
assert (batchResult != null); assert (batchResult != null);
int loop = 0; int loop = 0;
for (@SuppressWarnings("rawtypes") for (DispatchCall<?, ?, CTX> call : this.actionQueue) {
DispatchCall call : this.actionQueue) {
Result result = batchResult.getResult(loop); Result result = batchResult.getResult(loop);
Throwable exception = batchResult.getException(loop); Throwable exception = batchResult.getException(loop);
@@ -90,17 +90,24 @@ public final class DispatchCallStack<CTX extends DispatchContext> {
} }
} }
@SuppressWarnings({"rawtypes", "unchecked"})
/**
* Scheduled command wrapping the callback method call.
*/
private class CallbackCommand implements ScheduledCommand { private class CallbackCommand implements ScheduledCommand {
@SuppressWarnings("rawtypes")
private final DispatchCall call; private final DispatchCall call;
private final Result result; private final Result result;
private final Throwable exception; private final Throwable exception;
@SuppressWarnings("rawtypes")
public CallbackCommand(DispatchCall call, Result result, Throwable exception) { public CallbackCommand(DispatchCall call, Result result, Throwable exception) {
this.call = call; this.call = call;
this.result = result; this.result = result;
this.exception = exception; this.exception = exception;
} }
@SuppressWarnings("unchecked")
@Override @Override
public void execute() { public void execute() {
if (hasFailed()) { if (hasFailed()) {
@@ -1,5 +1,8 @@
package com.sap.sse.gwt.dispatch.client.batching; package com.sap.sse.gwt.dispatch.client.batching;
/**
* Marker interface to identify actions that must not be batched
*/
public interface NonBatchableAction { public interface NonBatchableAction {
} }
@@ -8,6 +8,9 @@ import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand;
/**
* Convenience to enqueue and execute ScheduledCommands using the GWT scheduler.
*/
public class SplitScheduler { public class SplitScheduler {
private static final SplitScheduler INSTANCE = new SplitScheduler(); private static final SplitScheduler INSTANCE = new SplitScheduler();
@@ -16,6 +19,12 @@ public class SplitScheduler {
private boolean scheduled = false; private boolean scheduled = false;
private SplitScheduler() {
}
/**
* Repeating command instance that picks up the next element in the queue of pending commands and executes it.
*/
private final RepeatingCommand repeatingCommand = new RepeatingCommand() { private final RepeatingCommand repeatingCommand = new RepeatingCommand() {
@Override @Override
public boolean execute() { public boolean execute() {
@@ -33,13 +42,15 @@ public class SplitScheduler {
} }
}; };
private SplitScheduler() {
}
public static SplitScheduler get() { public static SplitScheduler get() {
return INSTANCE; return INSTANCE;
} }
/**
* Add a command to the queue and executes it using the gwt scheduler.
*
* @param command
*/
public void schedule(ScheduledCommand command) { public void schedule(ScheduledCommand command) {
pendingCommands.add(command); pendingCommands.add(command);
@@ -8,29 +8,42 @@ import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sse.common.Duration; import com.sap.sse.common.Duration;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchAsync; import com.sap.sse.gwt.dispatch.client.DispatchSystemAsync;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
/** /**
* Delegating dispatch implementation that caches results if actions implement IsClientCache interface. * Delegating dispatch implementation that caches results if actions implement IsClientCache interface.
* *
* @author pgtaboada
*/ */
public final class CachingDispatch<CTX extends DispatchContext> implements DispatchAsync<CTX> { public final class CachingDispatch<CTX extends DispatchContext> implements DispatchSystemAsync<CTX> {
private static final Logger LOG = Logger.getLogger(CachingDispatch.class.getName()); private static final Logger LOG = Logger.getLogger(CachingDispatch.class.getName());
private final CacheCleanupTask invalidationTask; private final CacheCleanupTask invalidationTask;
private final HashMap<String, ResultHolder> resultsCache = new HashMap<>(); private final HashMap<String, ResultHolder> resultsCache = new HashMap<>();
private final DispatchAsync<CTX> dispatch; private final DispatchSystemAsync<CTX> dispatch;
private final int defaultTimeToLive; private final int defaultTimeToLive;
public CachingDispatch(DispatchAsync<CTX> service) { /**
* Create new caching dispatch instance with default values.
*
* @param service
* the underlying service used to make dispatch calls
*/
public CachingDispatch(DispatchSystemAsync<CTX> service) {
this(service, true, (int) Duration.ONE_MINUTE.times(3).asMillis()); this(service, true, (int) Duration.ONE_MINUTE.times(3).asMillis());
} }
public CachingDispatch(DispatchAsync<CTX> service, boolean enableCleanup, int defaultTimeToLive) { /**
* Create new caching dispatch instance with given parameters
*
* @param service
* the underlying service used to make dispatch calls
* @param enableCleanup
* enable cleanup cached elements
* @param defaultTimeToLive
* default cached element time to live
*/
public CachingDispatch(DispatchSystemAsync<CTX> service, boolean enableCleanup, int defaultTimeToLive) {
this.dispatch = service; this.dispatch = service;
this.defaultTimeToLive = defaultTimeToLive; this.defaultTimeToLive = defaultTimeToLive;
if (enableCleanup) { if (enableCleanup) {
@@ -60,21 +73,23 @@ public final class CachingDispatch<CTX extends DispatchContext> implements Dispa
}; };
/** /**
* Perform dispatch call with intercepting callback. * Perform dispatch call with using an intercepting callback for client cacheable commands.
* *
* @param action * @param action
* @param callback * @param callback
*/ */
private <R extends Result, A extends Action<R, CTX>> void executeAndCache(final A action, private <R extends Result, A extends Action<R, CTX>> void executeAndCache(final A action,
final AsyncCallback<R> callback) { final AsyncCallback<R> callback) {
if (action instanceof IsClientCacheable) {
dispatch.execute(action, new InterceptingCallback<>(action, callback)); dispatch.execute(action, new InterceptingCallback<>(action, callback));
} else {
dispatch.execute(action, callback);
}
} }
/** /**
* Intercepting callback that stores the result into cache. * Intercepting callback that stores the result into cache.
* *
* @author pgtaboada
*
* @param <R> * @param <R>
* @param <A> * @param <A>
*/ */
@@ -89,26 +104,35 @@ public final class CachingDispatch<CTX extends DispatchContext> implements Dispa
@Override @Override
public void onSuccess(R result) { public void onSuccess(R result) {
if (action instanceof IsClientCacheable) {
final IsClientCacheable clientCacheableAction = (IsClientCacheable) action; final IsClientCacheable clientCacheableAction = (IsClientCacheable) action;
final String instanceKey = key(clientCacheableAction); final String instanceKey = key(clientCacheableAction);
int cacheTotalTimeToLiveMillis = timeToLive(action, result);
resultsCache.put(instanceKey, new ResultHolder(System.currentTimeMillis() + cacheTotalTimeToLiveMillis,
result));
LOG.finest("Added " + instanceKey + " to cache, ttl: " + cacheTotalTimeToLiveMillis + "ms");
if (invalidationTask != null && !invalidationTask.isRunning()) {
LOG.finest("Schedule cache cleanup");
invalidationTask.schedule((int) Duration.ONE_MINUTE.times(5).asMillis());
}
callback.onSuccess(result);
}
/**
* Calculate time to live fore result
*
* @param action
* @param result
* @return
*/
private int timeToLive(A action, R result) {
int cacheTotalTimeToLiveMillis = defaultTimeToLive; int cacheTotalTimeToLiveMillis = defaultTimeToLive;
if (result instanceof HasClientCacheTotalTimeToLive) { if (result instanceof HasClientCacheTotalTimeToLive) {
cacheTotalTimeToLiveMillis = ((HasClientCacheTotalTimeToLive) result).cacheTotalTimeToLiveMillis(); cacheTotalTimeToLiveMillis = ((HasClientCacheTotalTimeToLive) result).cacheTotalTimeToLiveMillis();
} else if (action instanceof HasClientCacheTotalTimeToLive) { } else if (action instanceof HasClientCacheTotalTimeToLive) {
cacheTotalTimeToLiveMillis = ((HasClientCacheTotalTimeToLive) action).cacheTotalTimeToLiveMillis(); cacheTotalTimeToLiveMillis = ((HasClientCacheTotalTimeToLive) action).cacheTotalTimeToLiveMillis();
} }
resultsCache.put(instanceKey, new ResultHolder(System.currentTimeMillis() return cacheTotalTimeToLiveMillis;
+ cacheTotalTimeToLiveMillis, result));
LOG.finest("Added " + instanceKey + " to cache, ttl: " + cacheTotalTimeToLiveMillis + "ms");
if (invalidationTask != null && !invalidationTask.isRunning()) {
LOG.finest("Schedule cache cleanup");
invalidationTask.schedule((int) Duration.ONE_MINUTE.times(5).asMillis());
} }
}
callback.onSuccess(result);
}
@Override @Override
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
callback.onFailure(caught); callback.onFailure(caught);
@@ -130,8 +154,6 @@ public final class CachingDispatch<CTX extends DispatchContext> implements Dispa
/** /**
* Holder class used to store results in cache. * Holder class used to store results in cache.
* *
* @author pgtaboada
*
*/ */
private static class ResultHolder { private static class ResultHolder {
final Result payload; final Result payload;
@@ -168,8 +190,6 @@ public final class CachingDispatch<CTX extends DispatchContext> implements Dispa
/** /**
* Removes due elements from resultscache. * Removes due elements from resultscache.
* *
* @author pgtaboada
*
*/ */
private class CacheCleanupTask extends Timer { private class CacheCleanupTask extends Timer {
@Override @Override
@@ -1,9 +1,7 @@
package com.sap.sse.gwt.dispatch.client.caching; package com.sap.sse.gwt.dispatch.client.caching;
/** /**
* Interface used by actions and results to override default cache time to live. * Interface used by actions and results to provide cache time to live.
*
* @author pgtaboada
* *
*/ */
public interface HasClientCacheTotalTimeToLive { public interface HasClientCacheTotalTimeToLive {
@@ -3,13 +3,14 @@ package com.sap.sse.gwt.dispatch.client.caching;
/** /**
* Cache interface used by the caching dispatch to identify which command can be cached on client side. * Cache interface used by the caching dispatch to identify which command can be cached on client side.
* *
* @author pgtaboada * Each command must provide an instance key used to store the results in the cache.
* *
* The instance key is appended to an class identifying key.
*/ */
public interface IsClientCacheable { public interface IsClientCacheable {
/** /**
* The instance specific key used to identify instance specific results in the cache. * The builder used to identify instance specific results in the cache.
* *
* @return * @return
*/ */
@@ -1,16 +1,25 @@
package com.sap.sse.gwt.dispatch.client.exceptions; package com.sap.sse.gwt.dispatch.client.exceptions;
import java.util.UUID;
import com.google.gwt.core.shared.GwtIncompatible;
import com.google.gwt.user.client.rpc.IsSerializable; import com.google.gwt.user.client.rpc.IsSerializable;
public class DispatchException extends RuntimeException implements IsSerializable { public class DispatchException extends RuntimeException implements IsSerializable {
private static final long serialVersionUID = 8192187255698006941L; private static final long serialVersionUID = 8192187255698006941L;
private String exceptionId;
protected DispatchException() { protected DispatchException() {
} }
@GwtIncompatible
public DispatchException(String message) { public DispatchException(String message) {
super(message); super(message);
exceptionId = UUID.randomUUID().toString();
}
public String getExceptionId() {
return exceptionId;
} }
} }
@@ -1,28 +1,17 @@
package com.sap.sse.gwt.dispatch.client.exceptions; package com.sap.sse.gwt.dispatch.client.exceptions;
import java.util.UUID; import com.google.gwt.core.shared.GwtIncompatible;
public class ServerDispatchException extends DispatchException { public class ServerDispatchException extends DispatchException {
private static final long serialVersionUID = 8192187255698006941L; private static final long serialVersionUID = 8192187255698006941L;
private String uuid;
protected ServerDispatchException() { protected ServerDispatchException() {
} }
public ServerDispatchException(String uuid, Throwable cause) { @GwtIncompatible
super(cause.getMessage());
this.uuid = uuid;
}
public ServerDispatchException(Throwable cause) { public ServerDispatchException(Throwable cause) {
super(cause.getMessage()); super(cause.getMessage());
this.uuid = UUID.randomUUID().toString();
}
public String getUuid() {
return uuid;
} }
} }
@@ -2,9 +2,15 @@ package com.sap.sse.gwt.dispatch.client.impl;
import java.util.Date; import java.util.Date;
import com.sap.sse.gwt.dispatch.client.DispatchAsync; import com.sap.sse.gwt.dispatch.client.DispatchSystemAsync;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
public interface DispatchSystem<CTX extends DispatchContext> extends DispatchAsync<CTX> { /**
* The dispatch system is the client side implementation of the dispatch communication pattern.
*
* @param <CTX>
* the context that will be provided to the actions during execution
*/
public interface DispatchSystem<CTX extends DispatchContext> extends DispatchSystemAsync<CTX> {
Date getCurrentServerTime(); Date getCurrentServerTime();
} }
@@ -1,43 +1,62 @@
package com.sap.sse.gwt.dispatch.client.impl; package com.sap.sse.gwt.dispatch.client.impl;
import java.util.Date; import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gwt.core.shared.GWT; import com.google.gwt.core.client.Scheduler;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchAsync; import com.sap.sse.gwt.dispatch.client.DispatchSystemAsync;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.batching.AutomaticBatchingDispatch; import com.sap.sse.gwt.dispatch.client.batching.AutomaticBatchingDispatch;
import com.sap.sse.gwt.dispatch.client.caching.CachingDispatch; import com.sap.sse.gwt.dispatch.client.caching.CachingDispatch;
import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException;
import com.sap.sse.gwt.dispatch.client.exceptions.ServerDispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.ServerDispatchException;
import com.sap.sse.gwt.dispatch.client.rpcimpl.SimpleDispatch; import com.sap.sse.gwt.dispatch.client.rpcimpl.SimpleDispatch;
public class DispatchSystemImpl<CTX extends DispatchContext> implements DispatchSystem<CTX> { /**
* Base implementation of a client side dispatch executor.
*
* The dispatch system implements caching {@link CachingDispatch} and automatic batching
* {@link AutomaticBatchingDispatch}.
*
* @param <CTX>
*/
public abstract class DispatchSystemImpl<CTX extends DispatchContext> implements DispatchSystem<CTX> {
private final Logger LOG = Logger.getLogger(DispatchSystemImpl.class.getName());
private final SimpleDispatch<CTX> simpleDispatch; private final SimpleDispatch<CTX> simpleDispatch;
private final DispatchAsync<CTX> dispatch; private final DispatchSystemAsync<CTX> dispatch;
public DispatchSystemImpl(String dispatchRPCPath) { public DispatchSystemImpl(String dispatchRPCPath) {
this(dispatchRPCPath, false); this(dispatchRPCPath, false);
} }
/**
*
* Create a dispatch system
*
* @param dispatchRPCPath
* rpc-servlet path
* @param processResultsScheduled
* use a {@link Scheduler} to process the results
*/
public DispatchSystemImpl(String dispatchRPCPath, boolean processResultsScheduled) { public DispatchSystemImpl(String dispatchRPCPath, boolean processResultsScheduled) {
simpleDispatch = new SimpleDispatch<CTX>(dispatchRPCPath); simpleDispatch = new SimpleDispatch<CTX>(dispatchRPCPath);
dispatch = new CachingDispatch<CTX>(new AutomaticBatchingDispatch<CTX>( dispatch = new CachingDispatch<CTX>(new AutomaticBatchingDispatch<CTX>(
simpleDispatch, processResultsScheduled)); simpleDispatch, processResultsScheduled));
LOG.finest("Started dispatch system for " + dispatchRPCPath);
} }
@Override @Override
public <R extends Result, A extends Action<R, CTX>> void execute(final A action, final AsyncCallback<R> callback) { public <R extends Result, A extends Action<R, CTX>> void execute(final A action, final AsyncCallback<R> callback) {
// TODO: client side execution time logging final AsyncCallback<R> wrappedCallback = new AsyncCallback<R>() {
AsyncCallback<R> wrappedCallback = new AsyncCallback<R>() {
@Override @Override
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
if (caught instanceof ServerDispatchException) { if (caught instanceof DispatchException) {
ServerDispatchException sde = (ServerDispatchException) caught; ServerDispatchException sde = (ServerDispatchException) caught;
// TODO: browser console log LOG.log(Level.SEVERE, "Server exception with id: " + sde.getExceptionId());
GWT.log("Server exception with id: " + sde.getUuid());
} }
callback.onFailure(caught); callback.onFailure(caught);
} }
@@ -4,9 +4,13 @@ import com.google.gwt.user.client.rpc.RemoteService;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.ResultWrapper;
import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException; import com.sap.sse.gwt.dispatch.client.exceptions.DispatchException;
/**
* GWT RPC interfaces for dispatch communication
*
* @param <CTX>
*/
public interface DispatchRPC<CTX extends DispatchContext> extends RemoteService { public interface DispatchRPC<CTX extends DispatchContext> extends RemoteService {
<R extends Result, A extends Action<R, CTX>> ResultWrapper<R> execute( <R extends Result, A extends Action<R, CTX>> ResultWrapper<R> execute(
@@ -4,8 +4,12 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.ResultWrapper;
/**
* Async interface
*
* @param <CTX>
*/
public interface DispatchRPCAsync<CTX extends DispatchContext> { public interface DispatchRPCAsync<CTX extends DispatchContext> {
<R extends Result, A extends Action<R, CTX>> void execute( <R extends Result, A extends Action<R, CTX>> void execute(
@@ -7,6 +7,16 @@ import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
/**
* Wrapper that encapsulates an action to be sent to the server.
*
* @param <A>
* Action
* @param <R>
* Result
* @param <CTX>
* Execution context
*/
public class RequestWrapper<R extends Result, A extends Action<R, CTX>, CTX extends DispatchContext> implements public class RequestWrapper<R extends Result, A extends Action<R, CTX>, CTX extends DispatchContext> implements
IsSerializable { IsSerializable {
@@ -1,9 +1,15 @@
package com.sap.sse.gwt.dispatch.client; package com.sap.sse.gwt.dispatch.client.rpcimpl;
import java.util.Date; import java.util.Date;
import com.google.gwt.user.client.rpc.IsSerializable; import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sse.gwt.dispatch.client.Result;
/**
* Result wrapper that encapsulates the curent server time.
*
* @param <R>
*/
public class ResultWrapper<R extends Result> implements IsSerializable { public class ResultWrapper<R extends Result> implements IsSerializable {
private Date currentServerTime = new Date(); private Date currentServerTime = new Date();
@@ -5,12 +5,16 @@ import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.sap.sse.gwt.dispatch.client.Action; import com.sap.sse.gwt.dispatch.client.Action;
import com.sap.sse.gwt.dispatch.client.DispatchAsync; import com.sap.sse.gwt.dispatch.client.DispatchSystemAsync;
import com.sap.sse.gwt.dispatch.client.DispatchContext; import com.sap.sse.gwt.dispatch.client.DispatchContext;
import com.sap.sse.gwt.dispatch.client.Result; import com.sap.sse.gwt.dispatch.client.Result;
import com.sap.sse.gwt.dispatch.client.ResultWrapper;
public class SimpleDispatch<CTX extends DispatchContext> implements DispatchAsync<CTX> { /**
* Simple dispatch implementation that uses GWT RPC internally to communicate with the server
*
* @param <CTX>
*/
public class SimpleDispatch<CTX extends DispatchContext> implements DispatchSystemAsync<CTX> {
private final DispatchRPCAsync<CTX> dispatchRPC = GWT.create(DispatchRPC.class); private final DispatchRPCAsync<CTX> dispatchRPC = GWT.create(DispatchRPC.class);
@@ -22,7 +26,8 @@ public class SimpleDispatch<CTX extends DispatchContext> implements DispatchAsyn
@Override @Override
public <R extends Result, A extends Action<R, CTX>> void execute(A action, final AsyncCallback<R> callback) { public <R extends Result, A extends Action<R, CTX>> void execute(A action, final AsyncCallback<R> callback) {
RequestWrapper<R, A, CTX> requestWrapper = new RequestWrapper<R, A, CTX>(action, LocaleInfo.getCurrentLocale() final RequestWrapper<R, A, CTX> requestWrapper = new RequestWrapper<R, A, CTX>(action, LocaleInfo
.getCurrentLocale()
.getLocaleName()); .getLocaleName());
final long clientTimeOnRequestStart = System.currentTimeMillis(); final long clientTimeOnRequestStart = System.currentTimeMillis();
dispatchRPC.execute(requestWrapper, new AsyncCallback<ResultWrapper<R>>() { dispatchRPC.execute(requestWrapper, new AsyncCallback<ResultWrapper<R>>() {