diff --git a/mindplot/src/main/javascript/RestPersistenceManager.js b/mindplot/src/main/javascript/RestPersistenceManager.js index b7d5b2aa..747815b8 100644 --- a/mindplot/src/main/javascript/RestPersistenceManager.js +++ b/mindplot/src/main/javascript/RestPersistenceManager.js @@ -18,14 +18,19 @@ mindplot.RESTPersistenceManager = new Class({ Extends:mindplot.PersistenceManager, - initialize:function (saveUrl, revertUrl, lockUrl) { + initialize:function (options) { this.parent(); - $assert(saveUrl, "saveUrl can not be null"); - $assert(revertUrl, "revertUrl can not be null"); - this.saveUrl = saveUrl; - this.revertUrl = revertUrl; - this.lockUrl = lockUrl; - this.timestamp = null; + $assert(options.saveUrl, "saveUrl can not be null"); + $assert(options.revertUrl, "revertUrl can not be null"); + $assert(options.lockUrl, "lockUrl can not be null"); + $assert(options.session, "session can not be null"); + $assert(options.timestamp, "timestamp can not be null"); + + this.saveUrl = options.saveUrl; + this.revertUrl = options.revertUrl; + this.lockUrl = options.lockUrl; + this.timestamp = options.timestamp; + this.session = options.session; }, saveMapXml:function (mapId, mapXml, pref, saveHistory, events, sync) { @@ -39,6 +44,7 @@ mindplot.RESTPersistenceManager = new Class({ var persistence = this; var query = "minor=" + !saveHistory; query = query + (this.timestamp ? "×tamp=" + this.timestamp : ""); + query = query + (this.session ? "&session=" + this.session : ""); var request = new Request({ url:this.saveUrl.replace("{id}", mapId) + "?" + query, diff --git a/mindplot/src/main/javascript/widget/ErrorNotifier.js b/mindplot/src/main/javascript/widget/ErrorNotifier.js new file mode 100644 index 00000000..fe0246d7 --- /dev/null +++ b/mindplot/src/main/javascript/widget/ErrorNotifier.js @@ -0,0 +1,83 @@ +/* + * Copyright [2011] [wisemapping] + * + * Licensed under WiseMapping Public License, Version 1.0 (the "License"). + * It is basically the Apache License, Version 2.0 (the "License") plus the + * "powered by wisemapping" text requirement on every single page; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the license at + * + * http://www.wisemapping.org/license + * + * 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. + */ + +mindplot.widget.ToolbarNotifier = new Class({ + + initialize:function () { + var container = $('headerNotifier'); + // In case of print,embedded no message is displayed .... + if (container) { + this._effect = new Fx.Elements(container, { + onComplete:function () { + container.setStyle('display', 'none'); + }.bind(this), + link:'cancel', + duration:8000, + transition:Fx.Transitions.Expo.easeInOut + }); + } + }, + + logError:function (userMsg) { + this.logMessage(userMsg, mindplot.widget.ToolbarNotifier.MsgKind.ERROR); + }, + + hide:function () { + + }, + + logMessage:function (msg, fade) { + $assert(msg, 'msg can not be null'); + + var container = $('headerNotifier'); + + // In case of print,embedded no message is displayed .... + if (container) { + container.set('text', msg); + container.setStyle('display', 'block'); + container.position({ + relativeTo:$('header'), + position:'upperCenter', + edge:'centerTop' + }); + + if (!$defined(fade) || fade) { + this._effect.start({ + 0:{ + opacity:[1, 0] + } + }); + + } else { + container.setStyle('opacity', '1'); + this._effect.pause(); + } + } + } + +}); + +mindplot.widget.ToolbarNotifier.MsgKind = { + INFO:1, + WARNING:2, + ERROR:3, + FATAL:4 +}; + +var toolbarNotifier = new mindplot.widget.ToolbarNotifier(); +$notify = toolbarNotifier.logMessage.bind(toolbarNotifier); \ No newline at end of file diff --git a/wise-webapp/src/main/java/com/wisemapping/exceptions/EditionSessionExpiredException.java b/wise-webapp/src/main/java/com/wisemapping/exceptions/EditionSessionExpiredException.java new file mode 100755 index 00000000..e34c4781 --- /dev/null +++ b/wise-webapp/src/main/java/com/wisemapping/exceptions/EditionSessionExpiredException.java @@ -0,0 +1,38 @@ +/* +* Copyright [2011] [wisemapping] +* +* Licensed under WiseMapping Public License, Version 1.0 (the "License"). +* It is basically the Apache License, Version 2.0 (the "License") plus the +* "powered by wisemapping" text requirement on every single page; +* you may not use this file except in compliance with the License. +* You may obtain a copy of the license at +* +* http://www.wisemapping.org/license +* +* 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 com.wisemapping.exceptions; + +import org.jetbrains.annotations.NotNull; + +public class EditionSessionExpiredException + extends ClientException +{ + public static final String MSG_KEY = "MINDMAP_TIMESTAMP_OUTDATED"; + + public EditionSessionExpiredException(@NotNull String msg) + { + super(msg); + } + + @NotNull + @Override + protected String getMsgBundleKey() { + return MSG_KEY; + } +} diff --git a/wise-webapp/src/main/java/com/wisemapping/exceptions/MindmapOutdatedException.java b/wise-webapp/src/main/java/com/wisemapping/exceptions/MultipleSessionsOpenException.java similarity index 91% rename from wise-webapp/src/main/java/com/wisemapping/exceptions/MindmapOutdatedException.java rename to wise-webapp/src/main/java/com/wisemapping/exceptions/MultipleSessionsOpenException.java index 87a65e91..7014001e 100755 --- a/wise-webapp/src/main/java/com/wisemapping/exceptions/MindmapOutdatedException.java +++ b/wise-webapp/src/main/java/com/wisemapping/exceptions/MultipleSessionsOpenException.java @@ -20,12 +20,12 @@ package com.wisemapping.exceptions; import org.jetbrains.annotations.NotNull; -public class MindmapOutdatedException +public class MultipleSessionsOpenException extends ClientException { public static final String MSG_KEY = "MINDMAP_TIMESTAMP_OUTDATED"; - public MindmapOutdatedException(@NotNull String msg) + public MultipleSessionsOpenException(@NotNull String msg) { super(msg); } diff --git a/wise-webapp/src/main/java/com/wisemapping/exceptions/SessionExpiredException.java b/wise-webapp/src/main/java/com/wisemapping/exceptions/SessionExpiredException.java new file mode 100755 index 00000000..392a5b90 --- /dev/null +++ b/wise-webapp/src/main/java/com/wisemapping/exceptions/SessionExpiredException.java @@ -0,0 +1,39 @@ +/* +* Copyright [2011] [wisemapping] +* +* Licensed under WiseMapping Public License, Version 1.0 (the "License"). +* It is basically the Apache License, Version 2.0 (the "License") plus the +* "powered by wisemapping" text requirement on every single page; +* you may not use this file except in compliance with the License. +* You may obtain a copy of the license at +* +* http://www.wisemapping.org/license +* +* 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 com.wisemapping.exceptions; + +import com.wisemapping.model.Collaborator; +import org.jetbrains.annotations.NotNull; + +public class SessionExpiredException + extends ClientException +{ + public static final String MSG_KEY = "MINDMAP_TIMESTAMP_OUTDATED"; + + public SessionExpiredException(@NotNull String msg,@NotNull Collaborator newEditor) + { + super(msg); + } + + @NotNull + @Override + protected String getMsgBundleKey() { + return MSG_KEY; + } +} diff --git a/wise-webapp/src/main/java/com/wisemapping/ncontroller/MindmapController.java b/wise-webapp/src/main/java/com/wisemapping/ncontroller/MindmapController.java index b154e0e2..dbae734f 100644 --- a/wise-webapp/src/main/java/com/wisemapping/ncontroller/MindmapController.java +++ b/wise-webapp/src/main/java/com/wisemapping/ncontroller/MindmapController.java @@ -27,6 +27,7 @@ import com.wisemapping.model.Mindmap; import com.wisemapping.model.MindMapHistory; import com.wisemapping.model.User; import com.wisemapping.security.Utils; +import com.wisemapping.service.LockInfo; import com.wisemapping.service.LockManager; import com.wisemapping.service.MindmapService; import com.wisemapping.view.MindMapBean; @@ -147,7 +148,7 @@ public class MindmapController { return showEditorPage(id, model, true); } - private String showEditorPage(int id, @NotNull final Model model, boolean requiresLock) throws AccessDeniedSecurityException, LockException { + private String showEditorPage(int id, @NotNull final Model model, boolean requiresLock) throws WiseMappingException { final MindMapBean mindmapBean = findMindmapBean(id); final Mindmap mindmap = mindmapBean.getDelegated(); final User collaborator = Utils.getUser(); @@ -159,10 +160,13 @@ public class MindmapController { final LockManager lockManager = this.mindmapService.getLockManager(); if (lockManager.isLocked(mindmap) && !lockManager.isLockedBy(mindmap, collaborator)) { readOnlyMode = true; - model.addAttribute("lockedBy", lockManager.getLockInfo(mindmap)); } else { - lockManager.lock(mindmap, collaborator); + final long session = lockManager.generateSession(); + final LockInfo lock = lockManager.lock(mindmap, collaborator, session); + model.addAttribute("lockTimestamp", lock.getTimestamp()); + model.addAttribute("lockSession", session); } + model.addAttribute("lockInfo", lockManager.getLockInfo(mindmap)); } // Set render attributes ... @@ -176,12 +180,12 @@ public class MindmapController { } @RequestMapping(value = "maps/{id}/view", method = RequestMethod.GET) - public String showMindmapViewerPage(@PathVariable int id, @NotNull Model model) throws LockException, AccessDeniedSecurityException { + public String showMindmapViewerPage(@PathVariable int id, @NotNull Model model) throws WiseMappingException { return showEditorPage(id, model, false); } @RequestMapping(value = "maps/{id}/try", method = RequestMethod.GET) - public String showMindmapTryPage(@PathVariable int id, @NotNull Model model) throws LockException, AccessDeniedSecurityException { + public String showMindmapTryPage(@PathVariable int id, @NotNull Model model) throws WiseMappingException { final String result = showEditorPage(id, model, false); model.addAttribute("memoryPersistence", true); return result; diff --git a/wise-webapp/src/main/java/com/wisemapping/rest/MindmapController.java b/wise-webapp/src/main/java/com/wisemapping/rest/MindmapController.java index b52a5fec..5738aa15 100644 --- a/wise-webapp/src/main/java/com/wisemapping/rest/MindmapController.java +++ b/wise-webapp/src/main/java/com/wisemapping/rest/MindmapController.java @@ -19,9 +19,7 @@ package com.wisemapping.rest; -import com.wisemapping.exceptions.ImportUnexpectedException; -import com.wisemapping.exceptions.MindmapOutdatedException; -import com.wisemapping.exceptions.WiseMappingException; +import com.wisemapping.exceptions.*; import com.wisemapping.importer.ImportFormat; import com.wisemapping.importer.Importer; import com.wisemapping.importer.ImporterException; @@ -30,10 +28,10 @@ import com.wisemapping.model.*; import com.wisemapping.rest.model.*; import com.wisemapping.security.Utils; import com.wisemapping.service.CollaborationException; +import com.wisemapping.service.LockInfo; import com.wisemapping.service.LockManager; import com.wisemapping.service.MindmapService; import com.wisemapping.validator.MapInfoValidator; -import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -141,7 +139,7 @@ public class MindmapController extends BaseController { @RequestMapping(method = RequestMethod.PUT, value = "/maps/{id}/document", consumes = {"application/xml", "application/json"}, produces = {"application/json", "text/html", "application/xml"}) @ResponseBody - public long updateDocument(@RequestBody RestMindmap restMindmap, @PathVariable int id, @RequestParam(required = false) boolean minor, @RequestParam(required = false) Long timestamp) throws WiseMappingException, IOException { + public long updateDocument(@RequestBody RestMindmap restMindmap, @PathVariable int id, @RequestParam(required = false) boolean minor, @RequestParam(required = false) Long timestamp, @RequestParam(required = false) Long session) throws WiseMappingException, IOException { final Mindmap mindmap = mindmapService.findMindmapById(id); final User user = Utils.getUser(); @@ -152,10 +150,8 @@ public class MindmapController extends BaseController { throw new IllegalArgumentException("Map properties can not be null"); } - // Check that there we are not overwriting an already existing map ... - if (timestamp != null && mindmap.getLastModificationTime().getTimeInMillis() > timestamp) { - throw new MindmapOutdatedException("Mindmap timestamp out of sync. Client timestamp: " + timestamp + ", DB Timestamp:" + timestamp); - } + // Could the map be updated ? + checkUpdate(mindmap, user, session, timestamp); // Update collaboration properties ... final CollaborationProperties collaborationProperties = mindmap.findCollaborationProperties(user); @@ -172,8 +168,36 @@ public class MindmapController extends BaseController { logger.debug("Mindmap save completed:" + restMindmap.getXml()); saveMindmap(minor, mindmap, user); - // Return last update timestamp ... - return mindmap.getLastModificationTime().getTimeInMillis(); + // Update edition timeout ... + final LockManager lockManager = mindmapService.getLockManager(); + final LockInfo lockInfo = lockManager.updateExpirationTimeout(mindmap, user, session); + return lockInfo.getTimestamp(); + } + + private void checkUpdate(@NotNull Mindmap mindmap, @NotNull User user, long session, long timestamp) throws WiseMappingException { + // The lock was lost, reclaim as the ownership of it. + final LockManager lockManager = mindmapService.getLockManager(); + final boolean lockLost = lockManager.isLocked(mindmap); + if (!lockLost) { + lockManager.lock(mindmap, user, session); + } + + final LockInfo lockInfo = lockManager.getLockInfo(mindmap); + if (lockInfo.getCollaborator().equals(user)) { + final boolean outdated = mindmap.getLastModificationTime().getTimeInMillis() > timestamp; + if (lockInfo.getSession() == session) { + // Timestamp might not be returned to the client. This try to cover this case, ignoring the client timestamp check. + final User lastEditor = mindmap.getLastEditor(); + if (outdated && (lockInfo.getPreviousTimestamp() != timestamp || lastEditor == null || !lastEditor.equals(user))) { + throw new MultipleSessionsOpenException("The map has been updated and not by you. Session lost."); + } + } else if (outdated) { + throw new MultipleSessionsOpenException("The map has been updated and not by you. Session lost."); + } + } else { + throw new SessionExpiredException("You have lost the edition session", lockInfo.getCollaborator()); + + } } /** @@ -361,7 +385,13 @@ public class MindmapController extends BaseController { final User user = Utils.getUser(); final LockManager lockManager = mindmapService.getLockManager(); final Mindmap mindmap = mindmapService.findMindmapById(id); - lockManager.updateLock(Boolean.parseBoolean(value), mindmap, user); + + final boolean lock = Boolean.parseBoolean(value); + if (!lock) { + lockManager.unlock(mindmap, user); + } else { + throw new UnsupportedOperationException("REST lock must be implemented."); + } } @RequestMapping(method = RequestMethod.DELETE, value = "/maps/batch") diff --git a/wise-webapp/src/main/java/com/wisemapping/rest/model/RestMindmapLock.java b/wise-webapp/src/main/java/com/wisemapping/rest/model/RestLockInfo.java similarity index 71% rename from wise-webapp/src/main/java/com/wisemapping/rest/model/RestMindmapLock.java rename to wise-webapp/src/main/java/com/wisemapping/rest/model/RestLockInfo.java index 5377b215..0c0f21e4 100644 --- a/wise-webapp/src/main/java/com/wisemapping/rest/model/RestMindmapLock.java +++ b/wise-webapp/src/main/java/com/wisemapping/rest/model/RestLockInfo.java @@ -24,14 +24,23 @@ import java.util.Set; getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY) @JsonIgnoreProperties(ignoreUnknown = true) -public class RestMindmapLock { +public class RestLockInfo { @NotNull - private Collaborator user; - @Nullable - private LockInfo lockInfo; + final private Collaborator user; - public RestMindmapLock(@Nullable LockInfo lockInfo, @NotNull Collaborator collaborator) { + @Nullable + final private LockInfo lockInfo; + + // This is required only for compliance with the JAXB serializer. + public RestLockInfo(){ + + this.lockInfo = null; + //noinspection ConstantConditions + this.user = null; + } + + public RestLockInfo(@Nullable LockInfo lockInfo, @NotNull Collaborator collaborator) { this.lockInfo = lockInfo; this.user = collaborator; @@ -52,4 +61,13 @@ public class RestMindmapLock { public void setLockedByMe(boolean lockedForMe) { // Ignore ... } + + public long getTimestamp() { + return lockInfo != null ? lockInfo.getTimestamp() : -1; + } + + public void setTimestamp(long value) { + // + } + } diff --git a/wise-webapp/src/main/java/com/wisemapping/service/LockInfo.java b/wise-webapp/src/main/java/com/wisemapping/service/LockInfo.java index d45be415..56b0455a 100644 --- a/wise-webapp/src/main/java/com/wisemapping/service/LockInfo.java +++ b/wise-webapp/src/main/java/com/wisemapping/service/LockInfo.java @@ -19,6 +19,7 @@ package com.wisemapping.service; import com.wisemapping.model.Collaborator; +import com.wisemapping.model.Mindmap; import org.jetbrains.annotations.NotNull; import java.util.Calendar; @@ -26,11 +27,15 @@ import java.util.Calendar; public class LockInfo { final private Collaborator collaborator; private Calendar timeout; - private static int EXPIRATION_MIN = 25; + private long session; + private static int EXPIRATION_MIN = 30; + private long timestamp = -1; + private long previousTimestamp; - public LockInfo(@NotNull Collaborator collaborator) { + public LockInfo(@NotNull Collaborator collaborator, @NotNull Mindmap mindmap, long session) { this.collaborator = collaborator; this.updateTimeout(); + this.updateTimestamp(mindmap); } public Collaborator getCollaborator() { @@ -38,7 +43,7 @@ public class LockInfo { } public boolean isExpired() { - return timeout.before(Calendar.getInstance()); + return timeout.before(Calendar.getInstance()); } public void updateTimeout() { @@ -47,4 +52,25 @@ public class LockInfo { this.timeout = calendar; } + + public long getSession() { + return session; + } + + public void setSession(long session) { + this.session = session; + } + + public long getTimestamp() { + return timestamp; + } + + public long getPreviousTimestamp() { + return previousTimestamp; + } + + public void updateTimestamp(@NotNull Mindmap mindmap) { + this.previousTimestamp = this.timestamp; + this.timestamp = mindmap.getLastModificationTime().getTimeInMillis(); + } } diff --git a/wise-webapp/src/main/java/com/wisemapping/service/LockManager.java b/wise-webapp/src/main/java/com/wisemapping/service/LockManager.java index 9a71facc..8c8859ad 100644 --- a/wise-webapp/src/main/java/com/wisemapping/service/LockManager.java +++ b/wise-webapp/src/main/java/com/wisemapping/service/LockManager.java @@ -19,6 +19,7 @@ package com.wisemapping.service; import com.wisemapping.exceptions.AccessDeniedSecurityException; +import com.wisemapping.exceptions.ClientException; import com.wisemapping.exceptions.LockException; import com.wisemapping.exceptions.WiseMappingException; import com.wisemapping.model.Collaborator; @@ -31,13 +32,13 @@ public interface LockManager { LockInfo getLockInfo(@NotNull Mindmap mindmap); - void updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull Collaborator user); + LockInfo updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull Collaborator user,long session); void unlock(@NotNull Mindmap mindmap, @NotNull Collaborator user) throws LockException, AccessDeniedSecurityException; boolean isLockedBy(@NotNull Mindmap mindmap, @NotNull Collaborator collaborator); - void lock(@NotNull Mindmap mindmap, @NotNull Collaborator user) throws AccessDeniedSecurityException, LockException; + LockInfo lock(@NotNull Mindmap mindmap, @NotNull Collaborator user, long session) throws WiseMappingException; - void updateLock(boolean value, Mindmap mindmap, User user) throws WiseMappingException; + long generateSession(); } diff --git a/wise-webapp/src/main/java/com/wisemapping/service/LockManagerImpl.java b/wise-webapp/src/main/java/com/wisemapping/service/LockManagerImpl.java index 79ea189d..a9bcf4b5 100644 --- a/wise-webapp/src/main/java/com/wisemapping/service/LockManagerImpl.java +++ b/wise-webapp/src/main/java/com/wisemapping/service/LockManagerImpl.java @@ -24,7 +24,6 @@ import com.wisemapping.exceptions.WiseMappingException; import com.wisemapping.model.CollaborationRole; import com.wisemapping.model.Collaborator; import com.wisemapping.model.Mindmap; -import com.wisemapping.model.User; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; @@ -36,6 +35,15 @@ import java.util.concurrent.ConcurrentHashMap; * En caso que no sea posible grabar por que se perdio el lock, usar mensaje de error para explicar el por que... * Mensaje modal explicando que el mapa esta siendo editado, por eso no es posible edilarlo.... * Internacionalizacion de los mensaje ... +* Logout limpiar las sessiones ... +* +* Casos: +* - Usuario pierde el lock: +* - Y grabo con la misma sessions y el timestap ok. +* - Y grabo con la misma session y el timestap esta mal + * - Y grabo con distinta sessions + * - +* - Usuario pierde el lock, pero intenta grabar camio */ class LockManagerImpl implements LockManager { @@ -44,30 +52,6 @@ class LockManagerImpl implements LockManager { final static Timer expirationTimer = new Timer(); final private static Logger logger = Logger.getLogger("com.wisemapping.service.LockManager"); - public LockManagerImpl() { - lockInfoByMapId = new ConcurrentHashMap(); - expirationTimer.schedule(new TimerTask() { - @Override - public void run() { - - logger.debug("Lock expiration scheduler started. Current locks:" + lockInfoByMapId.keySet()); - - final List toRemove = new ArrayList(); - final Set mapIds = lockInfoByMapId.keySet(); - for (Integer mapId : mapIds) { - final LockInfo lockInfo = lockInfoByMapId.get(mapId); - if (lockInfo.isExpired()) { - toRemove.add(mapId); - } - } - - for (Integer mapId : toRemove) { - unlock(mapId); - } - } - }, ONE_MINUTE_MILLISECONDS, ONE_MINUTE_MILLISECONDS); - } - @Override public boolean isLocked(@NotNull Mindmap mindmap) { return this.getLockInfo(mindmap) != null; @@ -79,18 +63,21 @@ class LockManagerImpl implements LockManager { } @Override - public void updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull Collaborator user) { - if (this.isLocked(mindmap)) { - final LockInfo lockInfo = this.getLockInfo(mindmap); - if (!lockInfo.getCollaborator().equals(user)) { - throw new IllegalStateException("Could not update map lock timeout if you are not the locking user. User:" + lockInfo.getCollaborator() + ", " + user); - } - lockInfo.updateTimeout(); - logger.debug("Timeout updated for:" + mindmap.getId()); - - }else { + public LockInfo updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull Collaborator user, long session) { + if (!this.isLocked(mindmap)) { throw new IllegalStateException("Lock lost for map. No update possible."); } + + final LockInfo result = this.getLockInfo(mindmap); + if (!result.getCollaborator().equals(user)) { + throw new IllegalStateException("Could not update map lock timeout if you are not the locking user. User:" + result.getCollaborator() + ", " + user); + } + + result.updateTimeout(); + result.setSession(session); + result.updateTimestamp(mindmap); + logger.debug("Timeout updated for:" + mindmap.getId()); + return result; } @Override @@ -122,7 +109,8 @@ class LockManagerImpl implements LockManager { } @Override - public void lock(@NotNull Mindmap mindmap, @NotNull Collaborator user) throws AccessDeniedSecurityException, LockException { + @NotNull + public LockInfo lock(@NotNull Mindmap mindmap, @NotNull Collaborator user, long session) throws WiseMappingException { if (isLocked(mindmap) && !isLockedBy(mindmap, user)) { throw new LockException("Invalid lock, this should not happen"); } @@ -131,24 +119,46 @@ class LockManagerImpl implements LockManager { throw new AccessDeniedSecurityException("Invalid lock, this should not happen"); } - final LockInfo lockInfo = lockInfoByMapId.get(mindmap.getId()); - if (lockInfo != null) { + LockInfo result = lockInfoByMapId.get(mindmap.getId()); + if (result != null) { // Update timeout only... logger.debug("Update timestamp:" + mindmap.getId()); - updateExpirationTimeout(mindmap, user); + updateExpirationTimeout(mindmap, user, session); } else { logger.debug("Lock map id:" + mindmap.getId()); - lockInfoByMapId.put(mindmap.getId(), new LockInfo(user)); + result = new LockInfo(user, mindmap, session); + lockInfoByMapId.put(mindmap.getId(), result); } - + return result; } @Override - public void updateLock(boolean lock, @NotNull Mindmap mindmap, @NotNull User user) throws WiseMappingException { - if (lock) { - this.lock(mindmap, user); - } else { - this.unlock(mindmap, user); - } + public long generateSession() { + return System.nanoTime(); } + + public LockManagerImpl() { + lockInfoByMapId = new ConcurrentHashMap(); + expirationTimer.schedule(new TimerTask() { + @Override + public void run() { + + logger.debug("Lock expiration scheduler started. Current locks:" + lockInfoByMapId.keySet()); + + final List toRemove = new ArrayList(); + final Set mapIds = lockInfoByMapId.keySet(); + for (Integer mapId : mapIds) { + final LockInfo lockInfo = lockInfoByMapId.get(mapId); + if (lockInfo.isExpired()) { + toRemove.add(mapId); + } + } + + for (Integer mapId : toRemove) { + unlock(mapId); + } + } + }, ONE_MINUTE_MILLISECONDS, ONE_MINUTE_MILLISECONDS); + } + } diff --git a/wise-webapp/src/main/java/com/wisemapping/service/MindmapServiceImpl.java b/wise-webapp/src/main/java/com/wisemapping/service/MindmapServiceImpl.java index 941bff53..9a897712 100755 --- a/wise-webapp/src/main/java/com/wisemapping/service/MindmapServiceImpl.java +++ b/wise-webapp/src/main/java/com/wisemapping/service/MindmapServiceImpl.java @@ -100,10 +100,6 @@ public class MindmapServiceImpl throw new WiseMappingException("The tile can not be empty"); } - // Update edition timeout ... - final LockManager lockManager = this.getLockManager(); - lockManager.updateExpirationTimeout(mindMap, Utils.getUser()); - mindmapManager.updateMindmap(mindMap, saveHistory); } diff --git a/wise-webapp/src/main/webapp/WEB-INF/wisemapping-rest.xml b/wise-webapp/src/main/webapp/WEB-INF/wisemapping-rest.xml index f99c09ef..6fe8ad41 100644 --- a/wise-webapp/src/main/webapp/WEB-INF/wisemapping-rest.xml +++ b/wise-webapp/src/main/webapp/WEB-INF/wisemapping-rest.xml @@ -32,6 +32,7 @@ com.wisemapping.rest.model.RestCollaboration com.wisemapping.rest.model.RestCollaborationList com.wisemapping.rest.model.RestLogItem + com.wisemapping.rest.model.RestLockInfo diff --git a/wise-webapp/src/main/webapp/jsp/mindmapEditor.jsp b/wise-webapp/src/main/webapp/jsp/mindmapEditor.jsp index cf46b517..08490c72 100644 --- a/wise-webapp/src/main/webapp/jsp/mindmapEditor.jsp +++ b/wise-webapp/src/main/webapp/jsp/mindmapEditor.jsp @@ -34,8 +34,16 @@ // Configure designer options ... var options = loadDesignerOptions(); - - options.persistenceManager = new mindplot.RESTPersistenceManager("service/maps/{id}/document", "service/maps/{id}/history/latest","service/maps/{id}/lock"); + + options.persistenceManager = new mindplot.RESTPersistenceManager( + { + saveUrl:"service/maps/{id}/document", + revertUrl:"service/maps/{id}/history/latest", + lockUrl:"service/maps/{id}/lock", + timestamp: ${lockTimestamp}, + session: ${lockSession} + } + ); var userOptions = ${mindmap.properties}; options.zoom = userOptions.zoom;