From d823fbf418d316b2754d4e8a4b75769dc5545bdc Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Mon, 9 Oct 2023 20:28:57 +0200 Subject: [PATCH] #4235 - Direct access-by-URL to sidebar curation mode sometimes does not work - Use alternative approach to react to the URL parameters --- .../events/BeforeDocumentOpenedEvent.java | 2 + .../event/HybridApplicationUIEventRouter.java | 7 + .../sidebar/CurationSidebarBehavior.java | 173 ++++++++++++++++++ .../sidebar/CurationSidebarFactory.java | 8 +- .../curation/sidebar/CurationSidebarIcon.java | 104 +---------- 5 files changed, 198 insertions(+), 96 deletions(-) create mode 100644 inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BeforeDocumentOpenedEvent.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BeforeDocumentOpenedEvent.java index 9161db21c16..d17ce5a7668 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BeforeDocumentOpenedEvent.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/events/BeforeDocumentOpenedEvent.java @@ -21,6 +21,7 @@ import org.springframework.context.ApplicationEvent; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.clarin.webanno.support.wicket.event.HybridApplicationUIEvent; /** * Fire BeforeDocumentOpenedEvent to give listeners a chance to look at or even modify the CAS @@ -29,6 +30,7 @@ */ public class BeforeDocumentOpenedEvent extends ApplicationEvent + implements HybridApplicationUIEvent { private static final long serialVersionUID = -4644605041626140906L; diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/clarin/webanno/support/wicket/event/HybridApplicationUIEventRouter.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/clarin/webanno/support/wicket/event/HybridApplicationUIEventRouter.java index 64a622fe7f9..7ce951b4da8 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/clarin/webanno/support/wicket/event/HybridApplicationUIEventRouter.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/clarin/webanno/support/wicket/event/HybridApplicationUIEventRouter.java @@ -23,6 +23,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.event.Broadcast; import org.apache.wicket.feedback.IFeedback; +import org.apache.wicket.request.RequestHandlerExecutor.ReplaceHandlerException; import org.apache.wicket.request.cycle.RequestCycle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,12 +67,18 @@ public void routeEvent(HybridApplicationUIEvent aEvent) try { page.send(page, Broadcast.BREADTH, aEvent); } + catch (ReplaceHandlerException e) { + throw e; + } catch (Throwable e) { log.error("Exception while processing UI-routed event", e); page.error("Exception while processing UI-routed event: " + e.getMessage()); handler.get().addChildren(page, IFeedback.class); } } + catch (ReplaceHandlerException e) { + throw e; + } catch (Throwable e) { log.error("Unable to route event to UI", e); } diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java new file mode 100644 index 00000000000..ff17617309a --- /dev/null +++ b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarBehavior.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.curation.sidebar; + +import static de.tudarmstadt.ukp.clarin.webanno.support.WebAnnoConst.CURATION_USER; +import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.setProjectPageParameter; +import static java.util.Arrays.asList; + +import java.lang.invoke.MethodHandles; + +import org.apache.wicket.Component; +import org.apache.wicket.RestartResponseException; +import org.apache.wicket.behavior.Behavior; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; +import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage; +import de.tudarmstadt.ukp.inception.annotation.events.BeforeDocumentOpenedEvent; + +public class CurationSidebarBehavior + extends Behavior +{ + private static final long serialVersionUID = -6224298395673360592L; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final String STAY = "stay"; + private static final String OFF = "off"; + private static final String ON = "on"; + + private static final String PARAM_CURATION_SESSION = "curationSession"; + private static final String PARAM_CURATION_TARGET_OWN = "curationTargetOwn"; + + private @SpringBean CurationSidebarService curationSidebarService; + private @SpringBean UserDao userService; + + @Override + public void onConfigure(Component aComponent) + { + super.onConfigure(aComponent); + + var page = aComponent.getPage(); + if (!(page instanceof AnnotationPage)) { + return; + } + + var annotationPage = (AnnotationPage) page; + + if (annotationPage.getModelObject().getDocument() == null) { + return; + } + + handleCurationSessionPageParameters(annotationPage); + + handleWrongAnnotatorUserInState(annotationPage); + } + + @Override + public void onEvent(Component aComponent, IEvent aEvent) + { + if (aEvent.getPayload() instanceof BeforeDocumentOpenedEvent) { + var event = (BeforeDocumentOpenedEvent) aEvent.getPayload(); + var page = event.getRequestTarget().getPage(); + + if (!(page instanceof AnnotationPage)) { + return; + } + + var annotationPage = (AnnotationPage) page; + + handleCurationSessionPageParameters(annotationPage); + + handleWrongAnnotatorUserInState(annotationPage); + } + } + + private void handleWrongAnnotatorUserInState(AnnotationPage aPage) + { + if (isViewingPotentialCurationTarget(aPage) && isSessionActive(aPage)) { + var sessionOwner = userService.getCurrentUsername(); + var state = aPage.getModelObject(); + + // If curation is possible and the curation target user is different from the user set + // in the annotation state, then we need to update the state and reload. + var curationTarget = curationSidebarService.getCurationTargetUser(sessionOwner, + state.getProject().getId()); + if (!state.getUser().equals(curationTarget)) { + LOG.trace("Wrong user in state, setting and reloading"); + state.setUser(curationTarget); + aPage.actionLoadDocument(null); + RequestCycle.get().setResponsePage(aPage); + } + } + } + + private void handleCurationSessionPageParameters(AnnotationPage aPage) + { + var params = aPage.getPageParameters(); + + var curationSessionParameterValue = params.get(PARAM_CURATION_SESSION); + var curationTargetOwnParameterValue = params.get(PARAM_CURATION_TARGET_OWN); + var project = aPage.getModelObject().getProject(); + var sessionOwner = userService.getCurrentUsername(); + + switch (curationSessionParameterValue.toString(STAY)) { + case ON: + LOG.trace("Checking if to start curation session"); + // Start a new session or switch to new curation target + if (!isSessionActive(aPage) || !curationTargetOwnParameterValue.isEmpty()) { + curationSidebarService.startSession(sessionOwner, project, + curationTargetOwnParameterValue.toBoolean(false)); + } + break; + case OFF: + LOG.trace("Checking if to stop curation session"); + if (isSessionActive(aPage)) { + curationSidebarService.closeSession(sessionOwner, project.getId()); + } + break; + default: + // Ignore + } + + if (!curationSessionParameterValue.isEmpty()) { + LOG.trace("Reloading page without session parameters"); + params.remove(PARAM_CURATION_TARGET_OWN); + params.remove(PARAM_CURATION_SESSION); + setProjectPageParameter(params, project); + params.set(AnnotationPage.PAGE_PARAM_DOCUMENT, + aPage.getModelObject().getDocument().getId()); + throw new RestartResponseException(aPage.getClass(), params); + } + } + + private boolean isViewingPotentialCurationTarget(AnnotationPage aPage) + { + // Curation sidebar is not allowed when viewing another users annotations + var sessionOwner = userService.getCurrentUsername(); + var state = aPage.getModelObject(); + return asList(CURATION_USER, sessionOwner).contains(state.getUser().getUsername()); + } + + private boolean isSessionActive(AnnotationPage aPage) + { + var sessionOwner = userService.getCurrentUsername(); + var project = aPage.getModelObject().getProject(); + if (project != null + && curationSidebarService.existsSession(sessionOwner, project.getId())) { + return true; + } + + return false; + } +} diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarFactory.java b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarFactory.java index 20e359a07b7..a4b6b1832b4 100644 --- a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarFactory.java +++ b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarFactory.java @@ -73,7 +73,13 @@ public AnnotationSidebar_ImplBase create(String aId, IModel aMod AnnotationActionHandler aActionHandler, CasProvider aCasProvider, AnnotationPage aAnnotationPage) { - return new CurationSidebar(aId, aModel, aActionHandler, aCasProvider, aAnnotationPage); + var sidebar = new CurationSidebar(aId, aModel, aActionHandler, aCasProvider, + aAnnotationPage); + if (aAnnotationPage.getBehaviors(CurationSidebarBehavior.class).isEmpty()) { + aAnnotationPage.add(new CurationSidebarBehavior()); + } + + return sidebar; } @Override diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarIcon.java b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarIcon.java index 71f59ed38d2..98b4ff65c8d 100644 --- a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarIcon.java +++ b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/inception/ui/curation/sidebar/CurationSidebarIcon.java @@ -17,29 +17,18 @@ */ package de.tudarmstadt.ukp.inception.ui.curation.sidebar; -import static de.tudarmstadt.ukp.clarin.webanno.support.WebAnnoConst.CURATION_USER; -import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.setProjectPageParameter; -import static java.util.Arrays.asList; - -import java.lang.invoke.MethodHandles; import java.util.Set; import org.apache.wicket.ClassAttributeModifier; -import org.apache.wicket.RestartResponseException; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.spring.injection.annot.SpringBean; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import de.agilecoders.wicket.core.markup.html.bootstrap.image.Icon; import de.agilecoders.wicket.core.markup.html.bootstrap.image.IconType; import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType; -import de.tudarmstadt.ukp.clarin.webanno.api.ProjectService; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; -import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; public class CurationSidebarIcon @@ -47,18 +36,8 @@ public class CurationSidebarIcon { private static final long serialVersionUID = -1870047500327624860L; - private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - private static final String STAY = "stay"; - private static final String OFF = "off"; - private static final String ON = "on"; - - private static final String PARAM_CURATION_SESSION = "curationSession"; - private static final String PARAM_CURATION_TARGET_OWN = "curationTargetOwn"; - private @SpringBean CurationSidebarService curationSidebarService; private @SpringBean UserDao userService; - private @SpringBean ProjectService projectService; public CurationSidebarIcon(String aId, IModel aState) { @@ -86,63 +65,6 @@ protected Set update(Set aClasses) })); } - @Override - protected void onInitialize() - { - super.onInitialize(); - - handleCurationSessionPageParameters(); - - if (isViewingPotentialCurationTarget() && isSessionActive()) { - String sessionOwner = userService.getCurrentUsername(); - AnnotatorState state = getModelObject(); - - // If curation is possible and the curation target user is different from the user set - // in the annotation state, then we need to update the state and reload. - User curationTarget = curationSidebarService.getCurationTargetUser(sessionOwner, - state.getProject().getId()); - if (!state.getUser().equals(curationTarget)) { - state.setUser(curationTarget); - findParent(AnnotationPage.class).actionLoadDocument(null); - throw new RestartResponseException(getPage()); - } - } - } - - private void handleCurationSessionPageParameters() - { - var params = getPage().getPageParameters(); - var curationSessionParameterValue = params.get(PARAM_CURATION_SESSION); - var curationTargetOwnParameterValue = params.get(PARAM_CURATION_TARGET_OWN); - var project = getModelObject().getProject(); - String sessionOwner = userService.getCurrentUsername(); - - switch (curationSessionParameterValue.toString(STAY)) { - case ON: - // Start a new session or switch to new curation target - if (!isSessionActive() || !curationTargetOwnParameterValue.isEmpty()) { - curationSidebarService.startSession(sessionOwner, project, - curationTargetOwnParameterValue.toBoolean(false)); - } - break; - case OFF: - if (isSessionActive()) { - curationSidebarService.closeSession(sessionOwner, project.getId()); - } - break; - default: - // Ignore - } - - if (!curationSessionParameterValue.isEmpty()) { - params.remove(PARAM_CURATION_TARGET_OWN); - params.remove(PARAM_CURATION_SESSION); - setProjectPageParameter(params, project); - params.set(AnnotationPage.PAGE_PARAM_DOCUMENT, getModelObject().getDocument().getId()); - throw new RestartResponseException(getPage().getClass(), params); - } - } - @SuppressWarnings("unchecked") public IModel getModel() { @@ -154,6 +76,15 @@ public AnnotatorState getModelObject() return (AnnotatorState) getDefaultModelObject(); } + private IconType getStateIcon() + { + if (isSessionActive()) { + return FontAwesome5IconType.play_circle_s; + } + + return FontAwesome5IconType.stop_circle_s; + } + private boolean isSessionActive() { var project = getModelObject().getProject(); @@ -164,21 +95,4 @@ private boolean isSessionActive() return false; } - - private boolean isViewingPotentialCurationTarget() - { - // Curation sidebar is not allowed when viewing another users annotations - String currentUsername = userService.getCurrentUsername(); - AnnotatorState state = getModelObject(); - return asList(CURATION_USER, currentUsername).contains(state.getUser().getUsername()); - } - - private IconType getStateIcon() - { - if (isSessionActive()) { - return FontAwesome5IconType.play_circle_s; - } - - return FontAwesome5IconType.stop_circle_s; - } }