Commit b347fdd6 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch 'conflict-in-update-preferences' into 'devel_15.0.x'

Conflict in update preferences

See merge request !1228
parents e1e83c29 d8c5562b
Pipeline #30953 passed with stage
in 14 minutes and 41 seconds
......@@ -3,6 +3,8 @@ minerva (15.0.3) stable; urgency=medium
issues (#1315)
* Bug fix: when there is a problem with minerva deployment on tomcat proper
warning message is presented in admin panel (#1332)
* Bug fix: API call updating user preferences did not handle properly
conflicts on new gui properties (#1326)
-- Piotr Gawron <piotr.gawron@uni.lu> Wed, 8 Jul 2020 16:00:00 +0200
......
......@@ -39,6 +39,8 @@ public abstract class BaseController {
return createErrorResponse("Object not found.", e.getMessage(), new HttpHeaders(), HttpStatus.NOT_FOUND);
} else if (e instanceof ObjectExistsException) {
return createErrorResponse("Object already exists.", e.getMessage(), new HttpHeaders(), HttpStatus.CONFLICT);
} else if (e instanceof UpdateConflictException) {
return createErrorResponse("There is a conflict with another query. Try again later.", e.getMessage(), new HttpHeaders(), HttpStatus.CONFLICT);
} else if (e instanceof OperationNotAllowedException) {
return createErrorResponse("Operation not allowed.", e.getMessage(), new HttpHeaders(),
HttpStatus.METHOD_NOT_ALLOWED);
......
package lcsb.mapviewer.api;
/**
* Thrown when object cannot be updated over API due to conflicting queries.
*
* @author Piotr Gawron
*
*/
public class UpdateConflictException extends QueryException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*
* @param message
* error message
*/
public UpdateConflictException(String message) {
super(message);
}
/**
* Constructor with error message and parent exception.
*
* @param message
* error message
* @param reason
* parent exception that caused this one
*/
public UpdateConflictException(String message, Exception reason) {
super(message, reason);
}
}
......@@ -5,6 +5,7 @@ import java.util.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.GrantedAuthority;
......@@ -25,7 +26,7 @@ import lcsb.mapviewer.model.user.*;
import lcsb.mapviewer.model.user.annotator.*;
import lcsb.mapviewer.services.interfaces.ILayoutService;
@Transactional
@Transactional(rollbackFor = UpdateConflictException.class)
@Service
public class UserRestImpl extends BaseRestImpl {
......@@ -606,6 +607,13 @@ public class UserRestImpl extends BaseRestImpl {
return getUser(login, "preferences");
} catch (IllegalArgumentException e) {
throw new QueryException("Invalid input", e);
} catch (Exception e) {
if (e.getCause() instanceof ConstraintViolationException) {
if (e.getCause().getCause().getMessage().contains("duplicate key value violates unique constraint")) {
throw new UpdateConflictException("Conflict when updating preferences.", e);
}
}
throw e;
}
}
......
......@@ -93,7 +93,7 @@ abstract public class ControllerIntegrationTest {
private LayoutDao layoutDao;
@Autowired
private DbUtils dbUtils;
protected DbUtils dbUtils;
private MinervaLoggerAppender appender;
private ExecutorService executorService;
......
package lcsb.mapviewer.web;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Arrays;
import java.util.*;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
......@@ -20,6 +23,8 @@ import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.RequestBuilder;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.model.user.UserGuiPreference;
import lcsb.mapviewer.services.interfaces.IUserService;
@RunWith(SpringJUnit4ClassRunner.class)
......@@ -28,8 +33,6 @@ public class UserControllerIntegrationTestWithoutTransaction extends ControllerI
private static final String TEST_USER_PASSWORD = "test_pass";
private static final String TEST_USER_LOGIN = "test_user";
private static final String ADMIN_PASSWORD = "admin";
private static final String ADMIN_LOGIN = "admin";
Logger logger = LogManager.getLogger();
@Autowired
private IUserService userService;
......@@ -41,7 +44,7 @@ public class UserControllerIntegrationTestWithoutTransaction extends ControllerI
@Test
public void createUser() throws Exception {
try {
MockHttpSession session = createSession(ADMIN_LOGIN, ADMIN_PASSWORD);
MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
String body = EntityUtils.toString(new UrlEncodedFormEntity(Arrays.asList(
new BasicNameValuePair("login", TEST_USER_LOGIN),
......@@ -60,4 +63,66 @@ public class UserControllerIntegrationTestWithoutTransaction extends ControllerI
removeUserInSeparateThread(userService.getUserByLogin(TEST_USER_LOGIN));
}
}
@Test
public void createGuiPreferencesTwice() throws Exception {
MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
try {
createUserInSeparateThread(TEST_USER_LOGIN, TEST_USER_PASSWORD);
callInSeparateThread(()->{
RequestBuilder request = patch("/users/" + TEST_USER_LOGIN + ":updatePreferences")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"preferences\":{}}")
.session(session);
try {
mockMvc.perform(request)
.andExpect(status().is2xxSuccessful());
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
MutableBoolean exceptionHappened = new MutableBoolean(false);
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
String body = "{\"preferences\":{\"gui-preferences\":{\"test\":\"val"+i+"\"}}}";
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dbUtils.createSessionForCurrentThread();
try {
RequestBuilder request = patch("/users/" + TEST_USER_LOGIN + ":updatePreferences")
.contentType(MediaType.APPLICATION_JSON)
.content(body)
.session(session);
int status = mockMvc.perform(request).andReturn().getResponse().getStatus();
assertNotEquals(500, status);
} catch (Exception e) {
e.printStackTrace();
exceptionHappened.setTrue();
} finally {
dbUtils.closeSessionForCurrentThread();
}
}
});
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
assertFalse(exceptionHappened.getValue());
} finally {
removeUserInSeparateThread(userService.getUserByLogin(TEST_USER_LOGIN));
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment