Skip to content

Commit f7b91ca

Browse files
committed
LUT-25199 : Improved_management_of_concurrent_access_when_editing_a_page
1 parent 5bd1f8a commit f7b91ca

File tree

11 files changed

+225
-6
lines changed

11 files changed

+225
-6
lines changed

src/java/fr/paris/lutece/plugins/wiki/business/ITopicDAO.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import fr.paris.lutece.portal.service.plugin.Plugin;
3737

38+
import java.sql.Timestamp;
3839
import java.util.Collection;
3940

4041
/**
@@ -105,4 +106,13 @@ public interface ITopicDAO
105106
* @return The topic
106107
*/
107108
Topic load( String strTopicName, Plugin plugin );
109+
110+
/**
111+
* Update the name and the time of a user visiting modify page of a topic
112+
* @param nTopicId
113+
* @param strUserLogin
114+
* @param date
115+
* @param plugin
116+
*/
117+
void updateLastOpenModifyPage(int nTopicId, String strUserLogin, Timestamp date, Plugin plugin);
108118
}

src/java/fr/paris/lutece/plugins/wiki/business/Topic.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import fr.paris.lutece.portal.business.page.Page;
3737
import fr.paris.lutece.portal.service.resource.IExtendableResource;
38+
import java.sql.Timestamp;
3839

3940
/**
4041
* This is the business class for the object Topic
@@ -50,6 +51,8 @@ public class Topic implements IExtendableResource
5051
private String _strViewRole = Page.ROLE_NONE;
5152
private String _strEditRole = Page.ROLE_NONE;
5253
private String _strParentPageName;
54+
private String _strLastUserEditing;
55+
private Timestamp _dateLastEditAttempt;
5356

5457
/**
5558
* Returns the IdTopic
@@ -221,4 +224,46 @@ public void setParentPageName( String strParentPageName )
221224
{
222225
_strParentPageName = strParentPageName;
223226
}
227+
228+
/**
229+
* Returns the last user editing
230+
*
231+
* @return The last user editing
232+
*/
233+
public String getLastUserEditing ( )
234+
{
235+
return _strLastUserEditing;
236+
}
237+
/**
238+
* Sets the last user editing
239+
*
240+
* @param strLastUserEditing
241+
* The last user editing
242+
*/
243+
public void setLastUserEditing ( String strLastUserEditing )
244+
{
245+
_strLastUserEditing = strLastUserEditing;
246+
}
247+
248+
249+
/**
250+
* Returns the date of the last user editing
251+
*
252+
* @return The date of the last user editing
253+
*/
254+
public Timestamp getDateLastEditAttempt ( )
255+
{
256+
return _dateLastEditAttempt;
257+
}
258+
259+
/**
260+
* Sets the date of the last user editing
261+
*
262+
* @param dateLastEditAttempt
263+
* The date of the last user editing
264+
*/
265+
public void setDateLastEditAttempt ( Timestamp dateLastEditAttempt )
266+
{
267+
_dateLastEditAttempt = dateLastEditAttempt;
268+
}
224269
}

src/java/fr/paris/lutece/plugins/wiki/business/TopicDAO.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import fr.paris.lutece.portal.service.plugin.Plugin;
3737
import fr.paris.lutece.util.sql.DAOUtil;
3838

39+
import java.sql.Timestamp;
3940
import java.util.ArrayList;
4041
import java.util.Collection;
4142

