Skip to content
Snippets Groups Projects
Commit 35ff1b0f authored by OZGCloud's avatar OZGCloud
Browse files

OZG-6811 remove bescheid-manager except for bescheid grpc service and add document service

parent fc369fe9
Branches
Tags
No related merge requests found
Showing
with 29 additions and 1058 deletions
......@@ -17,3 +17,4 @@ target/
.idea
*.iml
*.orig
.vscode/
......@@ -17,12 +17,18 @@
<properties>
<vorgang-manager.version>2.13.0</vorgang-manager.version>
<nachrichten-manager.version>2.9.0</nachrichten-manager.version>
<document-manager.version>1.0.0-PR-1-SNAPSHOT</document-manager.version>
<api-lib.version>0.12.0</api-lib.version>
<spring-cloud-config-client.version>4.1.3</spring-cloud-config-client.version>
</properties>
<dependencies>
<!-- ozg-cloud -->
<dependency>
<groupId>de.ozgcloud.document</groupId>
<artifactId>document-manager-server</artifactId>
<version>${document-manager.version}</version>
</dependency>
<dependency>
<groupId>de.ozgcloud.vorgang</groupId>
<artifactId>vorgang-manager-interface</artifactId>
......@@ -109,7 +115,8 @@
</dependency>
<!--dev tools-->
<!--dev
tools-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
......
package de.ozgcloud.bescheid;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import de.ozgcloud.bescheid.vorgang.VorgangId;
import de.ozgcloud.common.binaryfile.FileId;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
@Builder(toBuilder = true)
@Getter
public class Bescheid {
public static final String FIELD_VORGANG_ID = "vorgangId";
public static final String FIELD_CREATED_BY = "createdBy";
public static final String FIELD_BEWILLIGT = "bewilligt";
public static final String FIELD_STATUS = "status";
public static final String FIELD_BESCHEID_DOCUMENT = "bescheidDocument";
public static final String FIELD_BESCHIEDEN_AM = "beschiedenAm";
public static final String FIELD_SEND_BY = "sendBy";
public static final String FIELD_NACHRICHT_TEXT = "nachrichtText";
public static final String FIELD_NACHRICHT_SUBJECT = "nachrichtSubject";
public static final String FIELD_ATTACHMENTS = "attachments";
public static final String FIELD_SENT_INFO = "sentInfo";
private BescheidId id;
private long version;
private VorgangId vorgangId;
private UserId createdBy;
private boolean bewilligt;
private Status status;
private FileId bescheidDocument;
private LocalDate beschiedenAm;
private SendBy sendBy;
private SentInfo sentInfo;
@Builder.Default
private Optional<String> nachrichtSubject = Optional.empty();
@Builder.Default
private Optional<String> nachrichtText = Optional.empty();
@Singular
private List<FileId> attachments;
public enum Status {
DRAFT, SENT;
public boolean not(Object value) {
return !hasValue(value);
}
public boolean hasValue(Object value) {
return this.name().equalsIgnoreCase(String.valueOf(value));
}
}
public enum SendBy {
NACHRICHT, MANUAL;
public boolean notValue(Object sendByValue) {
return !hasValue(sendByValue);
}
public boolean hasValue(Object sendByValue) {
return this.name().equalsIgnoreCase(String.valueOf(sendByValue));
}
}
}
\ No newline at end of file
package de.ozgcloud.bescheid;
import static de.ozgcloud.common.grpc.GrpcUtil.*;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import de.ozgcloud.bescheid.common.callcontext.CurrentUserService;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
@Component("bescheidCallContextInterceptor")
public class BescheidCallContextAttachingInterceptor implements ClientInterceptor {
public static final String BESCHEID_MANAGER_CLIENT_NAME = "OzgCloud_BescheidManager";
@Autowired
private CurrentUserService userService;
// <A> = Request, <B> = Response
@Override
public <A, B> ClientCall<A, B> interceptCall(MethodDescriptor<A, B> method, CallOptions callOptions, Channel next) {
return new CallContextAttachingClientCall<>(next.newCall(method, callOptions));
}
final class CallContextAttachingClientCall<A, B> extends SimpleForwardingClientCall<A, B> {
protected CallContextAttachingClientCall(ClientCall<A, B> delegate) {
super(delegate);
}
@Override
public void start(Listener<B> responseListener, Metadata headers) {
headers.merge(buildCallContextMetadata());
super.start(responseListener, headers);
}
private Metadata buildCallContextMetadata() {
var metadata = new Metadata();
userService.getUser().getUserId().ifPresent(userId -> metadata.put(HEADER_KEY_USER_ID, userId.getBytes()));
metadata.put(HEADER_KEY_CLIENT_NAME, BESCHEID_MANAGER_CLIENT_NAME.getBytes());
metadata.put(HEADER_KEY_REQUEST_ID, generateRequestId().getBytes());
return metadata;
}
// TODO requestId für command handling uebertragen / erstellen
private String generateRequestId() {
return UUID.randomUUID().toString();
}
}
}
package de.ozgcloud.bescheid;
import de.ozgcloud.command.Command;
import de.ozgcloud.command.CommandExecutedEvent;
class BescheidCreatedEvent extends CommandExecutedEvent {
public BescheidCreatedEvent(Command command) {
super(command.getId());
}
public BescheidCreatedEvent(Command command, String createdItemId) {
super(command, createdItemId);
}
}
/*
* 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.bescheid;
import de.ozgcloud.command.Command;
import de.ozgcloud.command.CommandExecutedEvent;
class BescheidDeletedEvent extends CommandExecutedEvent {
public BescheidDeletedEvent(Command command) {
super(command);
}
}
/*
* 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.bescheid;
import static java.util.Objects.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Component;
import de.ozgcloud.bescheid.Bescheid.SendBy;
import de.ozgcloud.bescheid.binaryfile.BinaryFileService;
import de.ozgcloud.bescheid.common.callcontext.CurrentUserService;
import de.ozgcloud.bescheid.common.user.UserProfileService;
import de.ozgcloud.bescheid.vorgang.VorgangId;
import de.ozgcloud.command.Command;
import de.ozgcloud.command.CommandCreatedEvent;
import de.ozgcloud.command.CommandExecutedEvent;
import de.ozgcloud.command.CommandFailedEvent;
import de.ozgcloud.common.attached_item.AttachedItemService;
import de.ozgcloud.common.errorhandling.TechnicalException;
import de.ozgcloud.document.BescheidDocumentCreatedEvent;
import de.ozgcloud.document.Document;
import de.ozgcloud.document.DocumentService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@Component
@RequiredArgsConstructor
class BescheidEventListener {
public static final String CREATE_BESCHEID_ORDER = "CREATE_BESCHEID";
public static final String DELETE_BESCHEID_ORDER = "DELETE_BESCHEID";
public static final String UPDATE_BESCHEID_ORDER = "UPDATE_BESCHEID";
public static final String CREATE_BESCHEID_DOCUMENT_ORDER = "CREATE_BESCHEID_DOCUMENT";
public static final String SEND_BESCHEID_ORDER = "SEND_BESCHEID";
public static final Predicate<Command> IS_CREATE_BESCHEID_COMMAND = command -> CREATE_BESCHEID_ORDER.equals(command.getOrder());
private static final String IS_CREATE_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_CREATE_BESCHEID_COMMAND.test(event.getSource())}";
public static final Predicate<Command> IS_DELETE_BESCHEID_COMMAND = command -> DELETE_BESCHEID_ORDER.equals(command.getOrder());
private static final String IS_DELETE_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_DELETE_BESCHEID_COMMAND.test(event.getSource())}";
public static final Predicate<Command> IS_UPDATE_BESCHEID_COMMAND = command -> UPDATE_BESCHEID_ORDER.equals(command.getOrder());
private static final String IS_UPDATE_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_UPDATE_BESCHEID_COMMAND.test(event.getSource())}";
public static final Predicate<Command> IS_CREATE_BESCHEID_DOCUMENT_COMMAND = command -> CREATE_BESCHEID_DOCUMENT_ORDER.equals(command.getOrder());
private static final String IS_CREATE_BESCHEID_DOCUMENT = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_CREATE_BESCHEID_DOCUMENT_COMMAND.test(event.getSource())}";
public static final Predicate<Command> IS_SEND_BESCHEID_COMMAND = command -> nonNull(command) && SEND_BESCHEID_ORDER.equals(command.getOrder());
private static final String IS_SEND_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_SEND_BESCHEID_COMMAND.test(event.getSource())}";
private static final String IS_SEND_BESCHEID_ORDER = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_SEND_BESCHEID_COMMAND.test(event.getCommand())}";
static final String VORGANG_ID_BODY_KEY = "vorgangId";
static final String BESCHEID_VOM_BODY_KEY = "bescheidVom";
static final String GENEHMIGT_BODY_KEY = "genehmigt";
private static final String LOG_MESSAGE_TEMPLATE = "{}. Command failed.";
private static final String ERROR_MESSAGE_TEMPLATE = "Error on executing %s Command.";
private final BescheidService service;
private final BinaryFileService fileService;
private final AttachedItemService attachedItemService;
private final DocumentService documentService;
private final ApplicationEventPublisher eventPublisher;
private final CurrentUserService userService;
private final UserProfileService userProfileService;
@EventListener(condition = IS_CREATE_BESCHEID)
public void onCreateBescheidCommand(CommandCreatedEvent event) {
runWithSecurityContext(event.getSource(), this::doCreateBescheid);
}
void doCreateBescheid(Command command) {
var createdItemId = service.createBescheidDraft(command);
eventPublisher.publishEvent(new BescheidCreatedEvent(command, createdItemId));
}
@EventListener(condition = IS_DELETE_BESCHEID)
public void onDeleteBescheid(CommandCreatedEvent event) {
runWithSecurityContext(event.getSource(), this::doDeleteBescheid);
}
void doDeleteBescheid(Command command) {
attachedItemService.deleteBescheidDraft(command);
eventPublisher.publishEvent(new BescheidDeletedEvent(command));
}
@EventListener(condition = IS_UPDATE_BESCHEID)
public void onUpdateBescheidCommand(CommandCreatedEvent event) {
runWithSecurityContext(event.getSource(), this::doUpdateBescheid);
}
void doUpdateBescheid(Command command) {
attachedItemService.updateBescheidDraft(command, getDocument(command));
eventPublisher.publishEvent(new BescheidUpdatedEvent(command));
}
Optional<Document> getDocument(Command command) {
return Optional.ofNullable(MapUtils.getString(command.getBodyObject(), Bescheid.FIELD_BESCHEID_DOCUMENT))
.map(StringUtils::trimToNull)
.map(documentService::getDocument);
}
@EventListener(condition = IS_CREATE_BESCHEID_DOCUMENT)
public void onCreatedBescheidDocument(CommandCreatedEvent event) {
runWithSecurityContext(event.getSource(), this::doCreateBescheidDocument);
}
void doCreateBescheidDocument(Command command) {
var bescheid = doCreateBescheidBiz(command);
var bescheidDocument = documentService.createBescheidDocument(command, bescheid);
eventPublisher.publishEvent(new BescheidDocumentCreatedEvent(command, bescheidDocument));
}
BescheidResponse doCreateBescheidBiz(@NonNull Command command) {
var bescheid = service.createBescheid(createRequest(command));
return fileService.uploadBescheidFile(bescheid);
}
@EventListener(condition = IS_SEND_BESCHEID)
public void onSendBescheidCommand(CommandCreatedEvent event) {
runWithSecurityContext(event.getSource(), this::doSendBescheid);
}
void doSendBescheid(Command command) {
var bescheidItem = attachedItemService.getItem(command.getRelationId());
if (SendBy.MANUAL.hasValue(bescheidItem.getItem().get(Bescheid.FIELD_SEND_BY))) {
service.sendBescheidManually(bescheidItem, command);
} else if (SendBy.NACHRICHT.hasValue(bescheidItem.getItem().get(Bescheid.FIELD_SEND_BY))) {
service.sendBescheidPostfachMail(bescheidItem, command);
} else {
throw new TechnicalException("Bescheid has unexpected sendBy value: '%s'. Allowed values are %s."
.formatted(bescheidItem.getItem().get(Bescheid.FIELD_SEND_BY),
Arrays.stream(SendBy.values()).map(SendBy::name).collect(Collectors.joining(","))));
}
eventPublisher.publishEvent(new BescheidSentEvent(command));
}
BescheidRequest createRequest(Command command) {
var eventBody = command.getBodyObject();
var builder = BescheidRequest.builder();
builder.vorgangId(VorgangId.from(command.getVorgangId()));
Optional.ofNullable(MapUtils.getString(eventBody, BESCHEID_VOM_BODY_KEY))
.map(LocalDate::parse).ifPresent(builder::bescheidVom);
Optional.ofNullable(MapUtils.getBoolean(eventBody, GENEHMIGT_BODY_KEY))
.ifPresentOrElse(builder::genehmigt, () -> builder.genehmigt(true));
builder.createFor(userProfileService.getUserProfile());
return builder.build();
}
@EventListener(condition = IS_SEND_BESCHEID_ORDER)
public void onBescheidSent(CommandExecutedEvent event) {
runWithSecurityContext(event.getCommand(), this::setAntragBewilligung);
}
void setAntragBewilligung(Command command) {
service.setAntragBewilligung(attachedItemService.getItem(command.getRelationId()));
}
void runWithSecurityContext(Command command, Consumer<Command> commandExecutor) {
SecurityContext prevContext = null;
try {
prevContext = userService.startSecurityContext(command);
commandExecutor.accept(command);
} catch (Exception e) {
var errorMessage = ERROR_MESSAGE_TEMPLATE.formatted(command.getOrder());
LOG.error(LOG_MESSAGE_TEMPLATE, errorMessage, e);
eventPublisher.publishEvent(new CommandFailedEvent(command.getId(), buildErrorMessage(errorMessage, e)));
} finally {
userService.resetSecurityContext(prevContext);
}
}
private String buildErrorMessage(String message, Exception cause) {
try {
StringBuilder sb = new StringBuilder(message);
if (StringUtils.isNotBlank(cause.getMessage())) {
sb.append(" Cause: ").append(cause.getMessage());
}
return sb.toString();
} catch (Exception e2) {
LOG.error("Error in building Error Message (sick).", e2);
return message;
}
}
}
......@@ -4,13 +4,16 @@ import java.util.Optional;
import java.util.stream.Stream;
import de.ozgcloud.bescheid.BescheidServiceGrpc.BescheidServiceImplBase;
import de.ozgcloud.bescheid.vorgang.VorgangId;
import de.ozgcloud.document.bescheid.Bescheid;
import de.ozgcloud.document.bescheid.BescheidService;
import de.ozgcloud.document.bescheid.vorgang.VorgangId;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
@RequiredArgsConstructor
@Deprecated
class BescheidGrpcService extends BescheidServiceImplBase {
private final BescheidService service;
......
package de.ozgcloud.bescheid;
import de.ozgcloud.common.datatype.StringBasedValue;
public class BescheidId extends StringBasedValue {
BescheidId(String id) {
super(id);
}
public static BescheidId from(String id) {
return new BescheidId(id);
}
}
/*
* 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.bescheid;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class BescheidManagerConfig {
private String version;
private String javaVersion;
private Features features;
@Builder
@Getter
static class Features {
private boolean canCreateBescheidDocument;
}
}
/*
* 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.bescheid;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import de.ozgcloud.apilib.common.command.OzgCloudCommandService;
import de.ozgcloud.apilib.common.command.grpc.CommandMapper;
import de.ozgcloud.apilib.common.command.grpc.GrpcOzgCloudCommandService;
import de.ozgcloud.bescheid.common.callcontext.BescheidManagerCallContextProvider;
import de.ozgcloud.vorgang.grpc.command.CommandServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
@Configuration
public class BescheidManagerConfiguration {
public static final String COMMAND_SERVICE_NAME = "bescheid_OzgCloudCommandService";
@GrpcClient("command-manager")
private CommandServiceGrpc.CommandServiceBlockingStub commandServiceStub;
@Bean(COMMAND_SERVICE_NAME)
OzgCloudCommandService grpcOzgCloudCommandService(CommandMapper commandMapper, BescheidManagerCallContextProvider contextProvider) {
return new GrpcOzgCloudCommandService(commandServiceStub, commandMapper, contextProvider,
GrpcOzgCloudCommandService.DEFAULT_COMMAND_REQUEST_THRESHOLD_MILLIS);
}
}
package de.ozgcloud.bescheid;
import static de.ozgcloud.bescheid.Bescheid.*;
import static de.ozgcloud.document.bescheid.Bescheid.*;
import java.time.LocalDate;
import java.util.Collection;
......@@ -21,18 +21,26 @@ import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.ReportingPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import de.ozgcloud.bescheid.vorgang.VorgangId;
import de.ozgcloud.command.Command;
import de.ozgcloud.common.attached_item.AttachedItem;
import de.ozgcloud.common.attached_item.AttachedItemService;
import de.ozgcloud.common.binaryfile.FileId;
import de.ozgcloud.common.datatype.StringBasedValue;
import de.ozgcloud.document.bescheid.Bescheid;
import de.ozgcloud.document.bescheid.Bescheid.SendBy;
import de.ozgcloud.document.bescheid.BescheidCallContextAttachingInterceptor;
import de.ozgcloud.document.bescheid.BescheidId;
import de.ozgcloud.document.bescheid.BescheidManagerConfig;
import de.ozgcloud.document.bescheid.SentInfo;
import de.ozgcloud.document.bescheid.UserId;
import de.ozgcloud.document.bescheid.vorgang.VorgangId;
import de.ozgcloud.document.common.attached_item.AttachedItem;
import de.ozgcloud.document.common.attached_item.AttachedItemService;
@Mapper(uses = { SentInfoMapper.class }, //
unmappedTargetPolicy = ReportingPolicy.WARN, //
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, //
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, //
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) //
@Deprecated
public abstract class BescheidMapper {
@Autowired
......
package de.ozgcloud.bescheid;
import de.ozgcloud.bescheid.vorgang.Vorgang;
public interface BescheidRemoteService {
BescheidResponse create(BescheidRequest request, Vorgang vorgang);
}
package de.ozgcloud.bescheid;
import java.time.LocalDate;
import de.ozgcloud.bescheid.common.user.UserProfile;
import de.ozgcloud.bescheid.vorgang.VorgangId;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class BescheidRequest {
private VorgangId vorgangId;
private LocalDate bescheidVom;
private boolean genehmigt;
private UserProfile createFor;
}
package de.ozgcloud.bescheid;
import java.io.File;
import java.util.List;
import java.util.Optional;
import de.ozgcloud.bescheid.vorgang.Vorgang.ServiceKonto;
import de.ozgcloud.bescheid.vorgang.VorgangId;
import de.ozgcloud.common.binaryfile.FileId;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
import lombok.With;
@Builder(toBuilder = true)
@Getter
public class BescheidResponse {
private VorgangId vorgangId;
private boolean bewilligt;
private UserId createdBy;
private String bescheidFileName;
private File bescheidFile;
@With
private FileId bescheidFileId;
private String contentType;
private long size;
@Builder.Default
private Optional<String> nachrichtSubject = Optional.empty();
@Builder.Default
private Optional<String> nachrichtText = Optional.empty();
@Singular
private List<FileId> attachments;
private ServiceKonto serviceKonto;
}
\ No newline at end of file
/*
* 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.bescheid;
import de.ozgcloud.command.Command;
import de.ozgcloud.command.CommandExecutedEvent;
public class BescheidSentEvent extends CommandExecutedEvent {
public BescheidSentEvent(final Command command) {
super(command);
}
}
package de.ozgcloud.bescheid;
import static java.util.Objects.*;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.annotation.PostConstruct;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Service;
import de.ozgcloud.apilib.common.command.OzgCloudCommand;
import de.ozgcloud.apilib.common.command.OzgCloudCommandService;
import de.ozgcloud.apilib.common.command.OzgCloudCreateSubCommandsRequest;
import de.ozgcloud.apilib.common.command.grpc.CommandMapper;
import de.ozgcloud.bescheid.administration.AdministrationService;
import de.ozgcloud.bescheid.attributes.ClientAttributeService;
import de.ozgcloud.bescheid.common.freemarker.TemplateHandler;
import de.ozgcloud.bescheid.common.user.UserProfileService;
import de.ozgcloud.bescheid.vorgang.Vorgang;
import de.ozgcloud.bescheid.vorgang.VorgangId;
import de.ozgcloud.bescheid.vorgang.VorgangService;
import de.ozgcloud.command.Command;
import de.ozgcloud.common.attached_item.AttachedItem;
import de.ozgcloud.common.attached_item.AttachedItemService;
import de.ozgcloud.common.binaryfile.FileId;
import de.ozgcloud.common.errorhandling.TechnicalException;
import de.ozgcloud.document.Document;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Service
@Log4j2
@RequiredArgsConstructor
public class BescheidService {
static final String DEFAULT_NACHRICHT_SUBJECT = "Ihr Bescheid zum Antrag";
public static final String DEFAULT_NACHRICHT_TEMPLATE_FILE = "bescheid.nachrichtTemplate.txt.ftlh";
public static final String KEY_TEMPLATE_SIGNATURE = "signature";
private static final String ERROR_MESSAGE_NO_SERVICE = "No Bescheid Endpoint is configured.";
static final String VORGANG_BESCHEIDEN_ORDER = "VORGANG_BESCHEIDEN";
static final String SUBCOMMANDS_EXECUTION_MODE = "PARALLEL";
static final String SEND_POSTFACH_NACHRICHT_ORDER = "SEND_POSTFACH_NACHRICHT";
static final String FIELD_POSTFACH_ID = "postfachId";
static final String FIELD_REPLY_OPTION = "replyOption";
static final String REPLY_OPTION = "FORBIDDEN";
static final String FIELD_SUBJECT = "subject";
static final String FIELD_MAIL_BODY = "mailBody";
static final String FIELD_ATTACHMENTS = "attachments";
static final String SUBJECT = "Ihr Bescheid zum Antrag";
private final VorgangService vorgangService;
private final AttachedItemService attachedItemService;
private final UserProfileService userProfileService;
@Qualifier(BescheidManagerConfiguration.COMMAND_SERVICE_NAME)
private final OzgCloudCommandService commandService;
private final AdministrationService administrationService;
private final CommandMapper commandMapper;
private final ClientAttributeService bescheidClientAttributeService;
private final BuildProperties buildProperties;
private final Optional<BescheidRemoteService> remoteService;
private final BescheidMapper mapper;
private final TemplateHandler templateHandler;
@PostConstruct
void logStatus() {
remoteService.ifPresentOrElse(
service -> LOG.info("Bescheid-Manager is configured."),
() -> LOG.info("No BescheidRemoteService configured - Bescheid creation is not possible."));
}
public Optional<Bescheid> findDraft(String vorgangId) {
return attachedItemService.findBescheidItem(VorgangId.from(vorgangId)).map(mapper::mapFromAttachedItem);
}
public Stream<Bescheid> findAll(VorgangId vorgangId) {
return attachedItemService.findAllBescheid(vorgangId).stream().map(mapper::mapFromAttachedItem);
}
public String createBescheidDraft(Command createBescheidDraftCommand) {
validateBescheidData(createBescheidDraftCommand.getBodyObject());
return attachedItemService.createBescheidDraft(buildBescheidDraft(createBescheidDraftCommand));
}
void validateBescheidData(Map<String, Object> bodyObject) {
validateBescheidField(bodyObject, Bescheid.FIELD_BESCHIEDEN_AM);
validateBescheidField(bodyObject, Bescheid.FIELD_BEWILLIGT);
validateSendBy(MapUtils.getString(bodyObject, Bescheid.FIELD_SEND_BY));
}
void validateBescheidField(Map<String, Object> bescheid, String field) {
if (isNull(bescheid.get(field))) {
throw new TechnicalException("Fields '%s' is required for bescheid creation".formatted(field));
}
}
void validateSendBy(String sendBy) {
if (isUnknownSendByValue(sendBy)) {
var possibleSendByValues = Arrays.stream(Bescheid.SendBy.values()).map(Bescheid.SendBy::name)
.collect(Collectors.joining(","));
var message = String.format("Unexpected value for field '%s': %s. Allowed are: %s", Bescheid.FIELD_SEND_BY, sendBy,
possibleSendByValues);
throw new TechnicalException(message);
}
}
private boolean isUnknownSendByValue(String sendByValue) {
return Arrays.stream(Bescheid.SendBy.values()).noneMatch(sendBy -> sendBy.hasValue(sendByValue));
}
Bescheid buildBescheidDraft(Command command) {
var bescheid = mapper.mapFromCommand(command);
var resultBuilder = bescheid.toBuilder().id(null).version(0).status(Bescheid.Status.DRAFT);
if (bescheid.getNachrichtSubject().isEmpty()) {
resultBuilder.nachrichtSubject(Optional.of(DEFAULT_NACHRICHT_SUBJECT));
}
if (bescheid.getNachrichtText().isEmpty()) {
resultBuilder.nachrichtText(Optional.of(createNachrichtText()));
}
return resultBuilder.build();
}
String createNachrichtText() {
return templateHandler.fillTemplate(DEFAULT_NACHRICHT_TEMPLATE_FILE, getTemplateParameters());
}
Map<String, Object> getTemplateParameters() {
return administrationService.getNachrichtSignature().map(signature -> Map.<String, Object>of(KEY_TEMPLATE_SIGNATURE, signature))
.orElseGet(Collections::emptyMap);
}
public BescheidResponse createBescheid(BescheidRequest request) {
checkRemoteService();
return doCreateBescheid(request);
}
private BescheidResponse doCreateBescheid(BescheidRequest request) {
var vorgang = vorgangService.getById(request.getVorgangId());
return remoteService.map(service -> service.create(request, vorgang))
.map(bescheid -> updateBescheid(bescheid, vorgang))
.orElseThrow(() -> new TechnicalException(ERROR_MESSAGE_NO_SERVICE));
}
private BescheidResponse updateBescheid(BescheidResponse bescheid, Vorgang vorgang) {
return bescheid.toBuilder().vorgangId(vorgang.getId()).serviceKonto(vorgang.getServiceKonto()).build();
}
private void checkRemoteService() {
if (remoteService.isEmpty()) {
LOG.error(ERROR_MESSAGE_NO_SERVICE);
throw new TechnicalException(ERROR_MESSAGE_NO_SERVICE);
}
}
public void sendBescheidManually(AttachedItem bescheidItem, Command parentCommand) {
validateBescheidSendManually(bescheidItem, parentCommand.getRelationVersion());
var vorgang = vorgangService.getById(VorgangId.from(bescheidItem.getVorgangId()));
addSubCommands(parentCommand.getId(), buildSetBescheidSentSubCommands(bescheidItem, vorgang, getUserId()));
}
void validateBescheidSendManually(AttachedItem bescheidItem, long version) {
validateBescheid(bescheidItem, version);
var sendBy = bescheidItem.getItem().get(Bescheid.FIELD_SEND_BY);
if (Bescheid.SendBy.MANUAL.notValue(sendBy)) {
throw new TechnicalException(
"Bescheid has unexpected sendBy value: '%s'. Expected is %s".formatted(sendBy, Bescheid.SendBy.MANUAL));
}
}
public void sendBescheidPostfachMail(AttachedItem bescheidItem, Command parentCommand) {
validateBescheidSendPostfach(bescheidItem, parentCommand.getRelationVersion());
var vorgang = vorgangService.getById(VorgangId.from(bescheidItem.getVorgangId()));
var userId = getUserId();
var subCommands = buildSetBescheidSentSubCommands(bescheidItem, vorgang, userId);
subCommands.add(buildSendPostfachNachrichtCommand(bescheidItem, vorgang.getServiceKonto(), userId));
addSubCommands(parentCommand.getId(), subCommands);
}
public void validateBescheidSendPostfach(AttachedItem bescheidItem, long version) {
validateBescheid(bescheidItem, version);
var sendBy = bescheidItem.getItem().get(Bescheid.FIELD_SEND_BY);
if (Bescheid.SendBy.NACHRICHT.notValue(sendBy)) {
throw new TechnicalException(
"Bescheid has unexpected sendBy value: '%s'. Expected is %s".formatted(sendBy, Bescheid.SendBy.NACHRICHT));
}
if (getNachrichtSubject(bescheidItem).isEmpty()) {
throw new TechnicalException("Bescheid has no nachricht subject");
}
if (getNachrichtText(bescheidItem).isEmpty()) {
throw new TechnicalException("Bescheid has no nachricht text");
}
}
void validateBescheid(AttachedItem bescheidItem, long version) {
if (bescheidItem.getVersion() != version) {
throw new TechnicalException("Bescheid has different version. Expected: %d, but was: %d".formatted(version, bescheidItem.getVersion()));
}
var status = MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_STATUS);
if (Bescheid.Status.DRAFT.not(status)) {
throw new TechnicalException("Bescheid has status '%s'. Bescheid must have status DRAFT".formatted(status));
}
if (StringUtils.isBlank(MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_BESCHEID_DOCUMENT))) {
throw new TechnicalException("Bescheid has no document");
}
}
Optional<String> getNachrichtSubject(AttachedItem bescheidItem) {
return Optional.ofNullable(MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_NACHRICHT_SUBJECT))
.map(StringUtils::trimToNull);
}
Optional<String> getNachrichtText(AttachedItem bescheidItem) {
return Optional.ofNullable(MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_NACHRICHT_TEXT))
.map(StringUtils::trimToNull);
}
String getUserId() {
return userProfileService.getUserProfile().getId().toString();
}
List<OzgCloudCommand> buildSetBescheidSentSubCommands(AttachedItem bescheidItem, Vorgang vorgang, String userId) {
var subCommands = new ArrayList<OzgCloudCommand>();
subCommands.add(buildVorgangBescheidenCommand(vorgang));
subCommands.add(buildSetBescheidSentStatusCommand(bescheidItem, userId));
return subCommands;
}
OzgCloudCommand buildVorgangBescheidenCommand(Vorgang vorgang) {
return OzgCloudCommand.builder()
.vorgangId(commandMapper.toOzgCloudVorgangId(vorgang.getId().toString()))
.relationId(commandMapper.mapRelationId(vorgang.getId().toString()))
.relationVersion(vorgang.getVersion())
.order(VORGANG_BESCHEIDEN_ORDER)
.build();
}
OzgCloudCommand buildSetBescheidSentStatusCommand(AttachedItem bescheidItem, String userId) {
return OzgCloudCommand.builder()
.relationId(commandMapper.mapRelationId(bescheidItem.getId()))
.relationVersion(bescheidItem.getVersion())
.order(AttachedItemService.PATCH_ATTACHED_ITEM)
.bodyObject(Map.of(
AttachedItem.PROPERTY_ID, bescheidItem.getId(),
AttachedItem.PROPERTY_ITEM, buildBescheidSentStatusItem(userId)))
.build();
}
Map<String, Object> buildBescheidSentStatusItem(String userId) {
return Map.of(
Bescheid.FIELD_STATUS, Bescheid.Status.SENT.name(),
Bescheid.FIELD_SENT_INFO, buildSentInfoMap(userId));
}
Map<String, Object> buildSentInfoMap(String userId) {
return Map.of(
SentInfo.FIELD_SENT_AT, ZonedDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME),
SentInfo.FIELD_SENT_BY, userId);
}
OzgCloudCommand buildSendPostfachNachrichtCommand(AttachedItem bescheidItem, Vorgang.ServiceKonto serviceKonto, String userId) {
var bodyObject = buildSendNachrichtCommandBody(bescheidItem, buildPostfachAddress(serviceKonto.getPostfachAddresses().getFirst(),
serviceKonto.getType()));
return OzgCloudCommand.builder()
.vorgangId(commandMapper.toOzgCloudVorgangId(bescheidItem.getVorgangId()))
.relationId(commandMapper.mapRelationId(bescheidItem.getVorgangId()))
.order(SEND_POSTFACH_NACHRICHT_ORDER)
.createdBy(commandMapper.toOzgCloudUserId(userId))
.bodyObject(bodyObject)
.build();
}
Map<String, Object> buildPostfachAddress(Vorgang.PostfachAddress postfachAddress, String serviceKontoType) {
return Map.of(Vorgang.PostfachAddress.FIELD_TYPE, postfachAddress.getType(),
Vorgang.PostfachAddress.FIELD_VERSION, postfachAddress.getVersion(),
Vorgang.PostfachAddress.FIELD_IDENTIFIER, postfachAddress.getIdentifier(),
Vorgang.ServiceKonto.FIELD_SERVICEKONTO_TYPE, serviceKontoType);
}
Map<String, Object> buildSendNachrichtCommandBody(AttachedItem bescheidItem, Map<String, Object> postfachAddress) {
return Map.of(
FIELD_REPLY_OPTION, REPLY_OPTION,
FIELD_SUBJECT, getNachrichtSubject(bescheidItem).orElse(SUBJECT),
FIELD_MAIL_BODY, getNachrichtText(bescheidItem).orElse(StringUtils.EMPTY),
FIELD_ATTACHMENTS, buildAttachments(bescheidItem),
Vorgang.ServiceKonto.FIELD_POSTFACH_ADDRESS, postfachAddress);
}
List<String> buildAttachments(AttachedItem bescheidItem) {
return Stream.concat(Stream.of(getBescheidFileId(bescheidItem)), getAttachments(bescheidItem).stream()).map(FileId::toString).toList();
}
FileId getBescheidFileId(AttachedItem bescheidItem) {
var bescheidDocument = attachedItemService.getItem(MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_BESCHEID_DOCUMENT));
return FileId.from(MapUtils.getString(bescheidDocument.getItem(), Document.FIELD_DOCUMENT_FILE));
}
List<FileId> getAttachments(AttachedItem bescheidItem) {
var attachmentsObject = bescheidItem.getItem().get(Bescheid.FIELD_ATTACHMENTS);
if (attachmentsObject instanceof Collection<?> attachments) {
return attachments.stream().map(String::valueOf).map(FileId::from).toList();
}
return getStringAsList(attachmentsObject);
}
List<FileId> getStringAsList(Object attachmentsObject) {
if (attachmentsObject instanceof String attachment && StringUtils.isNotBlank(attachment)) {
return Collections.singletonList(FileId.from(attachment));
}
return Collections.emptyList();
}
void addSubCommands(String parentCommandId, List<OzgCloudCommand> subCommands) {
commandService.addSubCommands(buildCreateSubCommandsRequest(parentCommandId, subCommands));
}
OzgCloudCreateSubCommandsRequest buildCreateSubCommandsRequest(String parentCommandId, List<OzgCloudCommand> subCommands) {
return OzgCloudCreateSubCommandsRequest.builder()
.parentId(parentCommandId)
.executionMode(SUBCOMMANDS_EXECUTION_MODE)
.completedIfSubsCompleted(true)
.subCommands(subCommands)
.build();
}
public void setAntragBewilligung(AttachedItem bescheidItem) {
try {
bescheidClientAttributeService.setAntragResult(bescheidItem.getVorgangId(), getBewilligt(bescheidItem));
} catch (Exception e) {
LOG.error("Cannot set antrag result on vorgang (id: {})", bescheidItem.getVorgangId(), e);
}
}
boolean getBewilligt(AttachedItem bescheidItem) {
return MapUtils.getBooleanValue(bescheidItem.getItem(), Bescheid.FIELD_BEWILLIGT, false);
}
public BescheidManagerConfig getConfig() {
return BescheidManagerConfig.builder()
.version(buildProperties.getVersion())
.javaVersion(getJavaVersion())
.features(buildFeatures())
.build();
}
String getJavaVersion() {
return System.getProperty("java.version");
}
BescheidManagerConfig.Features buildFeatures() {
return BescheidManagerConfig.Features.builder()
.canCreateBescheidDocument(remoteService.isPresent())
.build();
}
public Bescheid getBescheid(String id) {
return mapper.mapFromAttachedItem(attachedItemService.getItem(id));
}
}
package de.ozgcloud.bescheid;
import de.ozgcloud.command.Command;
import de.ozgcloud.command.CommandExecutedEvent;
class BescheidUpdatedEvent extends CommandExecutedEvent {
public BescheidUpdatedEvent(Command command) {
super(command);
}
}
/*
* 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.bescheid;
import java.time.ZonedDateTime;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class SentInfo {
public static final String FIELD_SENT_BY = "sentBy";
public static final String FIELD_SENT_AT = "sentAt";
private String sentBy;
private ZonedDateTime sentAt;
}
......@@ -12,7 +12,10 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import de.ozgcloud.document.bescheid.SentInfo;
@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
@Deprecated
public interface SentInfoMapper {
String GRPC_DATE_TIME_FORMATTER = "yyyy-MM-dd'T'HH:mm:ss'Z'";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment