From 3bed0fbab4a48e19c9a0b613cf61d4b0780dca29 Mon Sep 17 00:00:00 2001 From: Harald Pehl Date: Mon, 19 Nov 2012 17:44:16 +0100 Subject: [PATCH] New resource and repository methods to control the activity lifecycle on the server instead on the client side --- .../activity/dispatch/CopyActivity.java | 20 ++ .../dispatch/CopyActivityHandler.java | 64 +++++ .../activity/dispatch/SaveActivity.java | 4 +- .../dispatch/SaveActivityHandler.java | 46 +--- .../activity/dispatch/StartActivity.java | 21 ++ .../dispatch/StartActivityHandler.java | 79 ++++++ .../activity/dispatch/StopActivity.java | 19 ++ .../dispatch/StopActivityHandler.java | 93 +++++++ .../activity/dispatch/TickActivity.java | 21 ++ .../dispatch/TickActivityHandler.java | 67 +++++ .../presenter/ActivityController.java | 260 +++++++----------- .../presenter/NewActivityPresenter.java | 5 + .../client/activity/view/DurationTextBox.java | 5 + .../client/activity/view/NewActivityView.java | 32 ++- .../dispatch/KarakaActionHandlerRegistry.java | 27 +- .../client/dispatch/KarakaCallback.java | 14 + .../pehl/karaka/client/gin/KarakaModule.java | 8 + .../activity/boundary/ActivitiesResource.java | 165 ++++++++--- .../activity/control/ActivityConverter.java | 30 +- .../activity/control/ActivityRepository.java | 98 +++++-- .../server/activity/entity/Activity.java | 156 ++++++++--- .../karaka/server/activity/entity/Time.java | 47 ++-- .../server/sampledata/ActivitiesProducer.java | 7 +- .../pehl/karaka/shared/model/Activity.java | 122 +------- .../presenter/ActivityControllerTest.java | 64 ++--- .../server/activity/entity/ActivityTest.java | 219 +++++++++++++-- .../karaka/shared/model/ActivitiesTest.java | 19 +- .../karaka/shared/model/ActivityTest.java | 113 +------- 28 files changed, 1174 insertions(+), 651 deletions(-) create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivity.java create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivityHandler.java create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivity.java create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivityHandler.java create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivity.java create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivityHandler.java create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivity.java create mode 100644 src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivityHandler.java diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivity.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivity.java new file mode 100644 index 0000000..51eafb2 --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivity.java @@ -0,0 +1,20 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.gwtplatform.dispatch.annotation.GenDispatch; +import com.gwtplatform.dispatch.annotation.In; +import com.gwtplatform.dispatch.annotation.Out; +import name.pehl.karaka.shared.model.Activity; + +/** + * Used to save new and update existing activities. + * + * @author $LastChangedBy:$ + * @version $LastChangedRevision:$ + */ +@GenDispatch +public class CopyActivity +{ + @In(1) Activity activity; + @In(2) String period; + @Out(1) Activity copy; +} diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivityHandler.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivityHandler.java new file mode 100644 index 0000000..b3b425b --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/CopyActivityHandler.java @@ -0,0 +1,64 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.inject.Inject; +import com.gwtplatform.dispatch.shared.SecurityCookie; +import com.gwtplatform.dispatch.shared.SecurityCookieAccessor; +import name.pehl.karaka.client.activity.model.ActivityReader; +import name.pehl.karaka.client.dispatch.KarakaActionHandler; +import name.pehl.karaka.client.dispatch.KarakaJsonCallback; +import name.pehl.karaka.client.rest.UrlBuilder; +import name.pehl.karaka.shared.model.Activity; +import name.pehl.piriti.json.client.JsonReader; +import org.fusesource.restygwt.client.Method; +import org.fusesource.restygwt.client.Resource; + +import static name.pehl.karaka.client.dispatch.KarakaActionHandler.HttpMethod.PUT; +import static org.fusesource.restygwt.client.Resource.CONTENT_TYPE_JSON; +import static org.fusesource.restygwt.client.Resource.HEADER_CONTENT_TYPE; + +/** + * @author $Author:$ + * @version $Date:$ $Revision:$ + */ +public class CopyActivityHandler extends KarakaActionHandler +{ + private final ActivityReader activityReader; + + + @Inject + protected CopyActivityHandler(@SecurityCookie String securityCookieName, + SecurityCookieAccessor securityCookieAccessor, ActivityReader activityReader) + { + super(CopyActivityAction.class, securityCookieName, securityCookieAccessor); + this.activityReader = activityReader; + } + + @Override + protected Resource resourceFor(CopyActivityAction action) + { + UrlBuilder urlBuilder = new UrlBuilder().module("rest") + .path("activities", "copy", action.getActivity().getId()); + return new Resource(urlBuilder.toUrl()); + } + + @Override + protected Method methodFor(CopyActivityAction action, Resource resource) + { + return new Method(resource, PUT.name()).header(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON); + } + + @Override + protected void executeMethod(final Method method, final AsyncCallback resultCallback) + { + method.send(new KarakaJsonCallback(activityReader, resultCallback) + { + @Override + protected CopyActivityResult extractResult(final JsonReader reader, final JSONObject json) + { + return new CopyActivityResult(reader.read(json)); + } + }); + } +} diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivity.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivity.java index a32f9ec..7271bda 100644 --- a/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivity.java +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivity.java @@ -15,6 +15,6 @@ @GenDispatch public class SaveActivity { - @In(1) Activity newOrModifiedActivity; - @Out(1) Activity storedActivity; + @In(1) Activity activity; + @Out(1) Activity saved; } diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivityHandler.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivityHandler.java index e9172dc..6ea6e01 100644 --- a/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivityHandler.java +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/SaveActivityHandler.java @@ -1,23 +1,19 @@ package name.pehl.karaka.client.activity.dispatch; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.inject.Inject; +import com.gwtplatform.dispatch.shared.SecurityCookie; +import com.gwtplatform.dispatch.shared.SecurityCookieAccessor; +import name.pehl.karaka.client.activity.model.ActivityReader; import name.pehl.karaka.client.dispatch.KarakaActionHandler; import name.pehl.karaka.client.dispatch.KarakaJsonCallback; -import name.pehl.piriti.json.client.JsonReader; -import name.pehl.karaka.client.activity.model.ActivityReader; -import name.pehl.karaka.client.activity.model.ActivityWriter; import name.pehl.karaka.client.rest.UrlBuilder; import name.pehl.karaka.shared.model.Activity; - +import name.pehl.piriti.json.client.JsonReader; import org.fusesource.restygwt.client.Method; import org.fusesource.restygwt.client.Resource; -import com.google.gwt.json.client.JSONObject; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.inject.Inject; -import com.gwtplatform.dispatch.shared.SecurityCookie; -import com.gwtplatform.dispatch.shared.SecurityCookieAccessor; - -import static name.pehl.karaka.client.dispatch.KarakaActionHandler.HttpMethod.POST; import static name.pehl.karaka.client.dispatch.KarakaActionHandler.HttpMethod.PUT; import static org.fusesource.restygwt.client.Resource.CONTENT_TYPE_JSON; import static org.fusesource.restygwt.client.Resource.HEADER_CONTENT_TYPE; @@ -29,50 +25,28 @@ public class SaveActivityHandler extends KarakaActionHandler { private final ActivityReader activityReader; - private final ActivityWriter activityWriter; @Inject protected SaveActivityHandler(@SecurityCookie String securityCookieName, - SecurityCookieAccessor securityCookieAccessor, ActivityReader activityReader, ActivityWriter activityWriter) + SecurityCookieAccessor securityCookieAccessor, ActivityReader activityReader) { super(SaveActivityAction.class, securityCookieName, securityCookieAccessor); this.activityReader = activityReader; - this.activityWriter = activityWriter; } @Override protected Resource resourceFor(SaveActivityAction action) { - UrlBuilder urlBuilder = null; - Activity newOrModifiedActivity = action.getNewOrModifiedActivity(); - if (newOrModifiedActivity.isTransient()) - { - urlBuilder = new UrlBuilder().module("rest").path("activities"); - } - else - { - urlBuilder = new UrlBuilder().module("rest").path("activities", newOrModifiedActivity.getId()); - } - return new Resource(urlBuilder.toUrl()); + return new Resource(new UrlBuilder().module("rest").path("activities", action.getActivity().getId()).toUrl()); } @Override protected Method methodFor(SaveActivityAction action, Resource resource) { - Method method = null; - Activity newOrModifiedActivity = action.getNewOrModifiedActivity(); - if (newOrModifiedActivity.isTransient()) - { - method = new Method(resource, POST.name()); - } - else - { - method = new Method(resource, PUT.name()); - } - return method.header(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON).text(activityWriter.toJson(newOrModifiedActivity)); + return new Method(resource, PUT.name()).header(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON); } diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivity.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivity.java new file mode 100644 index 0000000..1064a6d --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivity.java @@ -0,0 +1,21 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.gwtplatform.dispatch.annotation.GenDispatch; +import com.gwtplatform.dispatch.annotation.In; +import com.gwtplatform.dispatch.annotation.Out; +import name.pehl.karaka.shared.model.Activity; + +import java.util.Set; + +/** + * Used to save new and update existing activities. + * + * @author $LastChangedBy:$ + * @version $LastChangedRevision:$ + */ +@GenDispatch +public class StartActivity +{ + @In(1) Activity activity; + @Out(1) Set modified; +} diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivityHandler.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivityHandler.java new file mode 100644 index 0000000..147dcd9 --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/StartActivityHandler.java @@ -0,0 +1,79 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.inject.Inject; +import com.gwtplatform.dispatch.shared.SecurityCookie; +import com.gwtplatform.dispatch.shared.SecurityCookieAccessor; +import name.pehl.karaka.client.activity.model.ActivityReader; +import name.pehl.karaka.client.activity.model.ActivityWriter; +import name.pehl.karaka.client.dispatch.KarakaActionHandler; +import name.pehl.karaka.client.dispatch.KarakaJsonCallback; +import name.pehl.karaka.client.rest.UrlBuilder; +import name.pehl.karaka.shared.model.Activity; +import name.pehl.piriti.json.client.JsonReader; +import org.fusesource.restygwt.client.Method; +import org.fusesource.restygwt.client.Resource; + +import java.util.HashSet; + +import static name.pehl.karaka.client.dispatch.KarakaActionHandler.HttpMethod.PUT; +import static org.fusesource.restygwt.client.Resource.CONTENT_TYPE_JSON; +import static org.fusesource.restygwt.client.Resource.HEADER_CONTENT_TYPE; + +/** + * @author $Author:$ + * @version $Date:$ $Revision:$ + */ +public class StartActivityHandler extends KarakaActionHandler +{ + private final ActivityReader activityReader; + private final ActivityWriter activityWriter; + + + @Inject + protected StartActivityHandler(@SecurityCookie String securityCookieName, + SecurityCookieAccessor securityCookieAccessor, ActivityReader activityReader, ActivityWriter activityWriter) + { + super(StartActivityAction.class, securityCookieName, securityCookieAccessor); + this.activityReader = activityReader; + this.activityWriter = activityWriter; + } + + @Override + protected Resource resourceFor(StartActivityAction action) + { + Activity activity = action.getActivity(); + UrlBuilder urlBuilder = new UrlBuilder().module("rest").path("activities", "start"); + if (!activity.isTransient()) + { + urlBuilder = urlBuilder.path(activity.getId()); + } + return new Resource(urlBuilder.toUrl()); + } + + @Override + protected Method methodFor(StartActivityAction action, Resource resource) + { + Activity activity = action.getActivity(); + Method method = new Method(resource, PUT.name()).header(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON); + if (action.getActivity().isTransient()) + { + method = method.text(activityWriter.toJson(activity)); + } + return method; + } + + @Override + protected void executeMethod(final Method method, final AsyncCallback resultCallback) + { + method.send(new KarakaJsonCallback(activityReader, resultCallback) + { + @Override + protected StartActivityResult extractResult(final JsonReader reader, final JSONObject json) + { + return new StartActivityResult(new HashSet(reader.readList(json))); + } + }); + } +} diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivity.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivity.java new file mode 100644 index 0000000..09696b7 --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivity.java @@ -0,0 +1,19 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.gwtplatform.dispatch.annotation.GenDispatch; +import com.gwtplatform.dispatch.annotation.In; +import com.gwtplatform.dispatch.annotation.Out; +import name.pehl.karaka.shared.model.Activity; + +/** + * Used to save new and update existing activities. + * + * @author $LastChangedBy:$ + * @version $LastChangedRevision:$ + */ +@GenDispatch +public class StopActivity +{ + @In(1) Activity activity; + @Out(1) Activity stopped; +} diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivityHandler.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivityHandler.java new file mode 100644 index 0000000..08d0938 --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/StopActivityHandler.java @@ -0,0 +1,93 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.google.gwt.http.client.Request; +import com.google.gwt.http.client.RequestCallback; +import com.google.gwt.http.client.RequestException; +import com.google.gwt.http.client.Response; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.inject.Inject; +import com.gwtplatform.dispatch.shared.SecurityCookie; +import com.gwtplatform.dispatch.shared.SecurityCookieAccessor; +import name.pehl.karaka.client.activity.model.ActivityReader; +import name.pehl.karaka.client.dispatch.KarakaActionHandler; +import name.pehl.karaka.client.dispatch.KarakaJsonCallback; +import name.pehl.karaka.client.rest.UrlBuilder; +import name.pehl.karaka.shared.model.Activity; +import name.pehl.piriti.json.client.JsonReader; +import org.fusesource.restygwt.client.Method; +import org.fusesource.restygwt.client.Resource; + +import static name.pehl.karaka.client.dispatch.KarakaActionHandler.HttpMethod.PUT; +import static org.fusesource.restygwt.client.Resource.CONTENT_TYPE_JSON; +import static org.fusesource.restygwt.client.Resource.HEADER_CONTENT_TYPE; + +/** + * @author $Author:$ + * @version $Date:$ $Revision:$ + */ +public class StopActivityHandler extends KarakaActionHandler +{ + private final ActivityReader activityReader; + + @Inject + protected StopActivityHandler(@SecurityCookie String securityCookieName, + SecurityCookieAccessor securityCookieAccessor, ActivityReader activityReader) + { + super(StopActivityAction.class, securityCookieName, securityCookieAccessor); + this.activityReader = activityReader; + } + + + @Override + protected Resource resourceFor(StopActivityAction action) + { + UrlBuilder urlBuilder = new UrlBuilder().module("rest").path("activities", "stop", action.getActivity().getId()); + return new Resource(urlBuilder.toUrl()); + } + + + @Override + protected Method methodFor(StopActivityAction action, Resource resource) + { + return new Method(resource, PUT.name()).header(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON); + } + + + @Override + protected void executeMethod(final Method method, final AsyncCallback resultCallback) + { + method.send(new KarakaJsonCallback(activityReader, resultCallback) + { + @Override + protected StopActivityResult extractResult(final JsonReader reader, final JSONObject json) + { + return new StopActivityResult(reader.read(json)); + } + }); + + + try + { + method.send(new RequestCallback() + { + @Override + public void onResponseReceived(Request request, Response response) + { + resultCallback.onSuccess(new StopActivityResult()); + } + + + @Override + public void onError(Request request, Throwable exception) + { + resultCallback.onFailure(exception); + } + }); + } + catch (RequestException e) + { + resultCallback.onFailure(e); + } + } +} diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivity.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivity.java new file mode 100644 index 0000000..2956c9e --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivity.java @@ -0,0 +1,21 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.gwtplatform.dispatch.annotation.GenDispatch; +import com.gwtplatform.dispatch.annotation.In; +import com.gwtplatform.dispatch.annotation.Out; +import name.pehl.karaka.shared.model.Activity; + +import java.util.Set; + +/** + * Used to save new and update existing activities. + * + * @author $LastChangedBy:$ + * @version $LastChangedRevision:$ + */ +@GenDispatch +public class TickActivity +{ + @In(1) Activity activity; + @Out(1) Set modified; +} diff --git a/src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivityHandler.java b/src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivityHandler.java new file mode 100644 index 0000000..c267e15 --- /dev/null +++ b/src/main/java/name/pehl/karaka/client/activity/dispatch/TickActivityHandler.java @@ -0,0 +1,67 @@ +package name.pehl.karaka.client.activity.dispatch; + +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.inject.Inject; +import com.gwtplatform.dispatch.shared.SecurityCookie; +import com.gwtplatform.dispatch.shared.SecurityCookieAccessor; +import name.pehl.karaka.client.activity.model.ActivityReader; +import name.pehl.karaka.client.dispatch.KarakaActionHandler; +import name.pehl.karaka.client.dispatch.KarakaJsonCallback; +import name.pehl.karaka.client.rest.UrlBuilder; +import name.pehl.karaka.shared.model.Activity; +import name.pehl.piriti.json.client.JsonReader; +import org.fusesource.restygwt.client.Method; +import org.fusesource.restygwt.client.Resource; + +import java.util.HashSet; + +import static name.pehl.karaka.client.dispatch.KarakaActionHandler.HttpMethod.PUT; +import static org.fusesource.restygwt.client.Resource.CONTENT_TYPE_JSON; +import static org.fusesource.restygwt.client.Resource.HEADER_CONTENT_TYPE; + +/** + * @author $Author:$ + * @version $Date:$ $Revision:$ + */ +public class TickActivityHandler extends KarakaActionHandler +{ + private final ActivityReader activityReader; + + @Inject + protected TickActivityHandler(@SecurityCookie String securityCookieName, + SecurityCookieAccessor securityCookieAccessor, ActivityReader activityReader) + { + super(TickActivityAction.class, securityCookieName, securityCookieAccessor); + this.activityReader = activityReader; + } + + + @Override + protected Resource resourceFor(TickActivityAction action) + { + UrlBuilder urlBuilder = new UrlBuilder().module("rest").path("activities", "tick", action.getActivity().getId()); + return new Resource(urlBuilder.toUrl()); + } + + + @Override + protected Method methodFor(TickActivityAction action, Resource resource) + { + return new Method(resource, PUT.name()).header(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON); + } + + + @Override + protected void executeMethod(final Method method, final AsyncCallback resultCallback) + { + method.send(new KarakaJsonCallback(activityReader, resultCallback) + { + @Override + protected TickActivityResult extractResult(final JsonReader reader, final JSONObject json) + { + return new TickActivityResult(new HashSet(reader.readList(json))); + } + }); + } +} diff --git a/src/main/java/name/pehl/karaka/client/activity/presenter/ActivityController.java b/src/main/java/name/pehl/karaka/client/activity/presenter/ActivityController.java index 1346f96..bafe262 100644 --- a/src/main/java/name/pehl/karaka/client/activity/presenter/ActivityController.java +++ b/src/main/java/name/pehl/karaka/client/activity/presenter/ActivityController.java @@ -1,7 +1,5 @@ package name.pehl.karaka.client.activity.presenter; -import com.google.common.base.Predicate; -import com.google.common.collect.Sets; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.event.shared.GwtEvent; @@ -9,10 +7,18 @@ import com.google.inject.Inject; import com.google.web.bindery.event.shared.EventBus; import com.gwtplatform.dispatch.shared.DispatchAsync; +import name.pehl.karaka.client.activity.dispatch.CopyActivityAction; +import name.pehl.karaka.client.activity.dispatch.CopyActivityResult; import name.pehl.karaka.client.activity.dispatch.DeleteActivityAction; import name.pehl.karaka.client.activity.dispatch.DeleteActivityResult; import name.pehl.karaka.client.activity.dispatch.SaveActivityAction; import name.pehl.karaka.client.activity.dispatch.SaveActivityResult; +import name.pehl.karaka.client.activity.dispatch.StartActivityAction; +import name.pehl.karaka.client.activity.dispatch.StartActivityResult; +import name.pehl.karaka.client.activity.dispatch.StopActivityAction; +import name.pehl.karaka.client.activity.dispatch.StopActivityResult; +import name.pehl.karaka.client.activity.dispatch.TickActivityAction; +import name.pehl.karaka.client.activity.dispatch.TickActivityResult; import name.pehl.karaka.client.activity.event.ActivitiesLoadedEvent; import name.pehl.karaka.client.activity.event.ActivitiesLoadedEvent.ActivitiesLoadedHandler; import name.pehl.karaka.client.activity.event.ActivityAction.Action; @@ -32,15 +38,13 @@ import name.pehl.karaka.shared.model.Project; import name.pehl.karaka.shared.model.Tag; -import javax.annotation.Nullable; import java.util.List; -import java.util.SortedSet; +import java.util.Set; import static java.util.logging.Level.INFO; import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.*; import static name.pehl.karaka.client.logging.Logger.Category.activity; import static name.pehl.karaka.client.logging.Logger.info; -import static name.pehl.karaka.client.logging.Logger.trace; /** *

@@ -69,10 +73,10 @@ * *

Dispatcher actions

*
    - *
  • {@linkplain SaveActivityAction}
  • *
  • {@linkplain DeleteActivityAction}
  • + *
  • {@linkplain SaveActivityAction}
  • *
- * + * * @author $Author: harald.pehl $ * @version $Date: 2010-12-23 13:52:44 +0100 (Do, 23. Dez 2010) $ $Revision: 192 * $ @@ -83,22 +87,18 @@ public class ActivityController implements RepeatingCommand, HasHandlers, Runnin // ------------------------------------------------------- (static) members static final int TICK_INTERVAL = 60 * 1000; - static final long ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000; - + static final String ONE_DAY = "P1D"; final EventBus eventBus; final Scheduler scheduler; final DispatchAsync dispatcher; - /** * Should I tick? */ boolean ticking; - /** * The currently running actvity. Null if no activity is running. */ Activity runningActivity; - /** * The currently displayed activities */ @@ -107,6 +107,7 @@ public class ActivityController implements RepeatingCommand, HasHandlers, Runnin // ------------------------------------------------------------------ setup + @Inject public ActivityController(final EventBus eventBus, final Scheduler scheduler, final DispatchAsync dispatcher) { @@ -116,7 +117,6 @@ public ActivityController(final EventBus eventBus, final Scheduler scheduler, fi this.dispatcher = dispatcher; } - public void start() { eventBus.addHandler(RunningActivityLoadedEvent.getType(), this); @@ -133,7 +133,6 @@ public void fireEvent(GwtEvent event) eventBus.fireEventFromSource(event, this); } - @Override public void onRunningActivityLoaded(RunningActivityLoadedEvent event) { @@ -143,14 +142,12 @@ public void onRunningActivityLoaded(RunningActivityLoadedEvent event) tick(); } - @Override public void onActivitiesLoaded(ActivitiesLoadedEvent event) { activities = event.getActivities(); } - @Override public void onActivityAction(ActivityActionEvent event) { @@ -187,13 +184,14 @@ public void onActivityAction(ActivityActionEvent event) void save(final Activity activityToSave) { - trace(activity, "About to save " + activityToSave); + info(activity, "About to save " + activityToSave); dispatcher.execute(new SaveActivityAction(activityToSave), new KarakaCallback(eventBus) { @Override public void onSuccess(SaveActivityResult result) { - Activity savedActivity = result.getStoredActivity(); + Activity savedActivity = result.getSaved(); + info(activity, activityToSave + " successfully saved as " + savedActivity); updateActivities(activityToSave, savedActivity); ActivityChangedEvent.fire(ActivityController.this, CHANGED, savedActivity, activities); ShowMessageEvent.fire(ActivityController.this, @@ -203,124 +201,66 @@ public void onSuccess(SaveActivityResult result) }); } - - void copy(Activity activityToCopy) - { - trace(activity, "About to copy " + activityToCopy); - Activity plusOneDay = activityToCopy.plus(ONE_DAY_IN_MILLIS); - dispatcher.execute(new SaveActivityAction(plusOneDay), new KarakaCallback(eventBus) - { - @Override - public void onSuccess(SaveActivityResult result) - { - Activity copiedActivity = result.getStoredActivity(); - updateActivities(null, copiedActivity); - ActivityChangedEvent.fire(ActivityController.this, NEW, copiedActivity, activities); - ShowMessageEvent.fire(ActivityController.this, - new Message(INFO, "Activity \"" + copiedActivity.getName() + "\" added", true)); - } - }); - } - - - void start(final Activity activityToStart) + void copy(final Activity activityToCopy) { - info(activity, "About to start " + activityToStart); - if (activityToStart.isStopped()) - { - // if there's currently another activity running, stop it. - if (runningActivity != null && runningActivity.isRunning() && !runningActivity.equals(activityToStart)) - { - info(activity, "Stopping currently running " + runningActivity); - Activity runningActivityBackup = runningActivity; - stopTicking(); - dispatcher.execute(new SaveActivityAction(runningActivityBackup), new KarakaCallback( - eventBus) + info(activity, "About to copy " + activityToCopy); + dispatcher.execute(new CopyActivityAction(activityToCopy, ONE_DAY), + new KarakaCallback(eventBus) { @Override - public void onSuccess(SaveActivityResult result) + public void onSuccess(CopyActivityResult result) { - Activity stoppedActivity = result.getStoredActivity(); - info(activity, "Successfully stopped " + stoppedActivity); - start(activityToStart); + Activity copiedActivity = result.getCopy(); + info(activity, activityToCopy + " successfully copied as " + copiedActivity); + updateActivities(null, copiedActivity); + ActivityChangedEvent.fire(ActivityController.this, NEW, copiedActivity, activities); + ShowMessageEvent.fire(ActivityController.this, + new Message(INFO, "Activity \"" + copiedActivity.getName() + "\" added", true)); } }); - } - else - { - // Is there an activity to resume? - boolean resume = false; - if (activityToStart.isToday()) - { - // Get the activities for today - SortedSet activitiesOfToday = Sets.filter(activities.activities(), - new Predicate() - { - @Override - public boolean apply(@Nullable Activity input) - { - return input != null && input.isToday(); - } - }); - resume = activitiesOfToday.contains(activityToStart); - } - if (resume) - { - info(activity, "Resuming " + activityToStart); - activityToStart.resume(); - dispatcher.execute(new SaveActivityAction(activityToStart), new KarakaCallback( - eventBus) - { - @Override - public void onSuccess(SaveActivityResult result) - { - runningActivity = result.getStoredActivity(); - updateActivities(activityToStart, runningActivity); - ActivityChangedEvent.fire(ActivityController.this, RESUMED, runningActivity, activities); - ShowMessageEvent.fire(ActivityController.this, new Message(INFO, "Activity \"" - + runningActivity.getName() + "\" resumed", true)); - checkAndrefreshProjectsAndTags(activityToStart); - startTicking(); - } - }); - } - else - { - // If the parameter is an existing activity. We have to copy - // this activity. - final Activity newActivity = activityToStart.isTransient() ? activityToStart : activityToStart - .copy(); - newActivity.start(); - info(activity, "Starting " + newActivity); - dispatcher.execute(new SaveActivityAction(newActivity), new KarakaCallback( - eventBus) + } + + void start(final Activity activityToResumeOrStart) + { + info(activity, "About to resume / start " + activityToResumeOrStart); + if (activityToResumeOrStart.isRunning()) + { + info(activity, activityToResumeOrStart + " already running"); + } + else + { + dispatcher.execute(new StartActivityAction(activityToResumeOrStart), + new KarakaCallback(eventBus) { @Override - public void onSuccess(SaveActivityResult result) + public void onSuccess(final StartActivityResult result) { - runningActivity = result.getStoredActivity(); - updateActivities(newActivity, runningActivity); - ActivityChangedEvent.fire(ActivityController.this, STARTED, runningActivity, activities); + Set modifiedActivities = result.getModified(); + runningActivity = extractRunningActivity(modifiedActivities); + boolean resumed = modifiedActivities.contains(activityToResumeOrStart); + info(activity, + activityToResumeOrStart + " successfully " + (resumed ? "resumed" : "started") + ". Modified activities: " + modifiedActivities); + updateActivities(activityToResumeOrStart, modifiedActivities.toArray(new Activity[]{})); + ActivityChangedEvent + .fire(ActivityController.this, resumed ? RESUMED : STARTED, runningActivity, + activities); ShowMessageEvent.fire(ActivityController.this, new Message(INFO, "Activity \"" - + runningActivity.getName() + "\" started", true)); - checkAndrefreshProjectsAndTags(activityToStart); + + runningActivity.getName() + "\" " + (resumed ? "resumed" : "started"), true)); + checkAndrefreshProjectsAndTags(activityToResumeOrStart); startTicking(); } }); - } - } - } - else - { - info(activity, activityToStart + " already running"); } } - void stop(final Activity activityToStop) { info(activity, "About to stop " + activityToStop); - if (activityToStop.isRunning()) + if (activityToStop.isStopped()) + { + info(activity, activityToStop + " already stopped"); + } + else { if (!activityToStop.equals(runningActivity)) { @@ -328,13 +268,14 @@ void stop(final Activity activityToStop) + " which is not the same as the internal running " + runningActivity + "!"); } stopTicking(); - activityToStop.stop(); - dispatcher.execute(new SaveActivityAction(activityToStop), new KarakaCallback(eventBus) + dispatcher.execute(new StopActivityAction(activityToStop), new KarakaCallback(eventBus) { @Override - public void onSuccess(SaveActivityResult result) + public void onSuccess(StopActivityResult result) { - Activity stoppedActivity = result.getStoredActivity(); + Activity stoppedActivity = result.getStopped(); + info(activity, + activityToStop + " successfully stopped as " + stoppedActivity); updateActivities(activityToStop, stoppedActivity); ActivityChangedEvent.fire(ActivityController.this, STOPPED, stoppedActivity, activities); ShowMessageEvent.fire(ActivityController.this, @@ -342,13 +283,8 @@ public void onSuccess(SaveActivityResult result) } }); } - else - { - info(activity, activityToStop + " already stopped"); - } } - void delete(final Activity activityToDelete) { info(activity, "About to delete " + activityToDelete); @@ -361,39 +297,44 @@ void delete(final Activity activityToDelete) } stopTicking(); } - dispatcher.execute(new DeleteActivityAction(activityToDelete), new KarakaCallback(eventBus) - { - @Override - public void onSuccess(DeleteActivityResult result) - { - updateActivities(activityToDelete, null); - ActivityChangedEvent.fire(ActivityController.this, DELETE, activityToDelete, activities); - ShowMessageEvent.fire(ActivityController.this, - new Message(INFO, "Activity \"" + activityToDelete.getName() + "\" deleted", true)); - } - }); + dispatcher + .execute(new DeleteActivityAction(activityToDelete), new KarakaCallback(eventBus) + { + @Override + public void onSuccess(DeleteActivityResult result) + { + info(activity, activityToDelete + " successfully deleted"); + updateActivities(activityToDelete); + ActivityChangedEvent.fire(ActivityController.this, DELETE, activityToDelete, activities); + ShowMessageEvent.fire(ActivityController.this, + new Message(INFO, "Activity \"" + activityToDelete.getName() + "\" deleted", true)); + } + }); } - - private void updateActivities(Activity activityBefore, Activity activityAfter) + private void updateActivities(Activity activityBefore, Activity... activitiesAfter) { if (activities.contains(activityBefore)) { activities.remove(activityBefore); } - if (activities.matchingRange(activityAfter)) + if (activitiesAfter != null) { - activities.add(activityAfter); + for (Activity activityAfter : activitiesAfter) + { + if (activities.matchingRange(activityAfter)) + { + activities.add(activityAfter); + } + } } } - /** * Called after an activity was saved successfully. If the unsaved activity - * contained transient project or tags, we have to refresh our local caches! - * - * @param activity - * The activity before it was saved + * contained a transient project or tags, we have to refresh our local caches! + * + * @param activity The activity before it was saved */ private void checkAndrefreshProjectsAndTags(Activity activity) { @@ -413,6 +354,18 @@ private void checkAndrefreshProjectsAndTags(Activity activity) } } + private Activity extractRunningActivity(final Set modifiedActivities) + { + for (Activity modifiedActivity : modifiedActivities) + { + if (modifiedActivity.isRunning()) + { + return runningActivity; + } + } + return null; + } + // ------------------------------------------------------------------- tick @@ -422,18 +375,15 @@ private void startTicking() scheduler.scheduleFixedPeriod(ActivityController.this, TICK_INTERVAL); } - /** * Stops the {@code runningActivity} and sets it to null. */ private void stopTicking() { ticking = false; - runningActivity.stop(); runningActivity = null; } - @Override public boolean execute() { @@ -444,14 +394,16 @@ private boolean tick() { if (ticking) { - info(activity, "Tick for " + runningActivity); - runningActivity.tick(); - dispatcher.execute(new SaveActivityAction(runningActivity), new KarakaCallback(eventBus) + info(activity, "About to tick " + runningActivity); + dispatcher.execute(new TickActivityAction(runningActivity), new KarakaCallback(eventBus) { @Override - public void onSuccess(SaveActivityResult result) + public void onSuccess(TickActivityResult result) { - runningActivity = result.getStoredActivity(); + Set modifiedActivities = result.getModified(); + runningActivity = extractRunningActivity(modifiedActivities); + info(activity, + runningActivity + " successfully ticked. Modified activities: " + modifiedActivities); updateActivities(runningActivity, runningActivity); TickEvent.fire(ActivityController.this, runningActivity, activities); } diff --git a/src/main/java/name/pehl/karaka/client/activity/presenter/NewActivityPresenter.java b/src/main/java/name/pehl/karaka/client/activity/presenter/NewActivityPresenter.java index 8d305d6..4d36e9e 100644 --- a/src/main/java/name/pehl/karaka/client/activity/presenter/NewActivityPresenter.java +++ b/src/main/java/name/pehl/karaka/client/activity/presenter/NewActivityPresenter.java @@ -54,6 +54,8 @@ public class NewActivityPresenter extends PresenterWidget { void setProject(Project project); + + void clear(); } final DispatchAsync dispatcher; @@ -228,10 +230,12 @@ else if (selectedProject != null) // 3. Duration if (enteredDuration == null || enteredDuration.isZero()) { + // Start the new activity ActivityActionEvent.fire(this, START_STOP, activity); } else { + // Just store the new activity if (!activity.isTransient()) { activity = activity.copy(); @@ -260,5 +264,6 @@ else if (selectedProject != null) activity.setDuration(enteredDuration); ActivityActionEvent.fire(this, SAVE, activity); } + getView().clear(); } } diff --git a/src/main/java/name/pehl/karaka/client/activity/view/DurationTextBox.java b/src/main/java/name/pehl/karaka/client/activity/view/DurationTextBox.java index b536a91..736117d 100644 --- a/src/main/java/name/pehl/karaka/client/activity/view/DurationTextBox.java +++ b/src/main/java/name/pehl/karaka/client/activity/view/DurationTextBox.java @@ -106,4 +106,9 @@ public void onValueChange(ValueChangeEvent event) // TODO Error handling } } + + public void clear() + { + textBox.setText(""); + } } diff --git a/src/main/java/name/pehl/karaka/client/activity/view/NewActivityView.java b/src/main/java/name/pehl/karaka/client/activity/view/NewActivityView.java index db83312..0c430d4 100644 --- a/src/main/java/name/pehl/karaka/client/activity/view/NewActivityView.java +++ b/src/main/java/name/pehl/karaka/client/activity/view/NewActivityView.java @@ -1,18 +1,5 @@ package name.pehl.karaka.client.activity.view; -import java.util.Date; - -import name.pehl.karaka.client.activity.presenter.NewActivityPresenter; -import name.pehl.karaka.client.activity.presenter.NewActivityUiHandlers; -import name.pehl.karaka.client.model.NamedModelSuggestOracle; -import name.pehl.karaka.client.model.NamedModelSuggestion; -import name.pehl.karaka.client.project.ProjectsCache; -import name.pehl.karaka.client.resources.Resources; -import name.pehl.karaka.client.ui.Html5TextBox; -import name.pehl.karaka.shared.model.Activity; -import name.pehl.karaka.shared.model.Duration; -import name.pehl.karaka.shared.model.Project; - import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyUpEvent; @@ -28,6 +15,18 @@ import com.google.gwt.user.client.ui.Widget; import com.google.inject.Inject; import com.gwtplatform.mvp.client.ViewWithUiHandlers; +import name.pehl.karaka.client.activity.presenter.NewActivityPresenter; +import name.pehl.karaka.client.activity.presenter.NewActivityUiHandlers; +import name.pehl.karaka.client.model.NamedModelSuggestOracle; +import name.pehl.karaka.client.model.NamedModelSuggestion; +import name.pehl.karaka.client.project.ProjectsCache; +import name.pehl.karaka.client.resources.Resources; +import name.pehl.karaka.client.ui.Html5TextBox; +import name.pehl.karaka.shared.model.Activity; +import name.pehl.karaka.shared.model.Duration; +import name.pehl.karaka.shared.model.Project; + +import java.util.Date; public class NewActivityView extends ViewWithUiHandlers implements NewActivityPresenter.MyView { @@ -107,6 +106,13 @@ public void setProject(final Project project) } } + @Override + public void clear() + { + activity.setText(""); + project.setText(""); + duration.clear(); + } // ------------------------------------------------------------ ui handlers diff --git a/src/main/java/name/pehl/karaka/client/dispatch/KarakaActionHandlerRegistry.java b/src/main/java/name/pehl/karaka/client/dispatch/KarakaActionHandlerRegistry.java index 7c0fa26..ab73f4b 100644 --- a/src/main/java/name/pehl/karaka/client/dispatch/KarakaActionHandlerRegistry.java +++ b/src/main/java/name/pehl/karaka/client/dispatch/KarakaActionHandlerRegistry.java @@ -1,5 +1,8 @@ package name.pehl.karaka.client.dispatch; +import com.google.inject.Inject; +import com.gwtplatform.dispatch.client.actionhandler.DefaultClientActionHandlerRegistry; +import name.pehl.karaka.client.activity.dispatch.CopyActivityHandler; import name.pehl.karaka.client.activity.dispatch.DeleteActivityHandler; import name.pehl.karaka.client.activity.dispatch.FindActivityHandler; import name.pehl.karaka.client.activity.dispatch.GetActivitiesHandler; @@ -7,24 +10,27 @@ import name.pehl.karaka.client.activity.dispatch.GetRunningActivityHandler; import name.pehl.karaka.client.activity.dispatch.GetYearsHandler; import name.pehl.karaka.client.activity.dispatch.SaveActivityHandler; +import name.pehl.karaka.client.activity.dispatch.StartActivityHandler; +import name.pehl.karaka.client.activity.dispatch.StopActivityHandler; +import name.pehl.karaka.client.activity.dispatch.TickActivityHandler; import name.pehl.karaka.client.client.GetClientsHandler; import name.pehl.karaka.client.project.GetProjectsHandler; import name.pehl.karaka.client.settings.GetSettingsHandler; import name.pehl.karaka.client.tag.GetTagsHandler; -import com.google.inject.Inject; -import com.gwtplatform.dispatch.client.actionhandler.DefaultClientActionHandlerRegistry; - public class KarakaActionHandlerRegistry extends DefaultClientActionHandlerRegistry { @Inject - public KarakaActionHandlerRegistry(final DeleteActivityHandler deleteActivityHandler, - final FindActivityHandler findActivityHandler, final GetActivitiesHandler getActivitiesHandler, - final GetClientsHandler getClientsHandler, final GetDurationsHandler getDurationsHandler, - final GetProjectsHandler getProjectsHandler, final GetRunningActivityHandler getRunningActivityHandler, - final GetSettingsHandler getSettingsHandler, final GetTagsHandler getTagsHandler, - final GetYearsHandler getYearsHandler, final SaveActivityHandler saveActivityHandler) + public KarakaActionHandlerRegistry(final CopyActivityHandler copyActivityHandler, + final DeleteActivityHandler deleteActivityHandler, final FindActivityHandler findActivityHandler, + final GetActivitiesHandler getActivitiesHandler, final GetClientsHandler getClientsHandler, + final GetDurationsHandler getDurationsHandler, final GetProjectsHandler getProjectsHandler, + final GetRunningActivityHandler getRunningActivityHandler, final GetSettingsHandler getSettingsHandler, + final GetTagsHandler getTagsHandler, final GetYearsHandler getYearsHandler, + final SaveActivityHandler saveActivityHandler, final StartActivityHandler startActivityHandler, + final StopActivityHandler stopActivityHandler, final TickActivityHandler tickActivityHandler) { + register(copyActivityHandler); register(deleteActivityHandler); register(findActivityHandler); register(getActivitiesHandler); @@ -36,5 +42,8 @@ public KarakaActionHandlerRegistry(final DeleteActivityHandler deleteActivityHan register(getTagsHandler); register(getYearsHandler); register(saveActivityHandler); + register(startActivityHandler); + register(stopActivityHandler); + register(tickActivityHandler); } } diff --git a/src/main/java/name/pehl/karaka/client/dispatch/KarakaCallback.java b/src/main/java/name/pehl/karaka/client/dispatch/KarakaCallback.java index 3fc1b48..ef23ce2 100644 --- a/src/main/java/name/pehl/karaka/client/dispatch/KarakaCallback.java +++ b/src/main/java/name/pehl/karaka/client/dispatch/KarakaCallback.java @@ -39,12 +39,26 @@ public void onFailure(final Throwable caught) { onNotFound((FailedStatusCodeException) caught); } + else if (caught instanceof FailedStatusCodeException) + { + onFailedStatus((FailedStatusCodeException) caught); + } else { showError(caught); } } + /** + * Default implementation delegates to {@link #showError(Throwable)} + * + * @param caught + */ + public void onFailedStatus(FailedStatusCodeException caught) + { + showError(caught); + } + /** * Default implementation delegates to {@link #showError(Throwable)} * diff --git a/src/main/java/name/pehl/karaka/client/gin/KarakaModule.java b/src/main/java/name/pehl/karaka/client/gin/KarakaModule.java index 9a4ef43..1f4134d 100644 --- a/src/main/java/name/pehl/karaka/client/gin/KarakaModule.java +++ b/src/main/java/name/pehl/karaka/client/gin/KarakaModule.java @@ -9,6 +9,7 @@ import name.pehl.karaka.client.NameTokens; import name.pehl.karaka.client.about.AboutPresenter; import name.pehl.karaka.client.about.AboutView; +import name.pehl.karaka.client.activity.dispatch.CopyActivityHandler; import name.pehl.karaka.client.activity.dispatch.DeleteActivityHandler; import name.pehl.karaka.client.activity.dispatch.FindActivityHandler; import name.pehl.karaka.client.activity.dispatch.GetActivitiesHandler; @@ -16,6 +17,9 @@ import name.pehl.karaka.client.activity.dispatch.GetRunningActivityHandler; import name.pehl.karaka.client.activity.dispatch.GetYearsHandler; import name.pehl.karaka.client.activity.dispatch.SaveActivityHandler; +import name.pehl.karaka.client.activity.dispatch.StartActivityHandler; +import name.pehl.karaka.client.activity.dispatch.StopActivityHandler; +import name.pehl.karaka.client.activity.dispatch.TickActivityHandler; import name.pehl.karaka.client.activity.model.ActivitiesReader; import name.pehl.karaka.client.activity.model.ActivityReader; import name.pehl.karaka.client.activity.model.ActivityWriter; @@ -110,6 +114,7 @@ protected void configure() bind(TableResources.class).in(Singleton.class); // Rest Action Handlers + bind(CopyActivityHandler.class); bind(DeleteActivityHandler.class); bind(FindActivityHandler.class); bind(GetActivitiesHandler.class); @@ -121,6 +126,9 @@ protected void configure() bind(GetTagsHandler.class); bind(GetYearsHandler.class); bind(SaveActivityHandler.class); + bind(StartActivityHandler.class); + bind(StopActivityHandler.class); + bind(TickActivityHandler.class); // JsonReader / Writer // Bind them as eager singletons so that the JsonRegistry diff --git a/src/main/java/name/pehl/karaka/server/activity/boundary/ActivitiesResource.java b/src/main/java/name/pehl/karaka/server/activity/boundary/ActivitiesResource.java index 81855ae..de8f652 100644 --- a/src/main/java/name/pehl/karaka/server/activity/boundary/ActivitiesResource.java +++ b/src/main/java/name/pehl/karaka/server/activity/boundary/ActivitiesResource.java @@ -28,7 +28,6 @@ import javax.inject.Inject; import javax.ws.rs.DELETE; import javax.ws.rs.GET; -import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -40,13 +39,15 @@ import javax.ws.rs.core.UriInfo; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeSet; -import static javax.ws.rs.core.Response.Status.CREATED; -import static javax.ws.rs.core.Response.Status.NOT_FOUND; +import static javax.ws.rs.core.Response.Status.*; import static name.pehl.karaka.shared.model.HasLinks.SELF; +import static name.pehl.karaka.shared.model.Status.STOPPED; import static name.pehl.karaka.shared.model.TimeUnit.*; import static org.joda.time.Months.months; import static org.joda.time.Weeks.weeks; @@ -54,7 +55,7 @@ /** * Supported methods: *

