Skip to content
Snippets Groups Projects
Commit 9b70fc6c authored by OZGCloud's avatar OZGCloud
Browse files

OZG-6992 implement unlock vorgang

parent 04590baa
Branches
Tags
No related merge requests found
Showing with 265 additions and 38 deletions
/*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.command;
public class VorgangUnlockedEvent extends CommandExecutedEvent {
public VorgangUnlockedEvent(Command command) {
super(command);
}
}
...@@ -41,6 +41,7 @@ public enum Order { ...@@ -41,6 +41,7 @@ public enum Order {
VORGANG_ZUM_LOESCHEN_MARKIEREN, VORGANG_ZUM_LOESCHEN_MARKIEREN,
VORGANG_LOESCHEN, VORGANG_LOESCHEN,
VORGANG_LOCK, VORGANG_LOCK,
UNLOCK_VORGANG,
ASSIGN_USER, ASSIGN_USER,
......
...@@ -26,7 +26,6 @@ package de.ozgcloud.vorgang.vorgang; ...@@ -26,7 +26,6 @@ package de.ozgcloud.vorgang.vorgang;
import java.util.Collections; import java.util.Collections;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -36,42 +35,45 @@ import de.ozgcloud.command.CommandCreatedEvent; ...@@ -36,42 +35,45 @@ import de.ozgcloud.command.CommandCreatedEvent;
import de.ozgcloud.command.CommandExecutedEvent; import de.ozgcloud.command.CommandExecutedEvent;
import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.command.CommandFailedEvent;
import de.ozgcloud.command.CommandRevokeFailedEvent; import de.ozgcloud.command.CommandRevokeFailedEvent;
import de.ozgcloud.command.CommandRevokedEvent;
import de.ozgcloud.command.RevokeCommandEvent; import de.ozgcloud.command.RevokeCommandEvent;
import de.ozgcloud.command.VorgangUnlockedEvent;
import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.command.CommandService;
import de.ozgcloud.vorgang.command.Order; import de.ozgcloud.vorgang.command.Order;
import de.ozgcloud.vorgang.files.FileService; import de.ozgcloud.vorgang.files.FileService;
import de.ozgcloud.vorgang.status.StatusService; import de.ozgcloud.vorgang.status.StatusService;
import de.ozgcloud.vorgang.vorgang.redirect.VorgangForwardFailedEvent; import de.ozgcloud.vorgang.vorgang.redirect.VorgangForwardFailedEvent;
import de.ozgcloud.vorgang.vorgang.redirect.VorgangRedirectedEvent; import de.ozgcloud.vorgang.vorgang.redirect.VorgangRedirectedEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
@Component @Component
@RequiredArgsConstructor
@Log4j2 @Log4j2
public class VorgangEventListener { public class VorgangEventListener {
private static final String IS_ASSIGN_COMMAND_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_ASSIGN_COMMAND.test(event.getSource())}"; private static final String IS_ASSIGN_COMMAND_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_ASSIGN_COMMAND.test(event.getSource())}";
private static final String IS_LOESCHEN_COMMAND_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_LOESCHEN_COMMAND.test(event.getSource())}";
private static final String IS_SET_AKTENZEICHEN_COMMAND_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_SET_AKTENZEICHEN_COMMAND.test(event.getSource())}";
public static final Predicate<Command> IS_ASSIGN_COMMAND = command -> Order.ASSIGN_USER.isMeant(command.getOrder()); public static final Predicate<Command> IS_ASSIGN_COMMAND = command -> Order.ASSIGN_USER.isMeant(command.getOrder());
private static final String IS_LOESCHEN_COMMAND_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_LOESCHEN_COMMAND.test(event.getSource())}";
public static final Predicate<Command> IS_LOESCHEN_COMMAND = command -> Order.VORGANG_LOESCHEN.isMeant(command.getOrder()); public static final Predicate<Command> IS_LOESCHEN_COMMAND = command -> Order.VORGANG_LOESCHEN.isMeant(command.getOrder());
private static final String IS_SET_AKTENZEICHEN_COMMAND_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_SET_AKTENZEICHEN_COMMAND.test(event.getSource())}";
public static final Predicate<Command> IS_SET_AKTENZEICHEN_COMMAND = command -> Order.SET_AKTENZEICHEN.isMeant(command.getOrder()); public static final Predicate<Command> IS_SET_AKTENZEICHEN_COMMAND = command -> Order.SET_AKTENZEICHEN.isMeant(command.getOrder());
private static final String IS_LOCK_VORGANG_COMMAND = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_LOCK_VORGANG_ORDER.test(event.getSource())}"; private static final String IS_LOCK_VORGANG_COMMAND = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_LOCK_VORGANG_ORDER.test(event.getSource())}";
public static final Predicate<Command> IS_LOCK_VORGANG_ORDER = command -> Order.VORGANG_LOCK.isMeant(command.getOrder()); public static final Predicate<Command> IS_LOCK_VORGANG_ORDER = command -> Order.VORGANG_LOCK.isMeant(command.getOrder());
@Autowired private static final String IS_UNLOCK_VORGANG_COMMAND_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.VorgangEventListener).IS_UNLOCK_VORGANG_COMMAND.test(event.getSource())}";
private StatusService statusService; public static final Predicate<Command> IS_UNLOCK_VORGANG_COMMAND = command -> Order.UNLOCK_VORGANG.isMeant(command.getOrder());
@Autowired
private VorgangService vorgangService;
@Autowired
private VorgangHeaderService vorgangHeaderService;
@Autowired
private FileService fileService;
@Autowired
private CommandService commandService;
@Autowired private final StatusService statusService;
private ApplicationEventPublisher publisher; private final VorgangService vorgangService;
private final VorgangHeaderService vorgangHeaderService;
private final FileService fileService;
private final CommandService commandService;
private final ApplicationEventPublisher publisher;
@EventListener @EventListener
public void updateStatus(VorgangRedirectedEvent event) { public void updateStatus(VorgangRedirectedEvent event) {
...@@ -132,6 +134,7 @@ public class VorgangEventListener { ...@@ -132,6 +134,7 @@ public class VorgangEventListener {
var command = event.getSource(); var command = event.getSource();
try { try {
vorgangService.unlockVorgang(command); vorgangService.unlockVorgang(command);
publisher.publishEvent(new CommandRevokedEvent(command));
} catch (RuntimeException e) { } catch (RuntimeException e) {
LOG.error("Error unlocking vorgang.", e); LOG.error("Error unlocking vorgang.", e);
publisher.publishEvent(new CommandRevokeFailedEvent(command.getId(), e.getMessage())); publisher.publishEvent(new CommandRevokeFailedEvent(command.getId(), e.getMessage()));
...@@ -151,4 +154,16 @@ public class VorgangEventListener { ...@@ -151,4 +154,16 @@ public class VorgangEventListener {
private String getAktenzeichen(String vorgangId) { private String getAktenzeichen(String vorgangId) {
return vorgangHeaderService.getById(vorgangId).getAktenzeichen(); return vorgangHeaderService.getById(vorgangId).getAktenzeichen();
} }
@EventListener(condition = IS_UNLOCK_VORGANG_COMMAND_CONDITION)
public void onUnlockVorgang(CommandCreatedEvent event) {
var command = event.getSource();
try {
vorgangService.unlockVorgang(command);
publisher.publishEvent(new VorgangUnlockedEvent(command));
} catch (RuntimeException e) {
LOG.error("Error unlocking vorgang.", e);
publisher.publishEvent(new CommandFailedEvent(command.getId(), e.getMessage()));
}
}
} }
\ No newline at end of file
...@@ -83,6 +83,13 @@ class VorgangRepository { ...@@ -83,6 +83,13 @@ class VorgangRepository {
updateFirst(vorgangId, whereIdAndVersion(vorgangId, version), new Update().set(Vorgang.FIELD_AKTENZEICHEN, akteneinsicht)); updateFirst(vorgangId, whereIdAndVersion(vorgangId, version), new Update().set(Vorgang.FIELD_AKTENZEICHEN, akteneinsicht));
} }
public void removeLock(String vorgangId, long version) {
var update = new Update().inc(Vorgang.MONGODB_FIELDNAME_VERSION, 1)
.unset(VorgangService.KEY_HEADER_LOCK);
updateFirst(vorgangId, whereIdAndVersion(vorgangId, version), update);
}
private void updateFirst(String vorgangId, Criteria queryObj, Update update) { private void updateFirst(String vorgangId, Criteria queryObj, Update update) {
var updateResult = mongoOperations.updateFirst(query(queryObj), update, Vorgang.class); var updateResult = mongoOperations.updateFirst(query(queryObj), update, Vorgang.class);
collisionVerifier.verify(updateResult, vorgangId); collisionVerifier.verify(updateResult, vorgangId);
......
...@@ -38,7 +38,6 @@ import jakarta.validation.Valid; ...@@ -38,7 +38,6 @@ import jakarta.validation.Valid;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -55,33 +54,28 @@ import de.ozgcloud.vorgang.clientattribute.ClientAttributesMap; ...@@ -55,33 +54,28 @@ import de.ozgcloud.vorgang.clientattribute.ClientAttributesMap;
import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; import de.ozgcloud.vorgang.common.errorhandling.NotFoundException;
import de.ozgcloud.vorgang.servicekonto.ServiceKonto; import de.ozgcloud.vorgang.servicekonto.ServiceKonto;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Validated @Validated
@Service @Service
@RequiredArgsConstructor
public class VorgangService { public class VorgangService {
private static final String BODY_ASSIGNED_TO_FIELD = "assignedTo"; private static final String BODY_ASSIGNED_TO_FIELD = "assignedTo";
public static final String BODY_OBJECT_AKTENZEICHEN = "aktenzeichen"; public static final String BODY_OBJECT_AKTENZEICHEN = "aktenzeichen";
static final String KEY_HEADER_LOCK = "%s.%s".formatted(Vorgang.MONGODB_FIELDNAME_HEADER, VorgangHead.FIELD_LOCK); static final String KEY_HEADER_LOCK = "%s.%s".formatted(Vorgang.MONGODB_FIELDNAME_HEADER, VorgangHead.FIELD_LOCK);
@Autowired private final VorgangAuthorizationService vorgangAuthenticationService;
private VorgangAuthorizationService vorgangAuthenticationService; private final VorgangRepository repository;
@Autowired
private VorgangRepository repository;
@Autowired private final ApplicationEventPublisher publisher;
private ApplicationEventPublisher publisher; private final AktenzeichenProvider aktenzeichenProvider;
@Autowired
private AktenzeichenProvider aktenzeichenProvider;
@Autowired private final ClientAttributeReadPermitted readIsPermitted;
private ClientAttributeReadPermitted readIsPermitted;
@Autowired private final LabelProcessor kopControlDataMapper;
private LabelProcessor kopControlDataMapper;
@Autowired private final VorgangStubMapper stubMapper;
private VorgangStubMapper stubMapper;
public Vorgang startCreation(@Valid Eingang eingang) { public Vorgang startCreation(@Valid Eingang eingang) {
var mappedEingang = kopControlDataMapper.moveLabelsToControlData(eingang); var mappedEingang = kopControlDataMapper.moveLabelsToControlData(eingang);
...@@ -257,8 +251,21 @@ public class VorgangService { ...@@ -257,8 +251,21 @@ public class VorgangService {
} }
public void unlockVorgang(Command command) { public void unlockVorgang(Command command) {
// TODO wird implementiert in OZG-6992 validateUnlockingClient(command);
// publisher.publishEvent(new CommandRevokedEvent(command)); repository.removeLock(command.getVorgangId(), command.getRelationVersion());
throw new UnsupportedOperationException("Not yet implemented"); }
void validateUnlockingClient(Command command) {
var vorgang = repository.findById(command.getVorgangId());
if (vorgang.isEmpty()) {
throw new NotFoundException(Vorgang.class, command.getVorgangId());
}
vorgang.map(Vorgang::getHeader).map(VorgangHead::getLock).map(Lock::getClientName)
.filter(lockingClient -> !lockingClient.equals(command.getCreatedByClient()))
.ifPresent(lockingClient -> {
throw new AccessDeniedException(
"Vorgang was locked by %s, but client name is %s!".formatted(lockingClient, command.getCreatedByClient()));
});
} }
} }
\ No newline at end of file
...@@ -44,10 +44,13 @@ import org.springframework.context.ApplicationEventPublisher; ...@@ -44,10 +44,13 @@ import org.springframework.context.ApplicationEventPublisher;
import com.thedeanda.lorem.LoremIpsum; import com.thedeanda.lorem.LoremIpsum;
import de.ozgcloud.command.Command; import de.ozgcloud.command.Command;
import de.ozgcloud.command.CommandCreatedEvent;
import de.ozgcloud.command.CommandExecutedEvent; import de.ozgcloud.command.CommandExecutedEvent;
import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.command.CommandFailedEvent;
import de.ozgcloud.command.CommandRevokeFailedEvent; import de.ozgcloud.command.CommandRevokeFailedEvent;
import de.ozgcloud.command.CommandRevokedEvent;
import de.ozgcloud.command.RevokeCommandEvent; import de.ozgcloud.command.RevokeCommandEvent;
import de.ozgcloud.command.VorgangUnlockedEvent;
import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.common.errorhandling.TechnicalException;
import de.ozgcloud.vorgang.command.CommandCreatedEventTestFactory; import de.ozgcloud.vorgang.command.CommandCreatedEventTestFactory;
import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.command.CommandService;
...@@ -269,19 +272,25 @@ class VorgangEventListenerTest { ...@@ -269,19 +272,25 @@ class VorgangEventListenerTest {
@Captor @Captor
private ArgumentCaptor<CommandRevokeFailedEvent> eventCaptor; private ArgumentCaptor<CommandRevokeFailedEvent> eventCaptor;
private Command command = CommandTestFactory.create();
@Test @Test
void shouldCallUnlockVorgang() { void shouldCallUnlockVorgang() {
var command = CommandTestFactory.create();
listener.onRevokeLockVorgang(new RevokeCommandEvent(command)); listener.onRevokeLockVorgang(new RevokeCommandEvent(command));
verify(service).unlockVorgang(command); verify(service).unlockVorgang(command);
} }
@Test
void shouldPublishCommandRevokedEvent() {
listener.onRevokeLockVorgang(new RevokeCommandEvent(command));
verify(publisher).publishEvent(argThat(event -> event instanceof CommandRevokedEvent && event.getSource().equals(command)));
}
@Test @Test
void shouldPublishCommandFailedEvent() { void shouldPublishCommandFailedEvent() {
var command = CommandTestFactory.create();
var errorMessage = "error message"; var errorMessage = "error message";
doThrow(new RuntimeException(errorMessage)).when(service).unlockVorgang(command); doThrow(new RuntimeException(errorMessage)).when(service).unlockVorgang(command);
...@@ -294,4 +303,41 @@ class VorgangEventListenerTest { ...@@ -294,4 +303,41 @@ class VorgangEventListenerTest {
}); });
} }
} }
@Nested
class TestOnUnlockVorgang {
private Command command = CommandTestFactory.create();
@Test
void shouldUnlockVorgangOnUnlockVorgangOrder() {
onUnlockVorgang();
verify(service).unlockVorgang(command);
}
@Test
void shouldPublishVorgangUnlockedEvent() {
onUnlockVorgang();
verify(publisher)
.publishEvent(
argThat(event -> event instanceof VorgangUnlockedEvent && ((CommandExecutedEvent) event).getCommand().equals(command)));
}
@Test
void shouldPublishCommandFailedEvent() {
var errorMessage = "error message";
doThrow(new RuntimeException(errorMessage)).when(service).unlockVorgang(command);
onUnlockVorgang();
verify(publisher).publishEvent(argThat(event -> event.getSource().equals(CommandTestFactory.ID)
&& ((CommandFailedEvent) event).getErrorMessage().equals(errorMessage)));
}
private void onUnlockVorgang() {
listener.onUnlockVorgang(new CommandCreatedEvent(command));
}
}
} }
\ No newline at end of file
...@@ -360,4 +360,46 @@ class VorgangRepositoryITCase { ...@@ -360,4 +360,46 @@ class VorgangRepositoryITCase {
return mongoOperations.findById(VorgangTestFactory.ID, Vorgang.class); return mongoOperations.findById(VorgangTestFactory.ID, Vorgang.class);
} }
} }
@Nested
class TestRemoveLock {
private final Vorgang vorgang = VorgangTestFactory.create();
private Vorgang persistedVorgang;
@BeforeEach
void persistVorgang() {
persistedVorgang = repository.save(vorgang);
}
@Test
void shouldRemoveLock() {
removeLock();
assertThat(persistedVorgang.getHeader().getLock()).isNotNull();
assertThat(getVorgang().getHeader().getLock()).isNull();
}
@Test
void shouldIncrementVersion() {
removeLock();
assertThat(getVorgang().getVersion()).isEqualTo(VorgangTestFactory.VERSION + 1);
}
private void removeLock() {
repository.removeLock(VorgangTestFactory.ID, VorgangTestFactory.VERSION);
}
@Test
void shouldThrowExceptionByCollision() {
assertThrows(ConcurrentModificationException.class,
() -> repository.removeLock(VorgangTestFactory.ID, VorgangTestFactory.VERSION + 1));
}
private Vorgang getVorgang() {
return mongoOperations.findById(VorgangTestFactory.ID, Vorgang.class);
}
}
} }
\ No newline at end of file
...@@ -616,4 +616,82 @@ class VorgangServiceTest { ...@@ -616,4 +616,82 @@ class VorgangServiceTest {
assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP).containsEntry(Lock.FIELD_REASON, LockTestFactory.REASON); assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP).containsEntry(Lock.FIELD_REASON, LockTestFactory.REASON);
} }
} }
@Nested
class TestUnlockVorgang {
private final Command command = CommandTestFactory.create();
@BeforeEach
void mock() {
doNothing().when(service).validateUnlockingClient(any());
}
@Test
void shouldCallValidateUnlockingClient() {
unlockVorgang();
verify(service).validateUnlockingClient(command);
}
@Test
void shouldCallRepository() {
unlockVorgang();
verify(repository).removeLock(VorgangTestFactory.ID, CommandTestFactory.RELATION_VERSION);
}
private void unlockVorgang() {
service.unlockVorgang(command);
}
}
@Nested
class TestValidateUnlockingClient {
@Test
void shouldGetVorgangFromRepository() {
var command = CommandTestFactory.create();
try {
service.validateUnlockingClient(command);
} catch (Exception e) {
}
verify(repository).findById(VorgangTestFactory.ID);
}
@Test
void shouldThrowAccessDeniedExceptionOnNotMatchingClient() {
when(repository.findById(VorgangTestFactory.ID)).thenReturn(Optional.of(VorgangTestFactory.create()));
var command = CommandTestFactory.createBuilder().createdByClient("wrong client").build();
assertThrows(AccessDeniedException.class, () -> service.validateUnlockingClient(command));
}
@Test
void shouldThrowNotFoundException() {
var command = CommandTestFactory.create();
when(repository.findById(VorgangTestFactory.ID)).thenReturn(Optional.empty());
assertThrows(NotFoundException.class, () -> service.validateUnlockingClient(command));
}
@Test
void shouldNotThrowExceptionOnMatchingClient() {
when(repository.findById(VorgangTestFactory.ID)).thenReturn(Optional.of(VorgangTestFactory.create()));
var command = CommandTestFactory.createBuilder().createdByClient(LockTestFactory.CLIENT_NAME).build();
assertDoesNotThrow(() -> service.validateUnlockingClient(command));
}
@Test
void shouldNotThrowExceptionOnVorgangNotLocked() {
when(repository.findById(VorgangTestFactory.ID)).thenReturn(
Optional.of(VorgangTestFactory.createBuilder().header(VorgangHeadTestFactory.createBuilder().lock(null).build()).build()));
var command = CommandTestFactory.createBuilder().createdByClient(LockTestFactory.CLIENT_NAME).build();
assertDoesNotThrow(() -> service.validateUnlockingClient(command));
}
}
} }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment