Skip to content

Commit 75feaf7

Browse files
committed
LUT-25199 : Improved management of concurrent access when editing a page
1 parent a5b50b9 commit 75feaf7

File tree

12 files changed

+235
-8
lines changed

12 files changed

+235
-8
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@
6262
<artifactId>flexmark-all</artifactId>
6363
<version>0.62.2</version>
6464
</dependency>
65+
<dependency>
66+
<groupId>fr.paris.lutece.plugins</groupId>
67+
<artifactId>module-mylutece-database</artifactId>
68+
<version>[6.0.0,)</version>
69+
<type>lutece-plugin</type>
70+
</dependency>
6571
</dependencies>
6672

6773
<properties>

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: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public class Topic implements IExtendableResource
5252
private String _strViewRole = Page.ROLE_NONE;
5353
private String _strEditRole = Page.ROLE_NONE;
5454
private String _strParentPageName;
55-
private String _strModifyPageOpenLastBy;
56-
private Timestamp _strModifyPageOpenAt;
55+
private String _strLastUserEditing;
56+
private Timestamp _dateLastEditAttempt;
5757

5858

5959
/**
@@ -228,4 +228,46 @@ public void setParentPageName( String strParentPageName )
228228
}
229229

230230

231+
/**
232+
* Returns the last user editing
233+
*
234+
* @return The last user editing
235+
*/
236+
public String getLastUserEditing ( )
237+
{
238+
return _strLastUserEditing;
239+
}
240+
/**
241+
* Sets the last user editing
242+
*
243+
* @param strLastUserEditing
244+
* The last user editing
245+
*/
246+
public void setLastUserEditing ( String strLastUserEditing )
247+
{
248+
_strLastUserEditing = strLastUserEditing;
249+
}
250+
251+
252+
/**
253+
* Returns the date of the last user editing
254+
*
255+
* @return The date of the last user editing
256+
*/
257+
public Timestamp getDateLastEditAttempt ( )
258+
{
259+
return _dateLastEditAttempt;
260+
}
261+
262+
/**
263+
* Sets the date of the last user editing
264+
*
265+
* @param dateLastEditAttempt
266+
* The date of the last user editing
267+
*/
268+
public void setDateLastEditAttempt ( Timestamp dateLastEditAttempt )
269+
{
270+
_dateLastEditAttempt = dateLastEditAttempt;
271+
}
272+
231273
}

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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535

3636

3737
import com.fasterxml.jackson.databind.ObjectMapper;
38+
import com.google.gson.Gson;
39+
import com.google.gson.GsonBuilder;
40+
import fr.paris.lutece.api.user.User;
3841
import fr.paris.lutece.plugins.wiki.business.*;
3942
import fr.paris.lutece.plugins.wiki.service.ContentDeserializer;
4043
import fr.paris.lutece.plugins.wiki.service.RoleService;
@@ -142,4 +145,42 @@ public static HttpServletResponse modifyPage( HttpServletRequest request, HttpSe
142145

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

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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<head>
2+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
3+
</head>
4+
<div class="row">
5+
<div class="col-sm-12">
6+
<div class="container panel panel-default">
7+
<h6 style="align-content: center">${user_name?replace("_", " ")} #i18n{wiki.somebody_is_editing}</h6>
8+
<button class="btn btn-default">
9+
<a href="jsp/site/Portal.jsp?page=wiki&view=page&page_name=${page_name}" title="#i18n{wiki.button.backToPage}">
10+
<span class="glyphicon glyphicon-chevron-left"></span> #i18n{wiki.button.backToPage}
11+
</a>
12+
</button>
13+
</div>
14+
15+
</div>
16+
</div>

0 commit comments

Comments
 (0)