- *

GET Methods:

+ *

Read methods:

*
    *
  • GET /activities/{year}/{month}: Find activities by year and month
  • *
  • GET /activities/{year}/{month}/duration: Get the duration in minutes of the specified activities
  • @@ -77,30 +78,44 @@ *
  • GET /activities/running: Find the running activity
  • *
  • GET /activities/years: Returns the years, months and weeks in which activities are stored
  • *
- *

Other methods:

+ *

CUD methods:

*
    - *
  • POST: Create a new activity. If the state of the new activity is - * {@link name.pehl.karaka.shared.model.Status#RUNNING} and there's another running activity, the other activity is - * stopped.
  • - *
  • PUT /activities/{id}: Update an existing activity. Please note that changes to the status of an activity are - * ignored by this method! The only way to change the state of an activity is to call the relvant method / url pair. - *
  • PUT /activities/{id}/start: Start the specified activity. If the activity is already started nothing will - * happen. If the activity is stopped a new activity will be started or the current activity will be resumed. If - * there's another running activity this activity will be stoped first.
  • - *
  • PUT /activities/{id}/stop: Stops the specified activity
  • - *
  • DELETE /activities/{id}: Delete an existing activity
  • - *
- *

- * Normally for POST and PUT the end time is taken from the JSON input and the duration in minutes is calculated. - * There's one exception to this rule: If - *

+ *
  • PUT /activities/{id}: Update an existing activity. The data for the activity must be provided in the request + * body. The end time of the activity is taken from the JSON input and the duration in minutes is calculated. There's + * one exception to this rule: If *
      - *
    • the activity is stopped - *
    • the start time is present - *
    • the end time is not present and - *
    • the duration in minutes is specified + *
    • the activity is stopped
    • + *
    • the start time is present
    • + *
    • the end time is not present and
    • + *
    • the duration in minutes is specified
    • + *
    + * then the end time is calculated. Please note that changes to the status of an activity are ignored by this method! + * The only way to change the status of an activity is to call the relvant method / url pair.
    + * The method returns 200 together with the updated activity.
  • + *
  • PUT /activities/{id}/copy/{period}: Copy an existing activity as a new activity and adds the specified period. + * The period must follow the format described at ISO8601. + * The status of the original activity is not touched.
    + * The method returns 201 together with the copied activity.
  • + *
  • PUT /activities/start: Start a new activity. The data for the new activity must be provided in the request body. + * The new activity will be stored as the running activity. If there's another running activity that activity will be + * stopped first.
    + * The method returns 201 together with the created / modified activities in a collection (even if there was only one + * activity created).
  • + *
  • PUT /activities/{id}/start: Start an existing activity. Depending on the activities start date the activity is + * resumed (start date == today) or started as a new activity (start date != today). If there's another running + * activity that activity will be stopped first.
    + * The method returns 200 together with all modified activities in a collection (even if there was only one activity + * modified). If the activity was already started nothing will happen and 304 is returned.
  • + *
  • PUT /activities/{id}/tick: Tick an existing activity i.e. sets the end time to the current time and saves the + * activity. If the activity is not yet started, it will be started first. If there's another running activity that + * activity will be stopped first.
    + * The method returns 200 together with all modified activities in a collection (even if there was only one activity + * modified).
  • + *
  • PUT /activities/{id}/stop: Stop an existing activity.
    + * The method returns 200 together with the updated activity. If the activity is already stopped nothing will happen + * and 304 is returned.
  • + *
  • DELETE /activities/{id}: Delete an existing activity and return 204.
  • * - * then the end time is calculated. * * @author $Author: harald.pehl $ * @version $Date: 2011-05-16 12:54:26 +0200 (Mo, 16. Mai 2011) $ $Revision: 110 @@ -496,28 +511,74 @@ public name.pehl.karaka.shared.model.Activity runningActivity() // ------------------------------------------------------------ CUD methods - @POST - public Response saveNewActivity(name.pehl.karaka.shared.model.Activity newClientActivity) + @PUT + @Path("/{id}") + public Response updateActivity(@PathParam("id") String id, + name.pehl.karaka.shared.model.Activity clientActivity) + { + try + { + Activity serverActivity = repository.get(Key.create(id)); + activityConverter.merge(clientActivity, serverActivity); + repository.put(serverActivity); + name.pehl.karaka.shared.model.Activity updatedClientActivity = activityConverter.toModel(serverActivity); + return Response.ok(updatedClientActivity).build(); + } + catch (com.googlecode.objectify.NotFoundException e) + { + return Response.status(NOT_FOUND).build(); + } + } + + @PUT + @Path("/{id}/copy/{period}") + public Response copyActivity(@PathParam("id") String id, @PathParam("period") String period) + { + try + { + Activity activity = repository.get(Key.create(id)); + Activity copy = activity.copy(period); + repository.put(copy); + name.pehl.karaka.shared.model.Activity clientCopy = activityConverter.toModel(copy); + return Response.status(CREATED).entity(clientCopy).build(); + } + catch (com.googlecode.objectify.NotFoundException e) + { + return Response.status(NOT_FOUND).build(); + } + catch (IllegalArgumentException e) + { + return Response.status(BAD_REQUEST).entity( + "Illegal format for period \"" + period + "\". See http://en.wikipedia.org/wiki/ISO_8601#Durations for a valid format") + .build(); + } + } + + @PUT + @Path("start") + public Response startNewActivity(name.pehl.karaka.shared.model.Activity clientActivity) { - Activity newServerActivity = activityConverter.fromModel(newClientActivity); + // TODO Is it an error if the client activity already exists on the server? + Activity newServerActivity = activityConverter.fromModel(clientActivity); repository.put(newServerActivity); name.pehl.karaka.shared.model.Activity createdClientActivity = activityConverter.toModel(newServerActivity); return Response.status(CREATED).entity(createdClientActivity).build(); } @PUT - @Path("{id}") - public Response updateExistingActivity(@PathParam("id") String id, - name.pehl.karaka.shared.model.Activity modifiedClientActivity) + @Path("{id}/start") + public Response startActivity(@PathParam("id") String id) { try { - Activity existingServerActivity = repository.get(Key.create(id)); - activityConverter.merge(modifiedClientActivity, existingServerActivity); - repository.put(existingServerActivity); - name.pehl.karaka.shared.model.Activity updatedClientActivity = activityConverter - .toModel(existingServerActivity); - return Response.ok(updatedClientActivity).build(); + Activity activity = repository.get(Key.create(id)); + Iterable modifiedActivities = repository.start(activity); + Set clientActivities = new HashSet(); + for (Activity modifiedActivity : modifiedActivities) + { + clientActivities.add(activityConverter.toModel(modifiedActivity)); + } + return Response.ok(clientActivities).build(); } catch (com.googlecode.objectify.NotFoundException e) { @@ -526,14 +587,19 @@ public Response updateExistingActivity(@PathParam("id") String id, } @PUT - @Path("{id}/start") - public Response startActivity(@PathParam("id") String id) + @Path("{id}/tick") + public Response tickActivity(@PathParam("id") String id) { try { Activity activity = repository.get(Key.create(id)); - repository.start(activity); - return Response.ok().build(); + Iterable modifiedActivities = repository.tick(activity); + Set clientActivities = new HashSet(); + for (Activity modifiedActivity : modifiedActivities) + { + clientActivities.add(activityConverter.toModel(modifiedActivity)); + } + return Response.ok(clientActivities).build(); } catch (com.googlecode.objectify.NotFoundException e) { @@ -548,8 +614,17 @@ public Response stopActivity(@PathParam("id") String id) try { Activity activity = repository.get(Key.create(id)); - repository.stop(activity); - return Response.ok().build(); + if (activity.getStatus() == STOPPED) + { + return Response.status(NOT_MODIFIED).build(); + } + else + { + activity.stop(); + repository.put(activity); + name.pehl.karaka.shared.model.Activity clientActivity = activityConverter.toModel(activity); + return Response.ok(clientActivity).build(); + } } catch (com.googlecode.objectify.NotFoundException e) { @@ -559,7 +634,7 @@ public Response stopActivity(@PathParam("id") String id) @DELETE @Path("{id}") - public Response deleteExistingActivity(@PathParam("id") String id) + public Response deleteActivity(@PathParam("id") String id) { try { @@ -586,7 +661,7 @@ private Duration minutes(List activities) long minutes = 0; for (Activity activity : activities) { - minutes += activity.getMinutes(); + minutes += activity.getDuration(); } return new Duration(minutes); } diff --git a/src/main/java/name/pehl/karaka/server/activity/control/ActivityConverter.java b/src/main/java/name/pehl/karaka/server/activity/control/ActivityConverter.java index d38992c..2380fb3 100644 --- a/src/main/java/name/pehl/karaka/server/activity/control/ActivityConverter.java +++ b/src/main/java/name/pehl/karaka/server/activity/control/ActivityConverter.java @@ -11,11 +11,13 @@ import name.pehl.karaka.server.tag.control.TagConverter; import name.pehl.karaka.server.tag.control.TagRepository; import name.pehl.karaka.shared.model.Duration; +import org.joda.time.DateTime; import javax.enterprise.inject.Instance; import javax.inject.Inject; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.List; /** @@ -53,7 +55,7 @@ public name.pehl.karaka.shared.model.Activity toModel(name.pehl.karaka.server.ac model.setEnd(new name.pehl.karaka.shared.model.Time(entity.getEnd().toDate(), entity.getEnd() .getYear(), entity.getEnd().getMonth(), entity.getEnd().getWeek(), entity.getEnd().getDay())); model.setPause(new Duration(entity.getPause())); - model.setDuration(new Duration(entity.getMinutes())); + model.setDuration(new Duration(entity.getDuration())); model.setBillable(entity.isBillable()); model.setStatus(entity.getStatus()); @@ -124,17 +126,17 @@ private void internalModelToEntity(name.pehl.karaka.shared.model.Activity model, entity.setDescription(model.getDescription()); if (model.getStart() != null) { -// entity.setStart(new name.pehl.karaka.server.activity.entity.Time(model.getStart().getDate(), settings -// .get().getTimeZone())); + entity.setStart(new name.pehl.karaka.server.activity.entity.Time(model.getStart().getDate(), settings + .get().getTimeZone())); } else { -// entity.setStart(new name.pehl.karaka.server.activity.entity.Time(new Date(), settings.get().getTimeZone())); + entity.setStart(new name.pehl.karaka.server.activity.entity.Time(new Date(), settings.get().getTimeZone())); } if (model.getEnd() != null) { -// entity.setEnd(new name.pehl.karaka.server.activity.entity.Time(model.getEnd().getDate(), settings -// .get().getTimeZone())); + entity.setEnd(new name.pehl.karaka.server.activity.entity.Time(model.getEnd().getDate(), settings + .get().getTimeZone())); } else { @@ -147,20 +149,20 @@ private void internalModelToEntity(name.pehl.karaka.shared.model.Activity model, // then the end time is calculated. if (model.isStopped() && model.getStart() != null && !model.getDuration().isZero()) { -// DateTime start = entity.getStart().getDateTime(); -// DateTime end = start.plusMinutes((int) model.getDuration().getTotalMinutes()); -// entity.setEnd( -// new name.pehl.karaka.server.activity.entity.Time(end.toDate(), settings.get().getTimeZone())); + DateTime end = entity.getStart().plusMinutes((int) model.getDuration().getTotalMinutes()); + entity.setEnd( + new name.pehl.karaka.server.activity.entity.Time(end.toDate(), settings.get().getTimeZone())); } else { -// entity.setEnd( -// new name.pehl.karaka.server.activity.entity.Time(new Date(), settings.get().getTimeZone())); + // fall back to current time + entity.setEnd( + new name.pehl.karaka.server.activity.entity.Time(new Date(), settings.get().getTimeZone())); } } -// entity.setPause(model.getPause().getTotalMinutes()); + entity.setPause(model.getPause().getTotalMinutes()); entity.setBillable(model.isBillable()); - // entity.setStatus(model.getStatus()); Status can only be changed calling distinct services! + // entity.setStatus(model.getStatus()); Status can only be changed calling distinct service methods! // relations Key projectKey = null; diff --git a/src/main/java/name/pehl/karaka/server/activity/control/ActivityRepository.java b/src/main/java/name/pehl/karaka/server/activity/control/ActivityRepository.java index 66db0b3..48cfa66 100644 --- a/src/main/java/name/pehl/karaka/server/activity/control/ActivityRepository.java +++ b/src/main/java/name/pehl/karaka/server/activity/control/ActivityRepository.java @@ -4,10 +4,18 @@ import com.googlecode.objectify.Key; import name.pehl.karaka.server.activity.entity.Activity; import name.pehl.karaka.server.repository.NamedEntityRepository; +import name.pehl.karaka.server.settings.control.CurrentSettings; +import name.pehl.karaka.server.settings.entity.Settings; import name.pehl.karaka.shared.model.Status; import javax.inject.Inject; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; + +import static name.pehl.karaka.shared.model.Status.STOPPED; /** * @author $Author: harald.pehl $ @@ -16,70 +24,65 @@ */ public class ActivityRepository extends NamedEntityRepository { + @Inject @CurrentSettings Settings settings; + + @Inject public ActivityRepository(ActivityIndexSearch indexSearch) { super(Activity.class, indexSearch); } - public List findByYear(int year) { return query().filter("start.year =", year).list(); } - public boolean hasActivitiesByYear(int year) { return query().filter("start.year =", year).count() > 0; } - public List findByYearMonth(int year, int month) { return query().filter("start.year =", year).filter("start.month =", month).list(); } - public boolean hasActivitiesByYearMonth(int year, int month) { return query().filter("start.year =", year).filter("start.month =", month).count() > 0; } - public List findByYearWeek(int year, int week) { return query().filter("start.year =", year).filter("start.week =", week).list(); } - public boolean hasActivitiesByYearWeek(int year, int week) { return query().filter("start.year =", year).filter("start.week =", week).count() > 0; } - public List findByYearMonthDay(int year, int month, int day) { return query().filter("start.year =", year).filter("start.month =", month).filter("start.day =", day).list(); } - public boolean hasActivitiesByYearMonthDay(int year, int month, int day) { - return query().filter("start.year =", year).filter("start.month =", month).filter("start.day =", day).count() > 0; + return query().filter("start.year =", year).filter("start.month =", month).filter("start.day =", day) + .count() > 0; } - /** * Finds the activity with {@link Status#RUNNING}. If no activity is * {@link Status#RUNNING} null is returned. - * + * * @return the activity with {@link Status#RUNNING}, null * otherwise. + * * @throws EntityNotFoundException - * @throws IllegalStateException - * if more thaan one activity is {@link Status#RUNNING}. + * @throws IllegalStateException if more thaan one activity is {@link Status#RUNNING}. */ public Activity findRunningActivity() { @@ -96,13 +99,74 @@ public Activity findRunningActivity() return activity; } - public void start(final Activity activity) + /** + * Starts or resumes the specified activity. If the activities start day is today, the activity is resumed. If not + * the activity is cloned and started as a new activity. If there's another activity running, this activity is + * stopped first. The returned set contains all modified activities (two if there was another activity + * running, one otherwise). + * + * @param activity + * + * @return + */ + public Iterable start(final Activity activity) { - + if (activity != null) + { + Set saveMe = new HashSet(); + Activity runningActivity = findRunningActivity(); + if (runningActivity != null && !runningActivity.equals(activity)) + { + runningActivity.stop(); + saveMe.add(runningActivity); + } + // same day --> resume, start otherwise + if (activity.isToday()) + { + activity.resume(); + saveMe.add(activity); + } + else + { + Activity copy = activity.copy(); + copy.start(); + saveMe.add(copy); + } + Map, Activity> saved = putAll(saveMe); + return saved.values(); + } + return Collections.emptySet(); } - public void stop(final Activity activity) + /** + * Ticks the specified activity. If the activity is not running it is started. If there's another activity running, + * this activity is stopped first. The returned set contains all modified activities (two if there was another + * activity running, one otherwise). + * + * @param activity + * + * @return + */ + public Iterable tick(final Activity activity) { - + if (activity != null) + { + Set saveMe = new HashSet(); + Activity runningActivity = findRunningActivity(); + if (runningActivity != null && !runningActivity.equals(activity)) + { + runningActivity.stop(); + saveMe.add(runningActivity); + } + if (activity.getStatus() == STOPPED) + { + activity.start(); + } + activity.tick(); + saveMe.add(activity); + Map, Activity> saved = putAll(saveMe); + return saved.values(); + } + return Collections.emptySet(); } } diff --git a/src/main/java/name/pehl/karaka/server/activity/entity/Activity.java b/src/main/java/name/pehl/karaka/server/activity/entity/Activity.java index 761378d..b88b525 100644 --- a/src/main/java/name/pehl/karaka/server/activity/entity/Activity.java +++ b/src/main/java/name/pehl/karaka/server/activity/entity/Activity.java @@ -1,5 +1,6 @@ package name.pehl.karaka.server.activity.entity; +import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Entity; @@ -9,9 +10,11 @@ import name.pehl.karaka.server.project.entity.Project; import name.pehl.karaka.server.tag.entity.Tag; import name.pehl.karaka.shared.model.Status; +import org.joda.time.DateMidnight; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Minutes; +import org.joda.time.Period; import javax.persistence.Embedded; import javax.persistence.PostLoad; @@ -42,7 +45,6 @@ public class Activity extends DescriptiveEntity implements Comparable // ------------------------------------------------------ members private static final long serialVersionUID = 3829933815690771262L; - @Unindexed private final String timeZoneId; @Embedded private Time start; @Embedded @Unindexed private Time end; @@ -56,6 +58,7 @@ public class Activity extends DescriptiveEntity implements Comparable // ------------------------------------------------------ constructors + Activity() { this(null, null, null); @@ -117,86 +120,99 @@ void restoreDateTimes() } if (start != null && start.date != null) { - start.init(start.date, timeZone); + start.init(new DateTime(start.date, timeZone)); } if (end != null && end.date != null) { - end.init(end.date, timeZone); + end.init(new DateTime(end.date, timeZone)); } } // --------------------------------------------------------- business methods - public Activity start() + /** + * Creates a new activity which is a copy of this activitiy with the + * following differences: + *
      + *
    • Id is null i.e. the copy is a transient activity + *
    • Start and end is the current time. + *
    • pause and duration are 0. + *
    • status is {@link Status#STOPPED} + *
    + * + * @return + */ + public Activity copy() + { + Activity copy = new Activity(getName(), getDescription(), timeZone); + copy.setProject(project); + copy.setTags(tags); + return copy; + } + + /** + * Copies the current activity and adds the specified period to the start and end date. + * The period must follow ISO8601. + * + * @param period + * + * @return + * + * @throws IllegalArgumentException if the duration is null, empty or + */ + public Activity copy(final String period) + { + if (Strings.emptyToNull(period) == null) + { + throw new IllegalArgumentException("Period must not be null"); + } + Period p = Period.parse(period); + Activity copy = copy(); + copy.setEnd(new Time(end.dateTime.withPeriodAdded(p, 1))); + copy.setStart(new Time(start.dateTime.withPeriodAdded(p, 1))); + return copy; + } + + public void start() { - ensureStart(); - ensureEnd(); + start = now(); + end = now(); status = RUNNING; - return this; } - public Activity resume() + public void resume() { - ensureStart(); - ensureEnd(); - DateTime now = new DateTime(timeZone); if (start.isAfterNow()) { - start = new Time(null, timeZone); + start = now(); } if (end.isAfterNow()) { - end = new Time(null, timeZone); + end = now(); } - pause += minutesBetween(end, now).getMinutes(); + pause += minutesBetween(end, now()).getMinutes(); end = now(); status = RUNNING; - return this; - } - - public Activity tick() - { - return tick(now()); } - public Activity tick(Time to) + public void tick() { if (status == RUNNING) { - ensureStart(); - end = to; + end = now(); } - return this; } - public Activity stop() + public void stop() { - ensureStart(); end = now(); status = STOPPED; - return this; - } - - private void ensureStart() - { - if (this.start == null) - { - this.start = now(); - } - } - - private void ensureEnd() - { - if (this.end == null) - { - this.end = now(); - } } private Time now() { - return new Time(null, timeZone); + return new Time(); } @@ -227,7 +243,13 @@ public String toString() // ------------------------------------------------------ properties - public long getMinutes() + public boolean isToday() + { + DateMidnight now = new DateMidnight(timeZone); + return now.equals(start.toDateMidnight()); + } + + public long getDuration() { Minutes m = minutesBetween(start, end); long minutes = m.getMinutes() - pause; @@ -239,16 +261,60 @@ public Time getStart() return start; } + public void setStart(final Time start) + { + if (start != null) + { + if (start.isAfter(end)) + { + this.start = new Time(end); + this.pause = 0; + } + else + { + this.start = start; + } + } + } + public Time getEnd() { return end; } + public void setEnd(final Time end) + { + if (end != null) + { + if (end.isBefore(start)) + { + this.end = new Time(start); + this.pause = 0; + } + else + { + this.end = end; + } + } + } + public long getPause() { return pause; } + public void setPause(final long pause) + { + if (pause >= 0) + { + long duration = minutesBetween(start, end).getMinutes(); + if (pause <= duration) + { + this.pause = pause; + } + } + } + public List> getTags() { return unmodifiableList(this.tags); diff --git a/src/main/java/name/pehl/karaka/server/activity/entity/Time.java b/src/main/java/name/pehl/karaka/server/activity/entity/Time.java index 637a79d..ae5b4f1 100644 --- a/src/main/java/name/pehl/karaka/server/activity/entity/Time.java +++ b/src/main/java/name/pehl/karaka/server/activity/entity/Time.java @@ -1,10 +1,12 @@ package name.pehl.karaka.server.activity.entity; import org.joda.time.Chronology; +import org.joda.time.DateMidnight; import org.joda.time.DateTime; import org.joda.time.DateTimeFieldType; import org.joda.time.DateTimeZone; import org.joda.time.Instant; +import org.joda.time.ReadableDateTime; import org.joda.time.ReadableInstant; import javax.persistence.Transient; @@ -43,32 +45,39 @@ public class Time implements ReadableInstant // ----------------------------------------------------------- constructors - Time() + public Time() { - this(new Date(), DateTimeZone.getDefault()); + init(null); } + public Time(Time time) + { + init(time == null ? null : time.dateTime); + } + + public Time(ReadableDateTime dateTime) + { + init(dateTime); + } + + public Time(DateTime dateTime) + { + init(dateTime); + } public Time(Date date, DateTimeZone timeZone) { - init(date, timeZone); + init(new DateTime(date, timeZone)); } - void init(Date date, DateTimeZone timeZone) + void init(ReadableDateTime dateTime) { - if (date == null) - { - this.dateTime = new DateTime(timeZone); - } - else - { - this.dateTime = new DateTime(date.getTime(), timeZone); - } - this.date = dateTime.toDate(); - this.year = dateTime.year().get(); - this.month = dateTime.monthOfYear().get(); - this.week = dateTime.weekOfWeekyear().get(); - this.day = dateTime.dayOfMonth().get(); + this.dateTime = dateTime == null ? new DateTime(DateTimeZone.getDefault()) : new DateTime(dateTime); + this.date = this.dateTime.toDate(); + this.year = this.dateTime.year().get(); + this.month = this.dateTime.monthOfYear().get(); + this.week = this.dateTime.weekOfWeekyear().get(); + this.day = this.dateTime.dayOfMonth().get(); } @@ -182,4 +191,8 @@ public int getDay() @Override public int compareTo(final ReadableInstant readableInstant) {return dateTime.compareTo(readableInstant);} + + public DateMidnight toDateMidnight() {return dateTime.toDateMidnight();} + + public DateTime plusMinutes(final int minutes) {return dateTime.plusMinutes(minutes);} } diff --git a/src/main/java/name/pehl/karaka/server/sampledata/ActivitiesProducer.java b/src/main/java/name/pehl/karaka/server/sampledata/ActivitiesProducer.java index e15bfef..48f52c4 100644 --- a/src/main/java/name/pehl/karaka/server/sampledata/ActivitiesProducer.java +++ b/src/main/java/name/pehl/karaka/server/sampledata/ActivitiesProducer.java @@ -37,7 +37,7 @@ public List produceActivities(List> projectKeys, List activities = new ArrayList(); - MutableDateTime mdt = new MutableDateTime(start); + MutableDateTime mdt = new MutableDateTime(start, timeZone); while (mdt.isBefore(end)) { mdt.hourOfDay().set(9); @@ -45,9 +45,10 @@ public List produceActivities(List> projectKeys, List(); - ensureStart(); } @@ -100,103 +100,6 @@ public Activity copy() } - /** - * Creates a copy of this activity and adds the specified amount of - * milliseconds to both start and end. No matter what the status of this - * activity is, the copied status will be {@link Status#STOPPED}. - * - * @param millis - * The amount of milliseconds to add to this start and end. - * @return - */ - public Activity plus(long millis) - { - Time startCopy = new Time(new Date(this.start.getDate().getTime() + millis)); - Time endCopy = new Time(); - if (this.end != null) - { - endCopy.setDate(new Date(this.end.getDate().getTime() + millis)); - } - else - { - endCopy.setDate(startCopy.getDate()); - } - Activity copy = new Activity(this.name); - copy.setDescription(description); - copy.setStart(startCopy); - copy.setEnd(endCopy); - copy.setPause(this.pause); - copy.setBillable(this.billable); - copy.setStatus(STOPPED); - copy.setProject(this.project); - copy.getTags().addAll(this.tags); - return copy; - } - - - public void start() - { - ensureStart().setDate(new Date()); - ensureEnd().setDate(new Date()); - status = RUNNING; - } - - - public void resume() - { - ensureStart(); - ensureEnd(); - Date now = new Date(); - if (start.after(now)) - { - start = new Time(); - } - if (end.after(now)) - { - end = new Time(); - } - pause = pause.plus(new Duration(end.getDate(), now)); - end.setDate(now); - status = RUNNING; - } - - - public void stop() - { - ensureStart(); - ensureEnd().setDate(new Date()); - status = STOPPED; - } - - - public void tick() - { - if (status == RUNNING) - { - ensureStart(); - ensureEnd().setDate(new Date()); - } - } - - - /** - * @return true if the start date of this activity is today, - * false otherwise. - */ - @SuppressWarnings("deprecation") - public boolean isToday() - { - Date now = new Date(); - Date startDate = start.getDate(); - if (now.getYear() == startDate.getYear() && now.getMonth() == startDate.getMonth() - && now.getDate() == startDate.getDate()) - { - return true; - } - return false; - } - - private void calculateDuration() { if (end != null) @@ -210,25 +113,6 @@ private void calculateDuration() } - private Time ensureStart() - { - if (this.start == null) - { - this.start = new Time(); - } - return this.start; - } - - - private Time ensureEnd() - { - if (this.end == null) - { - this.end = new Time(); - } - return this.end; - } - // --------------------------------------------------------- object methods @@ -320,9 +204,7 @@ public Duration getDuration() /** * Required for JSON (de)serialization - please don't call directly. The - * duration are calculated when calling {@link #setStart(Time)}, - * {@link #setEnd(Time)}, {@link #start()}, {@link #stop()}, {@link #resume()} - * and {@link #tick()}. + * duration are calculated when calling {@link #setStart(Time)} and {@link #setEnd(Time)}. * * @param duration */ diff --git a/src/test/java/name/pehl/karaka/client/activity/presenter/ActivityControllerTest.java b/src/test/java/name/pehl/karaka/client/activity/presenter/ActivityControllerTest.java index fd344ca..b2631b8 100644 --- a/src/test/java/name/pehl/karaka/client/activity/presenter/ActivityControllerTest.java +++ b/src/test/java/name/pehl/karaka/client/activity/presenter/ActivityControllerTest.java @@ -1,29 +1,9 @@ package name.pehl.karaka.client.activity.presenter; -import static java.util.Arrays.asList; -import static java.util.logging.Level.INFO; -import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.CHANGED; -import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.DELETE; -import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.NEW; -import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.RESUMED; -import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.STARTED; -import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.STOPPED; -import static name.pehl.karaka.shared.model.TimeUnit.WEEK; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.TreeSet; - +import com.google.common.collect.ImmutableMap; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.gwtplatform.dispatch.client.actionhandler.ClientActionHandler; +import com.gwtplatform.dispatch.client.actionhandler.ExecuteCommand; import name.pehl.karaka.client.PresenterTest; import name.pehl.karaka.client.activity.dispatch.DeleteActivityAction; import name.pehl.karaka.client.activity.dispatch.DeleteActivityHandler; @@ -32,6 +12,7 @@ import name.pehl.karaka.client.activity.dispatch.SaveActivityHandler; import name.pehl.karaka.client.activity.dispatch.SaveActivityResult; import name.pehl.karaka.client.activity.event.ActivitiesLoadedEvent; +import name.pehl.karaka.client.activity.event.ActivityChanged; import name.pehl.karaka.client.activity.event.ActivityChangedEvent; import name.pehl.karaka.client.activity.event.ActivityChangedEvent.ActivityChangedHandler; import name.pehl.karaka.client.activity.event.RunningActivityLoadedEvent; @@ -42,18 +23,27 @@ import name.pehl.karaka.client.application.ShowMessageEvent.ShowMessageHandler; import name.pehl.karaka.shared.model.Activities; import name.pehl.karaka.shared.model.Activity; - import org.joda.time.DateTime; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import com.google.common.collect.ImmutableMap; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.gwtplatform.dispatch.client.actionhandler.ClientActionHandler; -import com.gwtplatform.dispatch.client.actionhandler.ExecuteCommand; +import java.util.TreeSet; + +import static java.util.Arrays.asList; +import static java.util.logging.Level.INFO; +import static name.pehl.karaka.client.activity.event.ActivityChanged.ChangeAction.*; +import static name.pehl.karaka.shared.model.Status.RUNNING; +import static name.pehl.karaka.shared.model.Status.STOPPED; +import static name.pehl.karaka.shared.model.TimeUnit.WEEK; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; +@Ignore("Needs refactoring") public class ActivityControllerTest extends PresenterTest implements TickHandler, ActivityChangedHandler, ShowMessageHandler @@ -91,7 +81,7 @@ public void setUp() public void onRunningActivityLoaded() { Activity activity = td.newActivity(); - activity.start(); + activity.setStatus(RUNNING); cut.onRunningActivityLoaded(new RunningActivityLoadedEvent(activity)); assertSame(activity, cut.runningActivity); } @@ -200,7 +190,7 @@ public void startActivityNoOtherActivityRunning() activityToStart.setEnd(td.newTime(DateTime.now())); activityToStart.setName("Foo"); Activity startedActivity = activityToStart.copy(); - startedActivity.start(); + startedActivity.setStatus(RUNNING); final SaveActivityResult saveActivityResult = new SaveActivityResult(startedActivity); Answer saveActivityAnswer = new Answer() @@ -219,7 +209,7 @@ public Object answer(InvocationOnMock invocation) for (boolean[] combination : UPDATE_ACTIVITIES_COMBINATIONS) { - activityToStart.stop(); + activityToStart.setStatus(STOPPED); cut.ticking = false; cut.runningActivity = null; scheduler.getRepeatingCommands().clear(); @@ -253,7 +243,7 @@ public void resumeActivityNoOtherActivityRunning() Activity activityToResume = new Activity("Test activity"); activityToResume.setName("Foo"); Activity resumedActivity = activityToResume.copy(); - resumedActivity.start(); + resumedActivity.setStatus(RUNNING); final SaveActivityResult saveActivityResult = new SaveActivityResult(resumedActivity); Answer saveActivityAnswer = new Answer() @@ -272,7 +262,7 @@ public Object answer(InvocationOnMock invocation) for (boolean[] combination : UPDATE_ACTIVITIES_COMBINATIONS) { - activityToResume.stop(); + activityToResume.setStatus(STOPPED); cut.ticking = false; cut.runningActivity = null; scheduler.getRepeatingCommands().clear(); @@ -324,7 +314,7 @@ public Object answer(InvocationOnMock invocation) for (boolean[] combination : UPDATE_ACTIVITIES_COMBINATIONS) { - activityToStop.start(); + activityToStop.setStatus(RUNNING); cut.runningActivity = activityToStop; prepareUpdateActivities(activityToStop, stoppedActivity, combination[0], combination[1]); @@ -341,7 +331,7 @@ public Object answer(InvocationOnMock invocation) assertTrue(message.isAutoHide()); ActivityChangedEvent activityChangedEvent = (ActivityChangedEvent) popEvent(); - assertEquals(activityChangedEvent.getAction(), STOPPED); + assertEquals(activityChangedEvent.getAction(), ActivityChanged.ChangeAction.STOPPED); assertSame(activityChangedEvent.getActivity(), stoppedActivity); } } @@ -394,7 +384,7 @@ public void onExecuteTick() { Activities activities = td.newActivities(WEEK); Activity activity = td.newActivity(); - activity.start(); + activity.setStatus(RUNNING); activities.add(activity); cut.ticking = true; cut.runningActivity = activity; diff --git a/src/test/java/name/pehl/karaka/server/activity/entity/ActivityTest.java b/src/test/java/name/pehl/karaka/server/activity/entity/ActivityTest.java index 36d4a26..d3f52d7 100644 --- a/src/test/java/name/pehl/karaka/server/activity/entity/ActivityTest.java +++ b/src/test/java/name/pehl/karaka/server/activity/entity/ActivityTest.java @@ -20,7 +20,9 @@ import com.googlecode.objectify.Key; import name.pehl.karaka.server.tag.entity.Tag; +import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import org.joda.time.Days; import org.junit.Before; import org.junit.Test; @@ -48,22 +50,53 @@ public void setUp() @Test public void newInstance() { - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(0, cut.getMinutes()); - assertEquals(STOPPED, cut.getStatus()); - assertTrue(cut.getTags().isEmpty()); - assertNull(cut.getProject()); - assertFalse(cut.isBillable()); + assertContract(cut); + assertDefaultValues(cut); + } + + @Test + public void copy() + { + cut.start(); + Activity copy = cut.copy(); + + assertContract(copy); + assertEquals(STOPPED, copy.getStatus()); + } + + @Test(expected = IllegalArgumentException.class) + public void copyWithNullPeriod() + { + cut.copy(null); + } + + @Test(expected = IllegalArgumentException.class) + public void copyEmptyNullPeriod() + { + cut.copy(""); + } + + @Test(expected = IllegalArgumentException.class) + public void copyWithIllegalPeriod() + { + cut.copy("bang"); + } + + @Test + public void copyWithPeriod() + { + Activity copy = cut.copy("P1D"); + + assertContract(copy); + assertEquals(1, Days.daysBetween(cut.getStart(), copy.getStart()).getDays()); + assertEquals(1, Days.daysBetween(cut.getEnd(), copy.getEnd()).getDays()); } @Test public void start() { cut.start(); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(0, cut.getMinutes()); + assertContract(cut); assertEquals(RUNNING, cut.getStatus()); } @@ -71,9 +104,46 @@ public void start() public void resume() { cut.resume(); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(0, cut.getMinutes()); + assertContract(cut); + assertEquals(RUNNING, cut.getStatus()); + } + + @Test + public void resumeFuture() + { + Time now = new Time(); + Time start = plusMinutes(10); + Time end = plusMinutes(20); + cut.setEnd(end); + cut.setStart(start); + cut.resume(); + + assertContract(cut); + assertTrue(cut.getStart().isBefore(start)); + assertTrue(cut.getEnd().isBefore(end)); + assertEquals(RUNNING, cut.getStatus()); + } + + @Test + public void tickOnStopped() + { + Time end = cut.getEnd(); + cut.tick(); + + assertContract(cut); + assertDefaultValues(cut); + assertSame(end, cut.getEnd()); + } + + @Test + public void tickOnRunning() + { + Time end = cut.getEnd(); + cut.start(); + cut.tick(); + + assertContract(cut); + assertNotSame(end, cut.getEnd()); assertEquals(RUNNING, cut.getStatus()); } @@ -81,10 +151,95 @@ public void resume() public void stop() { cut.stop(); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(0, cut.getMinutes()); - assertEquals(STOPPED, cut.getStatus()); + assertContract(cut); + assertDefaultValues(cut); + } + + @Test + public void isToday() + { + assertTrue(cut.isToday()); + cut.setStart(plusMinutes(24 * 60 * -1)); + assertFalse(cut.isToday()); + } + + @Test + public void setNullStart() + { + cut.setStart(null); + assertContract(cut); + assertDefaultValues(cut); + } + + @Test + public void setFutureStart() + { + cut.setStart(plusMinutes(10)); + assertContract(cut); + assertDefaultValues(cut); + } + + @Test + public void setStart() + { + Time start = plusMinutes(-10); + cut.setStart(start); + assertContract(cut); + assertEquals(start, cut.getStart()); + } + + @Test + public void setNullEnd() + { + cut.setEnd(null); + assertContract(cut); + assertDefaultValues(cut); + } + + @Test + public void setPastEnd() + { + cut.setEnd(plusMinutes(-10)); + assertContract(cut); + assertDefaultValues(cut); + } + + @Test + public void setEnd() + { + Time end = plusMinutes(10); + cut.setEnd(end); + assertContract(cut); + assertEquals(end, cut.getEnd()); + } + + @Test + public void setNegativePause() + { + cut.setPause(-1); + assertContract(cut); + assertDefaultValues(cut); + } + + @Test + public void setPause() + { + cut.setEnd(plusMinutes(10)); + cut.setPause(5); + assertContract(cut); + assertEquals(5, cut.getPause()); + cut.setPause(11); + assertEquals(5, cut.getPause()); + } + + @Test + public void duration() + { + cut.setEnd(plusMinutes(10)); + assertContract(cut); + assertEquals(10, cut.getDuration()); + cut.setPause(5); + assertEquals(5, cut.getDuration()); } @Test @@ -92,10 +247,36 @@ public void setTags() { List> tags = new ArrayList>(); tags.add(Key.create("agtzfmthcmFrYS1kOHIKCxIDVGFnGMM-DKIBFTEwOTk3NTg1NDcyNjc4OTE3Mzc4NQ")); - - // assure tags are passed by value rather than by reference cut.setTags(tags); tags.clear(); + + // assure tags are passed by value rather than by reference + assertContract(cut); assertEquals(1, cut.getTags().size()); } + + void assertContract(Activity activity) + { + assertNotNull(activity.getStart()); + assertNotNull(activity.getEnd()); + assertFalse(activity.getStart().isAfter(activity.getEnd())); + assertTrue(activity.getDuration() >= 0); + assertTrue(activity.getPause() >= 0); + assertNotNull(activity.getStatus()); + assertNotNull(activity.getTags()); + } + + void assertDefaultValues(Activity activity) + { + assertEquals(STOPPED, activity.getStatus()); + assertEquals(0, activity.getDuration()); + assertEquals(0, activity.getPause()); + assertNull(activity.getProject()); + assertTrue(activity.getTags().isEmpty()); + } + + Time plusMinutes(final int minutes) + { + return new Time(new DateTime().plusMinutes(minutes)); + } } diff --git a/src/test/java/name/pehl/karaka/shared/model/ActivitiesTest.java b/src/test/java/name/pehl/karaka/shared/model/ActivitiesTest.java index 8779351..03271b8 100644 --- a/src/test/java/name/pehl/karaka/shared/model/ActivitiesTest.java +++ b/src/test/java/name/pehl/karaka/shared/model/ActivitiesTest.java @@ -1,19 +1,16 @@ package name.pehl.karaka.shared.model; -import static name.pehl.karaka.shared.model.TimeUnit.DAY; -import static name.pehl.karaka.shared.model.TimeUnit.WEEK; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import com.google.common.testing.EqualsTester; import name.pehl.karaka.TestData; - import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; -import com.google.common.testing.EqualsTester; +import static name.pehl.karaka.shared.model.Status.RUNNING; +import static name.pehl.karaka.shared.model.Status.STOPPED; +import static name.pehl.karaka.shared.model.TimeUnit.DAY; +import static name.pehl.karaka.shared.model.TimeUnit.WEEK; +import static org.junit.Assert.*; /** * TODO Replace current date/times with fixed ones? TODO Move common code to @@ -280,9 +277,9 @@ void internalGetRunningActivity(Activities cut) assertNull(cut.getRunningActivity()); cut.add(activity); assertNull(cut.getRunningActivity()); - activity.start(); + activity.setStatus(RUNNING); assertEquals(activity, cut.getRunningActivity()); - activity.stop(); + activity.setStatus(STOPPED); assertNull(cut.getRunningActivity()); } diff --git a/src/test/java/name/pehl/karaka/shared/model/ActivityTest.java b/src/test/java/name/pehl/karaka/shared/model/ActivityTest.java index 7660929..442cfe1 100644 --- a/src/test/java/name/pehl/karaka/shared/model/ActivityTest.java +++ b/src/test/java/name/pehl/karaka/shared/model/ActivityTest.java @@ -1,17 +1,12 @@ package name.pehl.karaka.shared.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import com.google.common.testing.EqualsTester; import name.pehl.karaka.TestData; - -import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; -import com.google.common.testing.EqualsTester; +import static name.pehl.karaka.shared.model.Status.RUNNING; +import static org.junit.Assert.*; /** * TODO Replace current date/times with fixed ones? @@ -72,113 +67,13 @@ public void copy() assertBlank(blank); // origin must not be changed Activity running = td.newActivity(); - running.start(); + running.setStatus(RUNNING); cut = running.copy(); assertBlank(cut); assertTrue(running.isRunning()); // origin must not be changed } - @Test - public void plus() - { - DateTime now = new DateTime(); - DateTime m1 = now.plusMinutes(1); - Time plusOneMinute = td.newTime(m1); - long oneMinuteInMillis = org.joda.time.Duration.standardMinutes(1).getMillis(); - - Activity blank = td.newActivity(now, null); - Activity cut = blank.plus(oneMinuteInMillis); - assertEquals(plusOneMinute, cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(Duration.ZERO, cut.getPause()); - assertTrue(cut.isStopped()); - assertFalse(cut.isRunning()); - assertNull(cut.getProject()); - assertTrue(cut.getTags().isEmpty()); - assertBlank(blank); // origin must not be changed - } - - - @Test - public void isToday() - { - Activity blank = td.newActivity(); - assertTrue(blank.isToday()); - assertFalse(blank.plus(org.joda.time.Duration.standardDays(1).getMillis()).isToday()); - } - - - @Test - public void start() - { - Activity cut = td.newActivity(); - cut.start(); - assertFalse(cut.isStopped()); - assertTrue(cut.isRunning()); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(Duration.ZERO, cut.getPause()); - } - - - @Test - public void stop() - { - Activity cut = td.newActivity(); - cut.stop(); - assertTrue(cut.isStopped()); - assertFalse(cut.isRunning()); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(Duration.ZERO, cut.getPause()); - } - - - @Test - public void resume() - { - Activity cut = td.newActivity(); - cut.resume(); - assertFalse(cut.isStopped()); - assertTrue(cut.isRunning()); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(Duration.ZERO, cut.getPause()); - - cut = td.newActivity(); - Time plusOneMinute = td.newTime(new DateTime().plusMinutes(1)); - cut.setStart(plusOneMinute); - cut.setEnd(plusOneMinute); - cut.resume(); - assertFalse(cut.isStopped()); - assertTrue(cut.isRunning()); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(Duration.ZERO, cut.getPause()); - - // TODO Test resume with pause - } - - - @Test - public void tick() - { - Activity cut = td.newActivity(); - cut.start(); - cut.tick(); - assertFalse(cut.isStopped()); - assertTrue(cut.isRunning()); - assertNotNull(cut.getStart()); - assertNotNull(cut.getEnd()); - assertEquals(Duration.ZERO, cut.getPause()); - - cut = td.newActivity(); - cut.tick(); - assertBlank(cut); - } - - @Test(expected = IllegalArgumentException.class) public void startMustNotBeNull() {