@@ -47,11 +48,13 @@ public final class TopicDAO implements ITopicDAO
4748
// Constants
4849
private static final String SQL_QUERY_NEW_PK = "SELECT max( id_topic ) FROM wiki_topic";
4950
private static final String SQL_QUERY_SELECT = "SELECT id_topic, namespace, page_name, page_view_role, page_edit_role, parent_page_name FROM wiki_topic WHERE id_topic = ?";
50-
private static final String SQL_QUERY_INSERT = "INSERT INTO wiki_topic ( id_topic, namespace, page_name, page_view_role, page_edit_role, parent_page_name ) VALUES (?, ?, ?, ?, ?, ? ) ";
51+
private static final String SQL_QUERY_INSERT = "INSERT INTO wiki_topic ( id_topic, namespace, page_name, page_view_role, page_edit_role, parent_page_name, last_user_editing, last_edit_attempt_date ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? ) ";
5152
private static final String SQL_QUERY_DELETE = "DELETE FROM wiki_topic WHERE id_topic = ? ";
5253
private static final String SQL_QUERY_UPDATE = "UPDATE wiki_topic SET id_topic = ?, namespace = ?, page_name = ?, page_view_role = ?, page_edit_role = ?, parent_page_name = ? WHERE id_topic = ?";
53-
private static final String SQL_QUERY_SELECTALL = "SELECT id_topic, namespace, page_name, page_view_role, page_edit_role, parent_page_name FROM wiki_topic";
54-
private static final String SQL_QUERY_SELECT_BY_NAME = "SELECT id_topic, namespace, page_name, page_view_role, page_edit_role, parent_page_name FROM wiki_topic WHERE page_name = ?";
54+
private static final String SQL_QUERY_SELECTALL = "SELECT id_topic, namespace, page_name, page_view_role, page_edit_role, parent_page_name, last_user_editing, last_edit_attempt_date FROM wiki_topic";
55+
private static final String SQL_QUERY_SELECT_BY_NAME = "SELECT id_topic, namespace, page_name, page_view_role, page_edit_role, parent_page_name, last_user_editing, last_edit_attempt_date FROM wiki_topic WHERE page_name = ?";
56+
private static final String SQL_QUERY_UPDATE_LAST_OPEN_MODIFY_PAGE = "UPDATE wiki_topic SET last_user_editing = ?, last_edit_attempt_date=? WHERE id_topic = ?";
57+
/**
5558
/**
5659
* Generates a new primary key
5760
*
@@ -90,6 +93,8 @@ public void insert( Topic topic, Plugin plugin )
9093
daoUtil.setString( 4, topic.getViewRole( ) );
9194
daoUtil.setString( 5, topic.getEditRole( ) );
9295
daoUtil.setString( 6, topic.getParentPageName( ) );
96+
daoUtil.setString( 7, topic.getLastUserEditing( ) );
97+
daoUtil.setTimestamp( 8, topic.getDateLastEditAttempt( ) );
9398

9499
daoUtil.executeUpdate( );
95100
}
@@ -193,6 +198,21 @@ public Topic load( String strTopicName, Plugin plugin )
193198

194199
return topic;
195200
}
201+
202+
/**
203+
* {@inheritDoc }
204+
*/
205+
@Override
206+
public void updateLastOpenModifyPage(int nTopicId, String strUserLogin, Timestamp date, Plugin plugin ) {
207+
try (DAOUtil daoUtil = new DAOUtil(SQL_QUERY_UPDATE_LAST_OPEN_MODIFY_PAGE, plugin)) {
208+
daoUtil.setString(1, strUserLogin);
209+
daoUtil.setTimestamp(2, date);
210+
daoUtil.setInt(3, nTopicId);
211+
212+
daoUtil.executeUpdate();
213+
}
214+
}
215+
196216
/**
197217
* set the content of a topic version with doaUtil
198218
*/
@@ -204,6 +224,8 @@ public Topic setTopicWithDaoUtil(DAOUtil daoUtil) {
204224
topic.setViewRole( daoUtil.getString( 4 ) );
205225
topic.setEditRole( daoUtil.getString( 5 ) );
206226
topic.setParentPageName( daoUtil.getString( 6 ) );
227+
topic.setLastUserEditing( daoUtil.getString( 7 ) );
228+
topic.setDateLastEditAttempt( daoUtil.getTimestamp( 8 ) );
207229
return topic;
208230
}
209231
}

src/java/fr/paris/lutece/plugins/wiki/business/TopicHome.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
*/
3434
package fr.paris.lutece.plugins.wiki.business;
3535

36+
import fr.paris.lutece.api.user.User;
3637
import fr.paris.lutece.plugins.wiki.web.Constants;
3738
import fr.paris.lutece.portal.service.plugin.Plugin;
3839
import fr.paris.lutece.portal.service.plugin.PluginService;
3940
import fr.paris.lutece.portal.service.spring.SpringContextService;
4041

42+
import java.sql.Timestamp;
4143
import java.util.Collection;
4244

4345
/**
@@ -134,4 +136,16 @@ public static Collection<Topic> getTopicsList( )
134136
return _dao.selectTopicsList( _plugin );
135137
}
136138

139+
/**
140+
* Update the name and the time of a user visiting modify page of a topic
141+
* @param topicId
142+
* @param user
143+
*/
144+
public static void updateLastOpenModifyPage(int topicId, User user)
145+
{
146+
Timestamp date = new Timestamp(System.currentTimeMillis());
147+
String userName = user.getFirstName() + "_" + user.getLastName();
148+
_dao.updateLastOpenModifyPage(topicId, userName, date, _plugin);
149+
}
150+
137151
}

src/java/fr/paris/lutece/plugins/wiki/web/WikiApp.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import fr.paris.lutece.util.url.UrlItem;
8282

8383
import java.io.*;
84+
import java.sql.Timestamp;
8485
import java.util.*;
8586
import java.util.concurrent.ConcurrentHashMap;
8687
import javax.servlet.http.HttpServletRequest;
@@ -101,6 +102,8 @@ public class WikiApp extends MVCApplication
101102
private static final String TEMPLATE_LIST_WIKI = "skin/plugins/wiki/list_wiki.html";
102103
private static final String TEMPLATE_MAP_WIKI = "skin/plugins/wiki/map_wiki.html";
103104
private static final String TEMPLATE_SEARCH_WIKI = "skin/plugins/wiki/search_wiki.html";
105+
public final static String TEMPLATE_SOMEBODY_IS_EDITING = "skin/plugins/wiki/somebody_is_editing.html";
106+
104107
private static final String BEAN_SEARCH_ENGINE = "wiki.wikiSearchEngine";
105108

106109
private static final String PROPERTY_PAGE_PATH = "wiki.pagePathLabel";
@@ -145,6 +148,8 @@ public class WikiApp extends MVCApplication
145148
private static final String VIEW_SEARCH = "search";
146149
private static final String VIEW_DIFF = "diff";
147150
private static final String VIEW_LIST_IMAGES = "listImages";
151+
public final static String VIEW_SOMEBODY_IS_EDITING = "somebodyIsEditing";
152+
148153
private static final String ACTION_NEW_PAGE = "newPage";
149154
private static final String ACTION_DELETE_PAGE = "deletePage";
150155
private static final String ACTION_REMOVE_IMAGE = "removeImage";
@@ -427,9 +432,8 @@ public XPage doCreateTopic( HttpServletRequest request ) throws UserNotSignedExc
427432
@View( VIEW_MODIFY_PAGE )
428433
public XPage getModifyTopic( HttpServletRequest request ) throws SiteMessageException, UserNotSignedException
429434
{
430-
WikiAnonymousUser.checkUser( request );
435+
LuteceUser user = WikiAnonymousUser.checkUser( request );
431436
String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
432-
Integer nVersion = getVersionTopicVersionId( request );
433437
Topic topic;
434438
Topic topicSession = (Topic) request.getSession( ).getAttribute( MARK_TOPIC );
435439
if ( topicSession != null && topicSession.getPageName( ).equals( strPageName ) )
@@ -441,6 +445,29 @@ public XPage getModifyTopic( HttpServletRequest request ) throws SiteMessageExce
441445
{
442446
topic = getTopic( request, strPageName, MODE_EDIT );
443447
}
448+
// get last user present on modify page for this topic
449+
String lastUser = topic.getLastUserEditing( );
450+
Timestamp lastDate = topic.getDateLastEditAttempt( );
451+
// if it's been less than 17 seconds since the last user, we cannot edit
452+
if ( lastUser != null && lastDate != null && !lastUser.equals( user.getName( ) + "_" + user.getLastName( ) ) )
453+
{
454+
Timestamp now = new Timestamp( System.currentTimeMillis( ) );
455+
long diff = now.getTime( ) - lastDate.getTime( );
456+
if ( diff < 17000 )
457+
{
458+
Map<String, String> mapParameters = new ConcurrentHashMap<>( );
459+
mapParameters.put( Constants.PARAMETER_PAGE_NAME, strPageName );
460+
mapParameters.put( Constants.PARAMETER_USER_NAME, lastUser );
461+
return redirect( request, VIEW_SOMEBODY_IS_EDITING, mapParameters );
462+
}
463+
else
464+
{
465+
topic.setLastUserEditing( user.getName( ) + "_" + user.getLastName( ) );
466+
Timestamp date = new Timestamp( System.currentTimeMillis( ) );
467+
topic.setDateLastEditAttempt( date );
468+
TopicHome.updateLastOpenModifyPage( topic.getIdTopic( ), user );
469+
}
470+
}
444471
String strLocale = WikiLocaleService.getDefaultLanguage( );
445472
try {
446473
if( request.getParameter( Constants.PARAMETER_LOCAL ) != null )
@@ -622,6 +649,22 @@ public XPage getDiff( HttpServletRequest request ) throws SiteMessageException,
622649

623650
return page;
624651
}
652+
653+
@View( VIEW_SOMEBODY_IS_EDITING )
654+
public XPage getSomebodyIsEditing( HttpServletRequest request ) throws SiteMessageException, UserNotSignedException
655+
{
656+
LuteceUser user = WikiAnonymousUser.checkUser( request );
657+
String strPageName = request.getParameter( Constants.PARAMETER_PAGE_NAME );
658+
String strUsername = request.getParameter( Constants.PARAMETER_USER_NAME );
659+
Topic topic = getTopic( request, strPageName, MODE_EDIT );
660+
Map<String, Object> model = getModel( );
661+
model.put( Constants.PARAMETER_PAGE_NAME, strPageName );
662+
model.put( Constants.PARAMETER_USER_NAME, strUsername );
663+
XPage page = getXPage( TEMPLATE_SOMEBODY_IS_EDITING, request.getLocale( ), model );
664+
page.setTitle( getPageTitle( getTopicTitle( request, topic ) ) );
665+
page.setExtendedPathLabel( getPageExtendedPath( topic, request ) );
666+
return page;
667+
}
625668
/**
626669
* Deletes a wiki page
627670
*

src/java/fr/paris/lutece/plugins/wiki/web/WikiDynamicInputs.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636

3737
import com.fasterxml.jackson.databind.ObjectMapper;
38+
import fr.paris.lutece.api.user.User;
3839
import fr.paris.lutece.plugins.wiki.business.*;
3940
import fr.paris.lutece.plugins.wiki.service.ContentDeserializer;
4041
import fr.paris.lutece.plugins.wiki.service.RoleService;
@@ -142,4 +143,42 @@ public static HttpServletResponse modifyPage( HttpServletRequest request, HttpSe
142143

143144
return response;
144145
}
146+
/**
147+
* Update the name and the time of a user visiting modify page of a topic
148+
* @param request
149+
* @throws IOException
150+
* @throws UserNotSignedException
151+
* @throws fr.paris.lutece.portal.service.security.UserNotSignedException
152+
*/
153+
public static void updateLastModifyAttemptPage( HttpServletRequest request ) throws IOException, UserNotSignedException
154+
{
155+
StringBuilder sb = new StringBuilder();
156+
BufferedReader reader = request.getReader();
157+
String line;
158+
while ((line = reader.readLine()) != null) {
159+
sb.append(line);
160+
}
161+
String requestBody = sb.toString();
162+
requestBody = requestBody.substring(1);
163+
requestBody = requestBody.substring(0, requestBody.length() - 1);
164+
final int topicId = Integer.parseInt(requestBody);
165+
Topic topic = TopicHome.findByPrimaryKey( topicId );
166+
try
167+
{
168+
if ( RoleService.hasEditRole( request, topic ) )
169+
{
170+
User user = WikiAnonymousUser.checkUser( request );
171+
TopicHome.updateLastOpenModifyPage( topic.getIdTopic( ), user );
172+
}
173+
else
174+
{
175+
throw new UserNotSignedException( );
176+
}
177+
}
178+
catch( Exception e )
179+
{
180+
AppLogService.error( "Error saving last user editing topic page", e );
181+
182+
}
183+
}
145184
}

src/sql/plugins/wiki/plugin/create_db_wiki.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ page_name VARCHAR(100) DEFAULT '' NOT NULL,
1111
page_view_role VARCHAR(50) DEFAULT '' NOT NULL,
1212
page_edit_role VARCHAR(50) DEFAULT '' NOT NULL,
1313
parent_page_name VARCHAR(100) DEFAULT '' NOT NULL,
14+
last_user_editing VARCHAR(100) DEFAULT '' NOT NULL,
15+
last_edit_attempt_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
16+
1417
PRIMARY KEY (id_topic)
1518
);
1619

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
ALTER TABLE wiki_topic_version_content ADD html_wiki_content LONG VARCHAR NULL;
22

3+
ALTER TABLE wiki_topic ADD last_user_editing VARCHAR(100) DEFAULT '' NOT NULL;
4+
ALTER TABLE wiki_topic ADD last_edit_attempt_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<div class="row">
2+
<div class="col-sm-12">
3+
<div class="container panel panel-default">
4+
<h6 style="align-content: center">${user_name?replace("_", " ")} #i18n{wiki.somebody_is_editing}</h6>
5+
<button class="btn btn-default">
6+
<a href="jsp/site/Portal.jsp?page=wiki&view=page&page_name=${page_name}" title="#i18n{wiki.button.backToPage}">
7+
<span class="ti ti-chevron-left"></span> #i18n{wiki.button.backToPage}
8+
</a>
9+
</button>
10+
</div>
11+
12+
</div>
13+
</div>

webapp/js/plugins/wiki/wiki_pages/modify_page.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,29 @@ function callInputs(saveType) {
474474
}
475475
}
476476

477+
/*_________________________ REGISTER LAST USER ON THIS PAGE ___________________________*/
478+
function updateLastModifyAttemptPage(){
479+
const topic_id = document.getElementById("topic_id").value;
480+
481+
fetch('jsp/site/plugins/wiki/WikiDynamicInputs.jsp?actionName=updateLastModifyAttemptPage', {
482+
method: 'POST',
483+
headers: {
484+
'Accept': 'application/json',
485+
'Content-Type': 'application/json',
486+
credentials: "same-origin"
487+
},
488+
body: JSON.stringify(topic_id)
489+
});
490+
}
491+
// do when page is loaded
492+
document.onreadystatechange = function() {
493+
if (document.readyState === 'complete') {
494+
setInterval(function (){
495+
updateLastModifyAttemptPage();}
496+
, 10000);
497+
}
498+
}
499+
477500

478501
/*_________________________ ON LOAD REMOVE UNDERLINE ADDED BY THE HTML TO MARKDOWN CONVERTION ___________________________*/
479502
function removeUnderLineHeadings (underLineWithEqual){

0 commit comments

Comments
 (0)