Skip to content
Snippets Groups Projects
Commit a3cc3924 authored by srosse's avatar srosse
Browse files

OO-3653: implement lock on edited portfolio pages

Add a GUI lock on the edited pages in portfolio to prevent concurrent
edition of the same contribution. Add system wide events to reload
changes for non editing users
parent 171e4aec
No related branches found
No related tags found
No related merge requests found
......@@ -64,11 +64,10 @@ public class DialogBoxUIFactory {
*/
public static DialogBoxController createYesNoDialog(UserRequest ureq, WindowControl wControl, String title, String text) {
Translator trans = Util.createPackageTranslator(DialogBoxUIFactory.class, ureq.getLocale());
List<String> yesNoButtons = new ArrayList<String>();
List<String> yesNoButtons = new ArrayList<>();
yesNoButtons.add(trans.translate("yes"));
yesNoButtons.add(trans.translate("no"));
DialogBoxController dialogCtr = new DialogBoxController(ureq, wControl, title, text, yesNoButtons);
return dialogCtr;
return new DialogBoxController(ureq, wControl, title, text, yesNoButtons);
}
/**
......@@ -85,11 +84,10 @@ public class DialogBoxUIFactory {
*/
public static DialogBoxController createOkCancelDialog(UserRequest ureq, WindowControl wControl, String title, String text) {
Translator trans = Util.createPackageTranslator(DialogBoxUIFactory.class, ureq.getLocale());
List<String> okCancelButtons = new ArrayList<String>();
List<String> okCancelButtons = new ArrayList<>();
okCancelButtons.add(trans.translate("ok"));
okCancelButtons.add(trans.translate("cancel"));
DialogBoxController dialogCtr = new DialogBoxController(ureq, wControl, title, text, okCancelButtons);
return dialogCtr;
return new DialogBoxController(ureq, wControl, title, text, okCancelButtons);
}
/**
......@@ -125,7 +123,7 @@ public class DialogBoxUIFactory {
String lockMsg = translator.translate(i18nLockMsgKey, i18nParams);
Translator trans = Util.createPackageTranslator(DialogBoxUIFactory.class, ureq.getLocale());
List<String> okButton = new ArrayList<String>();
List<String> okButton = new ArrayList<>();
okButton.add(trans.translate("ok"));
DialogBoxController ctrl = new DialogBoxController(ureq, wControl, null, lockMsg, okButton);
......
......@@ -79,6 +79,10 @@ public class PageController extends BasicController {
return fragmentsCmp.validateElements(ureq, messages);
}
/**
* @param ureq the user request
* @param reuse reuse the components already available
*/
public void loadElements(UserRequest ureq) {
List<? extends PageElement> elements = provider.getElements();
List<PageFragment> fragments = new ArrayList<>(elements.size());
......
......@@ -41,27 +41,24 @@ public class PageFragmentsComponent extends AbstractComponent implements Compone
private static final PageFragmentsComponentRenderer RENDERER = new PageFragmentsComponentRenderer();
private List<? extends PageFragment> fragments;
private List<Component> components = new ArrayList<>();
public PageFragmentsComponent(String name) {
super(name);
}
public List<? extends PageFragment> getFragments() {
return fragments;
public List<PageFragment> getFragments() {
return new ArrayList<>(fragments);
}
public void setFragments(List<? extends PageFragment> fragments) {
this.fragments = fragments;
components.clear();
for(PageFragment fragment:fragments) {
components.add(fragment.getComponent());
}
setDirty(true);
}
@Override
public Component getComponent(String name) {
for(PageFragment fragment:fragments) {
List<PageFragment> fragmentList = getFragments();
for(PageFragment fragment:fragmentList) {
if(fragment.getComponent().getComponentName().equals(name)) {
return fragment.getComponent();
}
......@@ -71,16 +68,20 @@ public class PageFragmentsComponent extends AbstractComponent implements Compone
@Override
public Iterable<Component> getComponents() {
List<PageFragment> fragmentList = getFragments();
List<Component> components = new ArrayList<>(fragmentList.size());
for(PageFragment fragment:fragmentList) {
components.add(fragment.getComponent());
}
return components;
}
public boolean validateElements(UserRequest ureq, List<ValidationMessage> messages) {
boolean allOk = true;
for(PageFragment fragment:fragments) {
List<PageFragment> fragmentList = getFragments();
for(PageFragment fragment:fragmentList) {
allOk &= fragment.getPageRunElement().validate(ureq, messages);
}
return allOk;
}
......
......@@ -47,10 +47,15 @@ import org.olat.core.gui.control.generic.dtabs.Activateable2;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.util.SyntheticUserRequest;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.id.context.StateEntry;
import org.olat.core.util.StringHelper;
import org.olat.core.util.UserSession;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.coordinate.LockResult;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.resource.OresHelper;
import org.olat.modules.ceditor.InteractiveAddPageElementHandler;
import org.olat.modules.ceditor.PageEditorProvider;
......@@ -90,6 +95,7 @@ import org.olat.modules.portfolio.model.StandardMediaRenderingHints;
import org.olat.modules.portfolio.ui.event.ClosePageEvent;
import org.olat.modules.portfolio.ui.event.DonePageEvent;
import org.olat.modules.portfolio.ui.event.MediaSelectionEvent;
import org.olat.modules.portfolio.ui.event.PageChangedEvent;
import org.olat.modules.portfolio.ui.event.PageDeletedEvent;
import org.olat.modules.portfolio.ui.event.PageRemovedEvent;
import org.olat.modules.portfolio.ui.event.PublishEvent;
......@@ -107,7 +113,7 @@ import org.springframework.beans.factory.annotation.Autowired;
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class PageRunController extends BasicController implements TooledController, Activateable2 {
public class PageRunController extends BasicController implements TooledController, GenericEventListener, Activateable2 {
private VelocityContainer mainVC;
private Link previousPageLink;
......@@ -126,15 +132,21 @@ public class PageRunController extends BasicController implements TooledControll
private ConfirmClosePageController confirmDonePageCtrl;
private DialogBoxController confirmPublishCtrl, confirmRevisionCtrl, confirmCloseCtrl,
confirmReopenCtrl, confirmMoveToTrashCtrl, confirmDeleteCtrl;
private DialogBoxController alreadyLockedDialogController;
private PageMetadataEditController editMetadataCtrl;
private UserCommentsAndRatingsController commentsCtrl;
private Page page;
private LockResult lockEntry;
private OLATResourceable lockOres;
private List<Assignment> assignments;
private final UserSession userSession;
private boolean dirtyMarker = false;
private final boolean openInEditMode;
private final BinderSecurityCallback secCallback;
@Autowired
private CoordinatorManager coordinator;
@Autowired
private PortfolioService portfolioService;
......@@ -144,7 +156,14 @@ public class PageRunController extends BasicController implements TooledControll
this.page = page;
this.stackPanel = stackPanel;
this.secCallback = secCallback;
this.openInEditMode = openEditMode && page.isEditable();
lockOres = OresHelper.createOLATResourceableInstance("Page", page.getKey());
userSession = ureq.getUserSession();
if(openEditMode && page.isEditable()) {
lockEntry = coordinator.getCoordinator().getLocker().acquireLock(lockOres, getIdentity(), "");
}
this.openInEditMode = openEditMode && page.isEditable() && (lockEntry != null && lockEntry.isSuccess());
coordinator.getCoordinator().getEventBus().registerFor(this, getIdentity(), lockOres);
assignments = portfolioService.getAssignments(page, null);
......@@ -230,6 +249,10 @@ public class PageRunController extends BasicController implements TooledControll
}
}
/**
* @param edit The wanted state of the links
* @return The edit link
*/
private Link editLink(boolean edit) {
if(page.isEditable()) {
if(editLink == null) {
......@@ -313,9 +336,25 @@ public class PageRunController extends BasicController implements TooledControll
@Override
protected void doDispose() {
//
if (lockEntry != null && lockEntry.isSuccess()) {
// release lock
coordinator.getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
}
coordinator.getCoordinator().getEventBus().deregisterFor(this, lockOres);
}
@Override
public void event(Event event) {
if(event instanceof PageChangedEvent) {
PageChangedEvent pce = (PageChangedEvent)event;
if(!pce.isMe(getIdentity()) && page.getKey().equals(pce.getPageKey())) {
dirtyMarker = false;
pageCtrl.loadElements(new SyntheticUserRequest(getIdentity(), getLocale(), userSession));
}
}
}
@Override
public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
if(entries == null || entries.isEmpty()) return;
......@@ -332,6 +371,8 @@ public class PageRunController extends BasicController implements TooledControll
if(pageEditCtrl == source) {
if(event == Event.CHANGED_EVENT) {
dirtyMarker = true;
coordinator.getCoordinator().getEventBus()
.fireEventToListenersOf(new PageChangedEvent(getIdentity().getKey(), page.getKey()), lockOres);
} else if(event instanceof PublishEvent) {
doConfirmPublish(ureq);
}
......@@ -578,22 +619,35 @@ public class PageRunController extends BasicController implements TooledControll
private void doEditPage(UserRequest ureq) {
removeAsListenerAndDispose(pageEditCtrl);
if(Boolean.FALSE.equals(editLink.getUserObject())) {
if(lockEntry != null && lockEntry.isSuccess()) {
coordinator.getCoordinator().getLocker().releaseLock(lockEntry);
}
doRunPage(ureq);
editLink(true);
// Add comments controller again in run mode, maybe removed by
// previous edit mode entering
if(commentsCtrl != null) {
mainVC.put("comments", commentsCtrl.getInitialComponent());
}
} else {
pageEditCtrl = new PageEditorController(ureq, getWindowControl(), new PortfolioPageEditorProvider(),
new FullEditorSecurityCallback(), getTranslator());
listenTo(pageEditCtrl);
mainVC.contextPut("isPersonalBinder", (!secCallback.canNewAssignment() && secCallback.canEditMetadataBinder()));
mainVC.put("page", pageEditCtrl.getInitialComponent());
editLink(false);
// Remove comments controller in edit mode, save button confuses user
if(commentsCtrl != null && commentsCtrl.getCommentsCount() == 0) {
mainVC.remove(commentsCtrl.getInitialComponent());
lockEntry = coordinator.getCoordinator().getLocker().acquireLock(lockOres, getIdentity(), "");
if(lockEntry.isSuccess()) {
pageEditCtrl = new PageEditorController(ureq, getWindowControl(), new PortfolioPageEditorProvider(),
new FullEditorSecurityCallback(), getTranslator());
listenTo(pageEditCtrl);
mainVC.contextPut("isPersonalBinder", (!secCallback.canNewAssignment() && secCallback.canEditMetadataBinder()));
mainVC.put("page", pageEditCtrl.getInitialComponent());
editLink(false);
// Remove comments controller in edit mode, save button confuses user
if(commentsCtrl != null && commentsCtrl.getCommentsCount() == 0) {
mainVC.remove(commentsCtrl.getInitialComponent());
}
} else {
removeAsListenerAndDispose(alreadyLockedDialogController);
alreadyLockedDialogController = DialogBoxUIFactory.createResourceLockedMessage(ureq, getWindowControl(), lockEntry, "warning.page.locked", getTranslator());
listenTo(alreadyLockedDialogController);
alreadyLockedDialogController.activate();
}
}
}
......
......@@ -428,5 +428,6 @@ warning.binder.synched=Die Portfolioaufgabe wurde mit ihrer Vorlage synchronisie
warning.evaluation.not.visible.text=Zur Zeit ist die Einsch\u00E4tzung nicht sichtbar.
warning.evaluation.not.visible.title=Einsch\u00E4tzung nicht sichtbar
warning.portfolio.not.found=Die Portfolio Mappe konnte nicht gefunden werden. Sie wurde wahrscheinlich gel\u00F6scht.
warning.page.locked=Dieser Eintrag Bewertung wird im Moment vom Benutzer {0} ({1}) ver\u00E4ndert und ist daher gesperrt. Bitte versuchen Sie es sp\u00E4ter noch einmal.
warning.page.not.found=Der Eintrag konnte nicht gefunden werden. Sie wurde wahrscheinlich gel\u00F6scht.
warning.template.in.use=Die Vorlage konnte nicht gel\u00F6scht werden, weil sie von einigen Benutzern in Verwendung ist.
......@@ -426,6 +426,7 @@ volume=Volume
warning.binder.synched=The binder is synchronized with its template.
warning.evaluation.not.visible.text=The evaluation is for the moment not visible.
warning.evaluation.not.visible.title=Evaluation not visible
warning.page.locked=This entry is currently modified by user {0} ({1}) and is therefore locked. Please try again later.
warning.page.not.found=The entry cannot be found, probably deleted in the mean time
warning.portfolio.not.found=The portfolio cannot be found, probably deleted in the mean time
warning.template.in.use=The template cannot deleted because some users use it.
......@@ -419,6 +419,7 @@ volume=Volume
warning.binder.synched=Le classeur a \u00E9t\u00E9 synchronis\u00E9 avec son mod\u00E8le.
warning.evaluation.not.visible.text=L'\u00E9valuation n'est pas visible pour l'instant.
warning.evaluation.not.visible.title=Evaluation pas visible
warning.page.locked=Cette contribution est momentan\u00E9ment bloqu\u00E9e par l'utilisateur {0} ({1}). Veuillez essayer d'y acc\u00E9der plus tard SVP.
warning.page.not.found=La contribution n'a pas pu \u00EAtre trouv\u00E9e. Elle a \u00E9t\u00E9 probablement effac\u00E9e entre-temps.
warning.portfolio.not.found=Le classeur n'a pas pu \u00EAtre trouv\u00E9, il a \u00E9t\u00E9 probablement effac\u00E9 entre temps.
warning.template.in.use=Le mod\u00E8le n'a pas pu \u00EAtre effac\u00E9 car il est utilis\u00E9 par d'autres utilisateurs.
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.modules.portfolio.ui.event;
import org.olat.basesecurity.IdentityRef;
import org.olat.core.util.event.MultiUserEvent;
/**
*
* Initial date: 2 Oct 2018<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class PageChangedEvent extends MultiUserEvent {
private static final long serialVersionUID = -6099560236380196678L;
public static final String PAGE_CHANGED = "portfolio-page-changed";
private final Long senderIdentityKey;
private final Long pageKey;
public PageChangedEvent(Long senderIdentityKey, Long pageKey) {
super(PAGE_CHANGED);
this.senderIdentityKey = senderIdentityKey;
this.pageKey = pageKey;
}
public Long getSenderIdentityKey() {
return senderIdentityKey;
}
public Long getPageKey() {
return pageKey;
}
public boolean isMe(IdentityRef me) {
return senderIdentityKey != null && senderIdentityKey.equals(me.getKey());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment