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

when gui preference option is created more than once for specified user...

when gui preference option is created more than once for specified user unhandled exception was thrown
parent 6b955ef0
Pipeline #30952 passed with stage
in 12 minutes and 2 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