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

prj42 fix creating postfach message - add postfach address

parent dcf409bb
No related branches found
No related tags found
No related merge requests found
Showing
with 118 additions and 28 deletions
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
<properties> <properties>
<pluto.version>1.12.0-SNAPSHOT</pluto.version> <pluto.version>1.13.0-SNAPSHOT</pluto.version>
</properties> </properties>
<dependencies> <dependencies>
...@@ -28,6 +28,10 @@ ...@@ -28,6 +28,10 @@
<artifactId>pluto-command</artifactId> <artifactId>pluto-command</artifactId>
<version>${pluto.version}</version> <version>${pluto.version}</version>
</dependency> </dependency>
<dependency>
<groupId>de.itvsh.ozg.pluto</groupId>
<artifactId>pluto-utils</artifactId>
</dependency>
<dependency> <dependency>
<groupId>de.itvsh.ozg.pluto</groupId> <groupId>de.itvsh.ozg.pluto</groupId>
<artifactId>pluto-interface</artifactId> <artifactId>pluto-interface</artifactId>
......
...@@ -3,6 +3,7 @@ package de.ozgcloud.bescheid; ...@@ -3,6 +3,7 @@ package de.ozgcloud.bescheid;
import java.io.File; import java.io.File;
import de.itvsh.kop.common.binaryfile.FileId; import de.itvsh.kop.common.binaryfile.FileId;
import de.ozgcloud.bescheid.vorgang.Vorgang;
import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.bescheid.vorgang.VorgangId;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
...@@ -24,4 +25,5 @@ public class Bescheid { ...@@ -24,4 +25,5 @@ public class Bescheid {
private String contentType; private String contentType;
private long size; private long size;
private Vorgang.ServiceKonto serviceKonto;
} }
...@@ -69,11 +69,9 @@ class BescheidEventListener { ...@@ -69,11 +69,9 @@ class BescheidEventListener {
LOG.error("Error on executing Create Bescheid Command. Command failed.", e); LOG.error("Error on executing Create Bescheid Command. Command failed.", e);
eventPublisher.publishEvent(new CommandFailedEvent(command.getId(), buildErrorMessage(e))); eventPublisher.publishEvent(new CommandFailedEvent(command.getId(), buildErrorMessage(e)));
} finally { } finally {
if (prevContext != null) {
userService.resetSecurityContext(prevContext); userService.resetSecurityContext(prevContext);
} }
} }
}
public void doCreateBescheidBiz(@NonNull Command command) { public void doCreateBescheidBiz(@NonNull Command command) {
var bescheid = service.createBescheid(createRequest(command)); var bescheid = service.createBescheid(createRequest(command));
......
...@@ -23,11 +23,14 @@ class BescheidService { ...@@ -23,11 +23,14 @@ class BescheidService {
public Bescheid createBescheid(BescheidRequest request) { public Bescheid createBescheid(BescheidRequest request) {
checkRemoteService(); checkRemoteService();
var vorgang = vorgangService.getById(request.getVorgangId()); return doCreateBescheid(request);
}
var bescheid = remoteService.create(request, vorgang);
return bescheid.toBuilder().vorgangId(request.getVorgangId()).build(); private Bescheid doCreateBescheid(BescheidRequest request) {
var vorgang = vorgangService.getById(request.getVorgangId());
return remoteService.create(request, vorgang)
.toBuilder().vorgangId(vorgang.getId()).serviceKonto(vorgang.getServiceKonto())
.build();
} }
private void checkRemoteService() { private void checkRemoteService() {
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
*/ */
package de.ozgcloud.bescheid.common.callcontext; package de.ozgcloud.bescheid.common.callcontext;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -98,6 +99,8 @@ public class CurrentUserService { ...@@ -98,6 +99,8 @@ public class CurrentUserService {
public void resetSecurityContext(SecurityContext context) { public void resetSecurityContext(SecurityContext context) {
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();
if (Objects.nonNull(context)) {
SecurityContextHolder.setContext(context); SecurityContextHolder.setContext(context);
} }
} }
}
\ No newline at end of file
...@@ -2,6 +2,7 @@ package de.ozgcloud.bescheid.nachricht; ...@@ -2,6 +2,7 @@ package de.ozgcloud.bescheid.nachricht;
import de.itvsh.kop.common.binaryfile.FileId; import de.itvsh.kop.common.binaryfile.FileId;
import de.ozgcloud.bescheid.UserId; import de.ozgcloud.bescheid.UserId;
import de.ozgcloud.bescheid.vorgang.Vorgang;
import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.bescheid.vorgang.VorgangId;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
...@@ -23,4 +24,6 @@ public class Nachricht { ...@@ -23,4 +24,6 @@ public class Nachricht {
@NonNull @NonNull
private UserId createdBy; private UserId createdBy;
private Vorgang.PostfachAddress postfachAddress;
} }
...@@ -4,14 +4,19 @@ import org.mapstruct.CollectionMappingStrategy; ...@@ -4,14 +4,19 @@ import org.mapstruct.CollectionMappingStrategy;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.NullValueCheckStrategy; import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.ReportingPolicy;
import de.itvsh.kop.common.binaryfile.FileId; import de.itvsh.kop.common.binaryfile.FileId;
import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
import de.itvsh.ozg.mail.postfach.GrpcPostfachNachricht; import de.itvsh.ozg.mail.postfach.GrpcPostfachNachricht;
import de.itvsh.ozg.pluto.vorgang.GrpcPostfachAddress;
import de.ozgcloud.bescheid.vorgang.Vorgang;
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, // @Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, //
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, unmappedTargetPolicy = ReportingPolicy.ERROR, uses = GrpcObjectMapper.class)
public interface NachrichtMapper { public interface NachrichtMapper {
@Mapping(target = "mergePostfachAddress", ignore = true)
@Mapping(target = "mergeFrom", ignore = true) @Mapping(target = "mergeFrom", ignore = true)
@Mapping(target = "clearField", ignore = true) @Mapping(target = "clearField", ignore = true)
@Mapping(target = "clearOneof", ignore = true) @Mapping(target = "clearOneof", ignore = true)
...@@ -28,6 +33,16 @@ public interface NachrichtMapper { ...@@ -28,6 +33,16 @@ public interface NachrichtMapper {
@Mapping(target = "replyOption", constant = "FORBIDDEN") @Mapping(target = "replyOption", constant = "FORBIDDEN")
GrpcPostfachNachricht mapToGrpc(Nachricht nachricht); GrpcPostfachNachricht mapToGrpc(Nachricht nachricht);
@Mapping(target = "mergeFrom", ignore = true)
@Mapping(target = "clearField", ignore = true)
@Mapping(target = "clearOneof", ignore = true)
@Mapping(target = "mergeIdentifier", ignore = true)
@Mapping(target = "mergeUnknownFields", ignore = true)
@Mapping(target = "unknownFields", ignore = true)
@Mapping(target = "versionBytes", ignore = true)
@Mapping(target = "allFields", ignore = true)
GrpcPostfachAddress mapAddress(Vorgang.PostfachAddress address);
default String mapToSTring(FileId fileId) { default String mapToSTring(FileId fileId) {
return fileId.toString(); return fileId.toString();
} }
......
...@@ -2,17 +2,22 @@ package de.ozgcloud.bescheid.nachricht; ...@@ -2,17 +2,22 @@ package de.ozgcloud.bescheid.nachricht;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Objects;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import de.itvsh.kop.common.errorhandling.TechnicalException; import de.itvsh.kop.common.errorhandling.TechnicalException;
import de.ozgcloud.bescheid.Bescheid; import de.ozgcloud.bescheid.Bescheid;
import de.ozgcloud.bescheid.vorgang.Vorgang.PostfachAddress;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.Template; import freemarker.template.Template;
import freemarker.template.TemplateException; import freemarker.template.TemplateException;
import lombok.extern.log4j.Log4j2;
@Service @Service
@Log4j2
public class NachrichtService { public class NachrichtService {
@Autowired @Autowired
...@@ -26,17 +31,26 @@ public class NachrichtService { ...@@ -26,17 +31,26 @@ public class NachrichtService {
private static final String TEMPLATE_FILE = "bescheid.nachrichtTemplate.txt.ftlh"; private static final String TEMPLATE_FILE = "bescheid.nachrichtTemplate.txt.ftlh";
public void createNachrichtDraft(Bescheid bescheid) { public void createNachrichtDraft(Bescheid bescheid) {
remoteService.saveDraft(buildNachricht(bescheid)); buildNachricht(bescheid).ifPresentOrElse(remoteService::saveDraft, () -> LOG.warn("No ServiceKonto given on Vorgang."));
} }
Nachricht buildNachricht(Bescheid bescheid) { Optional<Nachricht> buildNachricht(Bescheid bescheid) {
return Nachricht.builder() return getAddress(bescheid).map(address -> Nachricht.builder()
.vorgangId(bescheid.getVorgangId()) .vorgangId(bescheid.getVorgangId())
.postfachAddress(address)
.subject(SUBJECT) .subject(SUBJECT)
.mailBody(buildMessage(bescheid)) .mailBody(buildMessage(bescheid))
.createdBy(bescheid.getCreatedBy()) .createdBy(bescheid.getCreatedBy())
.bescheidFileId(bescheid.getBescheidFileId()) .bescheidFileId(bescheid.getBescheidFileId())
.build(); .build());
}
Optional<PostfachAddress> getAddress(Bescheid bescheid) {
var serviceKonto = bescheid.getServiceKonto();
if (Objects.nonNull(serviceKonto)) {
return Optional.of(serviceKonto.getPostfachAddresses().get(0));
}
return Optional.empty();
} }
String buildMessage(Bescheid bescheid) { String buildMessage(Bescheid bescheid) {
......
...@@ -9,18 +9,28 @@ import org.mapstruct.Mapping; ...@@ -9,18 +9,28 @@ import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy; import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
import de.itvsh.ozg.pluto.vorgang.GrpcEingang; import de.itvsh.ozg.pluto.vorgang.GrpcEingang;
import de.itvsh.ozg.pluto.vorgang.GrpcFormData; import de.itvsh.ozg.pluto.vorgang.GrpcFormData;
import de.itvsh.ozg.pluto.vorgang.GrpcPostfachAddress;
import de.itvsh.ozg.pluto.vorgang.GrpcServiceKonto;
import de.itvsh.ozg.pluto.vorgang.GrpcVorgangWithEingang; import de.itvsh.ozg.pluto.vorgang.GrpcVorgangWithEingang;
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, // @Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, //
uses = FormDataEntryMapper.class) uses = { FormDataEntryMapper.class, GrpcObjectMapper.class })
interface BescheidVorgangMapper { interface BescheidVorgangMapper {
@Mapping(target = "serviceKonto", source = "header.serviceKonto")
@Mapping(target = "vorgangNummer", source = "nummer") @Mapping(target = "vorgangNummer", source = "nummer")
@Mapping(target = "vorgangName", source = "name") @Mapping(target = "vorgangName", source = "name")
Vorgang mapVorgang(GrpcVorgangWithEingang vorgang); Vorgang mapVorgang(GrpcVorgangWithEingang vorgang);
@Mapping(target = "postfachAddress", ignore = true)
@Mapping(target = "postfachAddresses", source = "postfachAddressesList")
Vorgang.ServiceKonto mapServiceKonto(GrpcServiceKonto serviceKonto);
Vorgang.PostfachAddress mapAddress(GrpcPostfachAddress address);
VorgangId mapVorgangId(String vorgangId); VorgangId mapVorgangId(String vorgangId);
Vorgang.Eingang mapEingang(GrpcEingang eingang); Vorgang.Eingang mapEingang(GrpcEingang eingang);
...@@ -34,4 +44,5 @@ interface BescheidVorgangMapper { ...@@ -34,4 +44,5 @@ interface BescheidVorgangMapper {
return Collections.unmodifiableList(result); return Collections.unmodifiableList(result);
} }
} }
package de.ozgcloud.bescheid.vorgang; package de.ozgcloud.bescheid.vorgang;
import java.util.List; import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
...@@ -23,8 +24,26 @@ public class Vorgang { ...@@ -23,8 +24,26 @@ public class Vorgang {
private String vorgangNummer; private String vorgangNummer;
private String aktenzeichen; private String aktenzeichen;
private ServiceKonto serviceKonto;
private Eingang eingang; private Eingang eingang;
@Builder
@Getter
public static class ServiceKonto {
private String type;
@Singular
private List<PostfachAddress> postfachAddresses;
}
@Builder
@Getter
public static class PostfachAddress {
private String version;
private int type;
private Map<String, Object> identifier;
}
@Builder @Builder
@Getter @Getter
static class Eingang { static class Eingang {
......
...@@ -6,6 +6,8 @@ import org.springframework.http.MediaType; ...@@ -6,6 +6,8 @@ import org.springframework.http.MediaType;
import de.itvsh.kop.common.binaryfile.TempFileUtils; import de.itvsh.kop.common.binaryfile.TempFileUtils;
import de.itvsh.ozg.pluto.command.CommandTestFactory; import de.itvsh.ozg.pluto.command.CommandTestFactory;
import de.ozgcloud.bescheid.vorgang.Vorgang.PostfachAddress;
import de.ozgcloud.bescheid.vorgang.Vorgang.ServiceKonto;
import de.ozgcloud.bescheid.vorgang.VorgangTestFactory; import de.ozgcloud.bescheid.vorgang.VorgangTestFactory;
public class BescheidTestFactory { public class BescheidTestFactory {
...@@ -30,6 +32,9 @@ public class BescheidTestFactory { ...@@ -30,6 +32,9 @@ public class BescheidTestFactory {
.bescheidFileName(FILE_NAME) .bescheidFileName(FILE_NAME)
.bescheidFile(BESCHEID_FILE) .bescheidFile(BESCHEID_FILE)
.genehmigt(true) .genehmigt(true)
.size(TEST_BESCHEID.length); .size(TEST_BESCHEID.length)
.serviceKonto(ServiceKonto.builder()
.postfachAddress(PostfachAddress.builder().build())
.build());
} }
} }
...@@ -4,6 +4,8 @@ import static org.assertj.core.api.Assertions.*; ...@@ -4,6 +4,8 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*; import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -30,7 +32,7 @@ class NachrichtServiceTest { ...@@ -30,7 +32,7 @@ class NachrichtServiceTest {
@Test @Test
void shouldCallRemoteService() { void shouldCallRemoteService() {
doReturn(nachricht).when(service).buildNachricht(any()); doReturn(Optional.of(nachricht)).when(service).buildNachricht(any());
service.createNachrichtDraft(BescheidTestFactory.create()); service.createNachrichtDraft(BescheidTestFactory.create());
...@@ -47,28 +49,28 @@ class NachrichtServiceTest { ...@@ -47,28 +49,28 @@ class NachrichtServiceTest {
@Test @Test
void shouldFillMailBody() { void shouldFillMailBody() {
var nachricht = service.buildNachricht(BescheidTestFactory.create()); var nachricht = service.buildNachricht(BescheidTestFactory.create()).get();
assertThat(nachricht.getMailBody()).isEqualTo(NachrichtTestFactory.MAIL_BODY); assertThat(nachricht.getMailBody()).isEqualTo(NachrichtTestFactory.MAIL_BODY);
} }
@Test @Test
void shouldSetSubject() { void shouldSetSubject() {
var nachricht = service.buildNachricht(BescheidTestFactory.create()); var nachricht = service.buildNachricht(BescheidTestFactory.create()).get();
assertThat(nachricht.getSubject()).isEqualTo(NachrichtService.SUBJECT); assertThat(nachricht.getSubject()).isEqualTo(NachrichtService.SUBJECT);
} }
@Test @Test
void shouldSetUser() { void shouldSetUser() {
var nachricht = service.buildNachricht(BescheidTestFactory.create()); var nachricht = service.buildNachricht(BescheidTestFactory.create()).get();
assertThat(nachricht.getCreatedBy()).isEqualTo(BescheidTestFactory.CREATED_BY); assertThat(nachricht.getCreatedBy()).isEqualTo(BescheidTestFactory.CREATED_BY);
} }
@Test @Test
void shouldSetVorgangId() { void shouldSetVorgangId() {
var nachricht = service.buildNachricht(BescheidTestFactory.create()); var nachricht = service.buildNachricht(BescheidTestFactory.create()).get();
assertThat(nachricht.getVorgangId()).isEqualTo(VorgangTestFactory.ID); assertThat(nachricht.getVorgangId()).isEqualTo(VorgangTestFactory.ID);
} }
......
...@@ -3,7 +3,10 @@ package de.itvsh.ozg.mail.postfach; ...@@ -3,7 +3,10 @@ package de.itvsh.ozg.mail.postfach;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@Mapper import de.itvsh.kop.pluto.common.grpc.GrpcObjectMapper;
import de.itvsh.ozg.pluto.common.GrpcObject;
@Mapper(uses = GrpcObjectMapper.class)
public interface GrpcPostfachNachrichtMapper { public interface GrpcPostfachNachrichtMapper {
@Mapping(target = "attachments", source = "attachmentList") @Mapping(target = "attachments", source = "attachmentList")
...@@ -12,11 +15,13 @@ public interface GrpcPostfachNachrichtMapper { ...@@ -12,11 +15,13 @@ public interface GrpcPostfachNachrichtMapper {
@Mapping(target = "direction", constant = "OUT") @Mapping(target = "direction", constant = "OUT")
@Mapping(target = "messageCode", ignore = true) @Mapping(target = "messageCode", ignore = true)
@Mapping(target = "messageId", ignore = true) @Mapping(target = "messageId", ignore = true)
@Mapping(target = "postfachAddress", ignore = true) // uh
@Mapping(target = "postfachId", ignore = true) @Mapping(target = "postfachId", ignore = true)
@Mapping(target = "sentAt", ignore = true) @Mapping(target = "sentAt", ignore = true)
@Mapping(target = "sentSuccessful", ignore = true) @Mapping(target = "sentSuccessful", ignore = true)
@Mapping(target = "vorgangId", ignore = true) @Mapping(target = "vorgangId", ignore = true)
PostfachNachricht mapFromGrpc(GrpcPostfachNachricht nachricht); PostfachNachricht mapFromGrpc(GrpcPostfachNachricht nachricht);
default PostfachAddressIdentifier map(GrpcObject value) {
return StringBasedIdentifier.builder().postfachId(value.getProperty(0).getValue(0)).build();
}
} }
...@@ -106,7 +106,7 @@ public abstract class PostfachNachrichtMapper { ...@@ -106,7 +106,7 @@ public abstract class PostfachNachrichtMapper {
.messageId(MapUtils.getString(mailMap, PostfachNachricht.FIELD_MESSAGE_ID)) .messageId(MapUtils.getString(mailMap, PostfachNachricht.FIELD_MESSAGE_ID))
.createdAt(ZonedDateTime.parse(MapUtils.getString(mailMap, PostfachNachricht.FIELD_CREATED_AT))) .createdAt(ZonedDateTime.parse(MapUtils.getString(mailMap, PostfachNachricht.FIELD_CREATED_AT)))
.createdBy(MapUtils.getString(mailMap, PostfachNachricht.FIELD_CREATED_BY)) .createdBy(MapUtils.getString(mailMap, PostfachNachricht.FIELD_CREATED_BY))
.sentAt(ZonedDateTime.parse(MapUtils.getString(mailMap, PostfachNachricht.FIELD_SENT_AT))) .sentAt(getString(mailMap, PostfachNachricht.FIELD_SENT_AT).map(ZonedDateTime::parse).orElse(null))
.sentSuccessful(MapUtils.getBoolean(mailMap, PostfachNachricht.FIELD_SENT_SUCCESSFUL)) .sentSuccessful(MapUtils.getBoolean(mailMap, PostfachNachricht.FIELD_SENT_SUCCESSFUL))
.messageCode(MapUtils.getString(mailMap, PostfachNachricht.FIELD_MESSAGE_CODE)) .messageCode(MapUtils.getString(mailMap, PostfachNachricht.FIELD_MESSAGE_CODE))
.direction(Direction.valueOf(MapUtils.getString(mailMap, PostfachNachricht.FIELD_DIRECTION))) .direction(Direction.valueOf(MapUtils.getString(mailMap, PostfachNachricht.FIELD_DIRECTION)))
...@@ -123,6 +123,10 @@ public abstract class PostfachNachrichtMapper { ...@@ -123,6 +123,10 @@ public abstract class PostfachNachrichtMapper {
return postfachMailBuilder.build(); return postfachMailBuilder.build();
} }
private Optional<String> getString(Map<String, Object> mailMap, String key) {
return Optional.ofNullable(MapUtils.getString(mailMap, key));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private List<String> getAsList(Map<String, Object> mailMap, String fieldName) { private List<String> getAsList(Map<String, Object> mailMap, String fieldName) {
return (List<String>) mailMap.getOrDefault(fieldName, Collections.emptyList()); return (List<String>) mailMap.getOrDefault(fieldName, Collections.emptyList());
......
...@@ -74,10 +74,12 @@ enum GrpcDirection { ...@@ -74,10 +74,12 @@ enum GrpcDirection {
message GrpcPostfachNachricht { message GrpcPostfachNachricht {
string id = 1; string id = 1;
string subject = 2; de.itvsh.ozg.pluto.vorgang.GrpcPostfachAddress postfachAddress = 2;
string mailBody = 3; string subject = 3;
string replyOption = 4; string mailBody = 4;
repeated string attachment = 5; string replyOption = 5;
repeated string attachment = 6;
} }
message GrpcPostfachMail { message GrpcPostfachMail {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment