From 8cc8097f33ba2bfc1a11fa9a22702625abb853c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8C=E1=85=A1=E1=86=BC=E1=84=89=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=8B=E1=85=A2?= Date: Fri, 8 Sep 2023 10:17:39 +0900 Subject: [PATCH] =?UTF-8?q?Tube=20On/Off=20=EC=A0=9C=EC=96=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 14 +- .../ControlCenterManagementApplication.java | 6 + .../application/DefaultTubeService.java | 124 ++++++++++++++++++ .../application/SlotRepositoryDelegate.java | 12 ++ .../application/TubeService.java | 14 ++ .../domain/Command.java | 9 ++ .../domain/CommandType.java | 14 ++ .../domain/DeployRequest.java | 98 ++++++++++++++ .../controlcentermanagement/domain/Slot.java | 117 +++++++++++++++++ .../controlcentermanagement/domain/Tube.java | 33 +++++ .../amqp/IamAmqpConnectionProperties.java | 73 +++++++++++ .../amqp/RegisterResourceMappingDelegate.java | 47 +++++++ .../amqp/RegisterResourceProducer.java | 56 ++++++++ .../infrastructure/amqp/ResourceMapping.java | 15 +++ .../amqp/ResourceMessagePayload.java | 4 + .../infrastructure/nosql/SlotDocument.java | 58 ++++++++ .../nosql/SlotNoSQLRepository.java | 47 +++++++ .../infrastructure/nosql/SlotRepository.java | 12 ++ .../infrastructure/nosql/TubeDocument.java | 30 +++++ .../resttemplate/RestTemplateHandler.java | 9 ++ .../RestTemplateRequestHandler.java | 33 +++++ .../presentation/TubeController.java | 36 +++++ src/main/resources/application-k8s.yaml | 20 +++ src/main/resources/application.yaml | 21 +++ .../nosql/SlotNoSQLRepositoryTest.java | 41 ++++++ 25 files changed, 942 insertions(+), 1 deletion(-) create mode 100644 src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/application/SlotRepositoryDelegate.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/domain/Command.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/domain/CommandType.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/domain/DeployRequest.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/domain/Slot.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/domain/Tube.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/IamAmqpConnectionProperties.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceMappingDelegate.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceProducer.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMapping.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMessagePayload.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotDocument.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepository.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotRepository.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/TubeDocument.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateHandler.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateRequestHandler.java create mode 100644 src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java create mode 100644 src/main/resources/application-k8s.yaml create mode 100644 src/test/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepositoryTest.java diff --git a/build.gradle b/build.gradle index 5a2eb95..80c4608 100644 --- a/build.gradle +++ b/build.gradle @@ -5,13 +5,24 @@ plugins { } group = 'inc.sdt.controlcentermanagement' -version = '0.0.1-SNAPSHOT' +version = '0.0.1' java { sourceCompatibility = '17' } +def codeArtifactToken = "aws codeartifact get-authorization-token --domain sdt --domain-owner 003960268191 --region ap-northeast-1 --query authorizationToken --output text".execute().text + repositories { + maven { + url 'https://sdt-003960268191.d.codeartifact.ap-northeast-1.amazonaws.com/maven/sdt-development/' + credentials { + username "aws" + password codeArtifactToken + } +// url 'http://192.168.1.232:8081/repository/maven-releases/' +// allowInsecureProtocol true + } mavenCentral() } @@ -21,6 +32,7 @@ dependencies { } implementation("org.springframework.boot:spring-boot-starter-undertow") implementation("org.springframework.boot:spring-boot-starter-data-mongodb") + implementation("org.springframework.boot:spring-boot-starter-amqp") testImplementation ("org.springframework.boot:spring-boot-starter-test") } diff --git a/src/main/java/inc/sdt/controlcentermanagement/ControlCenterManagementApplication.java b/src/main/java/inc/sdt/controlcentermanagement/ControlCenterManagementApplication.java index 0479aab..cfbf970 100644 --- a/src/main/java/inc/sdt/controlcentermanagement/ControlCenterManagementApplication.java +++ b/src/main/java/inc/sdt/controlcentermanagement/ControlCenterManagementApplication.java @@ -2,6 +2,8 @@ package inc.sdt.controlcentermanagement; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; @SpringBootApplication public class ControlCenterManagementApplication { @@ -10,4 +12,8 @@ public class ControlCenterManagementApplication { SpringApplication.run(ControlCenterManagementApplication.class, args); } + @Bean + RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java b/src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java new file mode 100644 index 0000000..369db73 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java @@ -0,0 +1,124 @@ +package inc.sdt.controlcentermanagement.application; + +import com.fasterxml.jackson.core.type.TypeReference; +import inc.sdt.controlcentermanagement.domain.CommandType; +import inc.sdt.controlcentermanagement.domain.DeployRequest; +import inc.sdt.controlcentermanagement.domain.Slot; +import inc.sdt.controlcentermanagement.domain.Tube; +import inc.sdt.controlcentermanagement.infrastructure.resttemplate.RestTemplateRequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +@Service +class DefaultTubeService implements TubeService { + private final SlotRepositoryDelegate slotRepositoryDelegate; + private final RestTemplateRequestHandler restTemplateHandler; + private final String deployerEndpoint; + private final Logger log; + + public DefaultTubeService(SlotRepositoryDelegate slotRepositoryDelegate, RestTemplateRequestHandler restTemplateHandler, @Value("${deployer.endpoint}") String deployerEndpoint) { + this.slotRepositoryDelegate = slotRepositoryDelegate; + this.restTemplateHandler = restTemplateHandler; + this.deployerEndpoint = deployerEndpoint; + this.log = LoggerFactory.getLogger(DefaultTubeService.class); + } + + @Override + public List get(String chamberNumber) { + log.info("[get] chamberNumber: {}", chamberNumber); + return slotRepositoryDelegate.get(chamberNumber); + } + + @Override + public void controlByTubeCode(String authorization, Slot request) { + log.info("[controlByTubeCode] request: {}", request); + Map tubeMap = new LinkedHashMap<>(); + for (Tube tube : request.getTube()) { + Map tubeOffsetMap = this.tubeOffset(request); + String offsetKey = request.getSlotNumber().substring(request.getSlotNumber().length() - 1); + int tubeOffset = tubeOffsetMap.getOrDefault(offsetKey, 1); + int tubeKey = Integer.parseInt(tube.getCode()) + tubeOffset; + tubeMap.put(String.valueOf(tubeKey), String.valueOf(request.getCommand().ordinal())); + } + + DeployRequest deployRequest = DeployRequest.builder() + .assetCode(request.getAssetCode()) + .deviceType("nodeq") + .appName(request.getSlotNumber()) + .commandType(CommandType.JSON) + .parameters(tubeMap) + .build(); + + this.invokeDeployer(request.getAssetCode(), authorization, deployRequest); + } + + @Override + public void controlAll(String authorization, Slot request) { + log.info("[controlAll] request: {}", request); + + List slots = this.get(request.getChamberNumber()); + Map>> resultMap = new LinkedHashMap<>(); + for (Slot slot : slots) { + String assetCode = slot.getAssetCode(); + String slotNumber = slot.getSlotNumber(); + List tubes = slot.getTube(); + + Map> assetMap = resultMap.computeIfAbsent(assetCode, k -> new HashMap<>()); + Map slotMap = assetMap.computeIfAbsent(slotNumber, k -> new HashMap<>()); + + for (Tube tube : tubes) { + Map tubeOffsetMap = this.tubeOffset(request); + String offsetKey = slotNumber.substring(slotNumber.length() - 1); + int tubeOffset = tubeOffsetMap.getOrDefault(offsetKey, 1); + int offsetResult = Integer.parseInt(tube.getCode()) + tubeOffset; + + slotMap.put(String.valueOf(offsetResult), String.valueOf(request.getCommand().ordinal())); + } + } + + resultMap.forEach((assetCode, slot) -> slot.forEach((slotNumber, tube) -> { + DeployRequest deployRequest = DeployRequest.builder() + .assetCode(assetCode) + .deviceType("nodeq") + .appName(slotNumber) + .commandType(CommandType.JSON) + .parameters(tube) + .build(); + + this.invokeDeployer(assetCode, authorization, deployRequest); + })); + } + + private Map tubeOffset(Slot request) { + log.info("[tubeOffset] request: {}", request); + + int offset = 0; + Map tubeOffsetMap = new HashMap<>(); + for (int i = 1; i <= 8; i++) { + tubeOffsetMap.put(String.valueOf(i), offset); + offset += 16; + if (i == 4) offset = 0; + } + return tubeOffsetMap; + } + + private void invokeDeployer(String assetCode, String authorization, DeployRequest deployRequest) { + String url = UriComponentsBuilder.fromUriString(deployerEndpoint) + .path(String.format("/assets/%s/apps", assetCode)) + .build() + .toUriString(); + restTemplateHandler.httpPostRequestWithAuth(url, authorization, deployRequest, new TypeReference<>() { + }); + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/application/SlotRepositoryDelegate.java b/src/main/java/inc/sdt/controlcentermanagement/application/SlotRepositoryDelegate.java new file mode 100644 index 0000000..9f9e443 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/application/SlotRepositoryDelegate.java @@ -0,0 +1,12 @@ +package inc.sdt.controlcentermanagement.application; + +import inc.sdt.controlcentermanagement.domain.Slot; + +import java.util.List; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public interface SlotRepositoryDelegate { + List get(String chamberNumber); +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java b/src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java new file mode 100644 index 0000000..03ab0e5 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java @@ -0,0 +1,14 @@ +package inc.sdt.controlcentermanagement.application; + +import inc.sdt.controlcentermanagement.domain.Slot; + +import java.util.List; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public interface TubeService { + List get(String chamberNumber); + void controlByTubeCode(String authorization, Slot request); + void controlAll(String authorization, Slot request); +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/domain/Command.java b/src/main/java/inc/sdt/controlcentermanagement/domain/Command.java new file mode 100644 index 0000000..075af9a --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/domain/Command.java @@ -0,0 +1,9 @@ +package inc.sdt.controlcentermanagement.domain; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public enum Command { + OFF, + ON; +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/domain/CommandType.java b/src/main/java/inc/sdt/controlcentermanagement/domain/CommandType.java new file mode 100644 index 0000000..5191a81 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/domain/CommandType.java @@ -0,0 +1,14 @@ +package inc.sdt.controlcentermanagement.domain; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public enum CommandType { + JSON("json"); + + private String type; + + CommandType(String type) { + this.type = type; + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/domain/DeployRequest.java b/src/main/java/inc/sdt/controlcentermanagement/domain/DeployRequest.java new file mode 100644 index 0000000..8ef98d4 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/domain/DeployRequest.java @@ -0,0 +1,98 @@ +package inc.sdt.controlcentermanagement.domain; + +import java.util.Map; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public class DeployRequest { + private String assetCode; + private String deviceType; + private String appName; + private String commandType; +// private String fileName; // TODO: deployer에 추가되면 추가 + private Map parameters; + + public DeployRequest(String assetCode, String deviceType, String appName, String commandType, Map parameters) { + this.assetCode = assetCode; + this.deviceType = deviceType; + this.appName = appName; + this.commandType = commandType; + this.parameters = parameters; + } + + public static Builder builder() { + return new Builder(); + } + + public String getAssetCode() { + return assetCode; + } + + public String getDeviceType() { + return deviceType; + } + + public String getAppName() { + return appName; + } + + public String getCommandType() { + return commandType; + } + + public Map getParameters() { + return parameters; + } + + @Override + public String toString() { + return "DeployRequest{" + + "assetCode='" + assetCode + '\'' + + ", deviceType='" + deviceType + '\'' + + ", appName='" + appName + '\'' + + ", commandType=" + commandType + + ", parameters=" + parameters + + '}'; + } + + public static final class Builder { + private String assetCode; + private String deviceType; + private String appName; + private String commandType; + private Map parameters; + + private Builder() { + } + + public Builder assetCode(String assetCode) { + this.assetCode = assetCode; + return this; + } + + public Builder deviceType(String deviceType) { + this.deviceType = deviceType; + return this; + } + + public Builder appName(String appName) { + this.appName = appName; + return this; + } + + public Builder commandType(CommandType commandType) { + this.commandType = commandType.toString().toLowerCase(); + return this; + } + + public Builder parameters(Map parameters) { + this.parameters = parameters; + return this; + } + + public DeployRequest build() { + return new DeployRequest(assetCode, deviceType, appName, commandType, parameters); + } + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/domain/Slot.java b/src/main/java/inc/sdt/controlcentermanagement/domain/Slot.java new file mode 100644 index 0000000..3c90a0c --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/domain/Slot.java @@ -0,0 +1,117 @@ +package inc.sdt.controlcentermanagement.domain; + +import java.util.List; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public class Slot { + private String id; + private String slotNumber; + private String assetCode; + private List tube; + private String chamberNumber; + private Command command; + + protected Slot() { + } + + public Slot(String id,String slotNumber, String assetCode, List tube, String chamberNumber, Command command) { + this.id = id; + this.slotNumber = slotNumber; + this.assetCode = assetCode; + this.tube = tube; + this.chamberNumber = chamberNumber; + this.command = command; + } + + public static Builder builder() { + return new Builder(); + } + + public String getId() { + return id; + } + + public String getSlotNumber() { + return slotNumber; + } + + public String getAssetCode() { + return assetCode; + } + + public List getTube() { + return tube; + } + + public String getChamberNumber() { + return chamberNumber; + } + + public void setChamberNumber(String chamberNumber) { + this.chamberNumber = chamberNumber; + } + + public Command getCommand() { + return command; + } + + @Override + public String toString() { + return "Slot{" + + "id='" + id + '\'' + + ", slotNumber='" + slotNumber + '\'' + + ", assetCode='" + assetCode + '\'' + + ", tube=" + tube + + ", chamberNumber='" + chamberNumber + '\'' + + ", command=" + command + + '}'; + } + + public static final class Builder { + private String id; + private String slotNumber; + private String assetCode; + private List tube; + private String chamberNumber; + private Command command; + + private Builder() { + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder slotNumber(String slotNumber) { + this.slotNumber = slotNumber; + return this; + } + + public Builder assetCode(String assetCode) { + this.assetCode = assetCode; + return this; + } + + public Builder tube(List tube) { + this.tube = tube; + return this; + } + + public Builder chamberNumber(String chamberNumber) { + this.chamberNumber = chamberNumber; + return this; + } + + public Builder Command(Command command) { + this.command = command; + return this; + } + + public Slot build() { + return new Slot(id, slotNumber, assetCode, tube, chamberNumber, command); + } + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/domain/Tube.java b/src/main/java/inc/sdt/controlcentermanagement/domain/Tube.java new file mode 100644 index 0000000..af11073 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/domain/Tube.java @@ -0,0 +1,33 @@ +package inc.sdt.controlcentermanagement.domain; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public class Tube { + private String code; + private String offset; + + protected Tube() { + } + + public Tube(String code, String offset) { + this.code = code; + this.offset = offset; + } + + public String getCode() { + return code; + } + + public String getOffset() { + return offset; + } + + @Override + public String toString() { + return "Tube{" + + "number='" + code + '\'' + + ", offset=" + offset + + '}'; + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/IamAmqpConnectionProperties.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/IamAmqpConnectionProperties.java new file mode 100644 index 0000000..2dcf22d --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/IamAmqpConnectionProperties.java @@ -0,0 +1,73 @@ +package inc.sdt.controlcentermanagement.infrastructure.amqp; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "iam.amqp") +class IamAmqpConnectionProperties { + private String host; + private Integer port; + private String username; + private String password; + private String exchange; + private String routingKey; + + public String getHost() { + return host; + } + + void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + void setPort(Integer port) { + this.port = port; + } + + public String getUsername() { + return username; + } + + void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + void setPassword(String password) { + this.password = password; + } + + public String getExchange() { + return exchange; + } + + void setExchange(String exchange) { + this.exchange = exchange; + } + + public String getRoutingKey() { + return routingKey; + } + + void setRoutingKey(String routingKey) { + this.routingKey = routingKey; + } + + @Override + public String toString() { + return "IamAmqpConnectionProperties{" + + "host='" + host + '\'' + + ", port=" + port + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + ", exchange='" + exchange + '\'' + + ", routingKey='" + routingKey + '\'' + + '}'; + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceMappingDelegate.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceMappingDelegate.java new file mode 100644 index 0000000..6467497 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceMappingDelegate.java @@ -0,0 +1,47 @@ +package inc.sdt.controlcentermanagement.infrastructure.amqp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.Map; +import java.util.Objects; + +@ConditionalOnProperty(prefix = "iam", name = "enabled", havingValue = "true") +@Component +class RegisterResourceMappingDelegate { + private final String applicationName; + private final RegisterResourceProducer registerResourceProducer; + private final Logger log; + + RegisterResourceMappingDelegate(@Value("${spring.application.name}") String applicationName, + RegisterResourceProducer registerResourceProducer) { + this.log = LoggerFactory.getLogger(RegisterResourceMappingDelegate.class); + log.info("[Constructor] applicationName={}", applicationName); + this.registerResourceProducer = registerResourceProducer; + this.applicationName = applicationName; + } + + @EventListener + public void handleContextRefreshedEvent(ContextRefreshedEvent event) { + log.info("[handleContextRefreshedEvent] event = {}", event); + ApplicationContext applicationContext = event.getApplicationContext(); + RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + Map map = requestMappingHandlerMapping.getHandlerMethods(); + + map.values().stream() + .filter(value -> Objects.nonNull(value.getMethod().getDeclaredAnnotation(ResourceMapping.class))) + .map(value -> value.getMethod().getDeclaredAnnotation(ResourceMapping.class)) + .map(annotation -> new ResourceMessagePayload(annotation.name(), annotation.method(), annotation.uri(), applicationName, annotation.description())) + .forEach(registerResourceProducer); + } + +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceProducer.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceProducer.java new file mode 100644 index 0000000..0689a72 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/RegisterResourceProducer.java @@ -0,0 +1,56 @@ +package inc.sdt.controlcentermanagement.infrastructure.amqp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.function.Consumer; + +@ConditionalOnProperty(prefix = "iam", name = "enabled", havingValue = "true") +@EnableConfigurationProperties({IamAmqpConnectionProperties.class}) +@Component +class RegisterResourceProducer implements Consumer { + private final RabbitTemplate rabbitTemplate; + private final MessageConverter messageConverter; + private final MessageProperties messageProperties; + private final String exchange; + private final String routingKey; + private final Logger log; + + RegisterResourceProducer(IamAmqpConnectionProperties connectionProperties) { + this.log = LoggerFactory.getLogger(RegisterResourceProducer.class); + log.info("[Constructor] connectionProperties={}", connectionProperties); + + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setHost(connectionProperties.getHost()); + connectionFactory.setPort(connectionProperties.getPort()); + connectionFactory.setUsername(connectionProperties.getUsername()); + connectionFactory.setPassword(connectionProperties.getPassword()); + + this.messageConverter = new Jackson2JsonMessageConverter(); + this.rabbitTemplate = new RabbitTemplate(connectionFactory); + this.rabbitTemplate.setMessageConverter(this.messageConverter); + + this.exchange = connectionProperties.getExchange(); + this.routingKey = connectionProperties.getRoutingKey(); + + this.messageProperties = new MessageProperties(); + this.messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); + this.messageProperties.setHeader("Command", "CREATE"); + } + + @Override + public void accept(ResourceMessagePayload payload) { + log.info("[accept] payload={}", payload); + Message message = messageConverter.toMessage(payload, messageProperties); + rabbitTemplate.send(exchange, routingKey, message); + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMapping.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMapping.java new file mode 100644 index 0000000..46655c3 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMapping.java @@ -0,0 +1,15 @@ +package inc.sdt.controlcentermanagement.infrastructure.amqp; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ResourceMapping { + String name(); + String method(); + String uri(); + String description(); +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMessagePayload.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMessagePayload.java new file mode 100644 index 0000000..fbb8d44 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/amqp/ResourceMessagePayload.java @@ -0,0 +1,4 @@ +package inc.sdt.controlcentermanagement.infrastructure.amqp; + +public record ResourceMessagePayload(String name, String method, String uri, String applicationName, String description) { +} \ No newline at end of file diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotDocument.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotDocument.java new file mode 100644 index 0000000..21d4fda --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotDocument.java @@ -0,0 +1,58 @@ +package inc.sdt.controlcentermanagement.infrastructure.nosql; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.List; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +@Document("slot") +public class SlotDocument { + @Id + private String id; + private String slotNumber; + private String assetCode; + private List tube; + private String chamberNumber; + + public SlotDocument(String id, String slotNumber, String assetCode, List tube, String chamberNumber) { + this.id = id; + this.slotNumber = slotNumber; + this.assetCode = assetCode; + this.tube = tube; + this.chamberNumber = chamberNumber; + } + + public String getId() { + return id; + } + + public String getSlotNumber() { + return slotNumber; + } + + public String getAssetCode() { + return assetCode; + } + + public List getTube() { + return tube; + } + + public String getChamberNumber() { + return chamberNumber; + } + + @Override + public String toString() { + return "SlotDocument{" + + "id='" + id + '\'' + + ", slotNumber='" + slotNumber + '\'' + + ", assetCode='" + assetCode + '\'' + + ", tube=" + tube + + ", chamberNumber='" + chamberNumber + '\'' + + '}'; + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepository.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepository.java new file mode 100644 index 0000000..31ee454 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepository.java @@ -0,0 +1,47 @@ +package inc.sdt.controlcentermanagement.infrastructure.nosql; + +import inc.sdt.controlcentermanagement.application.SlotRepositoryDelegate; +import inc.sdt.controlcentermanagement.domain.Slot; +import inc.sdt.controlcentermanagement.domain.Tube; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +@Repository +public class SlotNoSQLRepository implements SlotRepositoryDelegate { + + private final SlotRepository slotRepository; + private final Logger log; + + public SlotNoSQLRepository(SlotRepository slotRepository) { + this.slotRepository = slotRepository; + this.log = LoggerFactory.getLogger(SlotNoSQLRepository.class); + } + + @Override + public List get(String chamberNumber) { + log.debug("[get] chamberNumber: {}", chamberNumber); + return slotRepository.findAllByChamberNumber(chamberNumber) + .stream() + .map(this::to) + .toList(); + } + + private Slot to(SlotDocument slotDocument) { + return Slot.builder() + .id(slotDocument.getId()) + .slotNumber(slotDocument.getSlotNumber()) + .assetCode(slotDocument.getAssetCode()) + .chamberNumber(slotDocument.getChamberNumber()) + .tube(slotDocument.getTube() + .stream() + .map(tubeDocument -> new Tube(tubeDocument.getCode(), tubeDocument.getOffset())) + .toList()) + .build(); + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotRepository.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotRepository.java new file mode 100644 index 0000000..c7e6b69 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotRepository.java @@ -0,0 +1,12 @@ +package inc.sdt.controlcentermanagement.infrastructure.nosql; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public interface SlotRepository extends MongoRepository { + List findAllByChamberNumber(String chamberNumber); +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/TubeDocument.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/TubeDocument.java new file mode 100644 index 0000000..c66e69a --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/nosql/TubeDocument.java @@ -0,0 +1,30 @@ +package inc.sdt.controlcentermanagement.infrastructure.nosql; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +public class TubeDocument { + private String code; + private String offset; + + public TubeDocument(String code, String offset) { + this.code = code; + this.offset = offset; + } + + public String getCode() { + return code; + } + + public String getOffset() { + return offset; + } + + @Override + public String toString() { + return "TubeDocument{" + + "id='" + code + '\'' + + ", offset=" + offset + + '}'; + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateHandler.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateHandler.java new file mode 100644 index 0000000..5312a20 --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateHandler.java @@ -0,0 +1,9 @@ +package inc.sdt.controlcentermanagement.infrastructure.resttemplate; + +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.Optional; + +public interface RestTemplateHandler { + Optional httpPostRequestWithAuth(String url, String authorization, Object body, TypeReference typeReference); +} \ No newline at end of file diff --git a/src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateRequestHandler.java b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateRequestHandler.java new file mode 100644 index 0000000..fabcc2f --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/infrastructure/resttemplate/RestTemplateRequestHandler.java @@ -0,0 +1,33 @@ +package inc.sdt.controlcentermanagement.infrastructure.resttemplate; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.Optional; + +@Component +public class RestTemplateRequestHandler implements RestTemplateHandler { + private final RestTemplate restTemplate; + private final Logger log; + + public RestTemplateRequestHandler(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + this.log = LoggerFactory.getLogger(RestTemplateRequestHandler.class); + } + + @Override + public Optional httpPostRequestWithAuth(String url, String authorization, Object body, TypeReference typeReference) { + log.info("[httpPostRequestWithAuth] url = {}", url); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(authorization.split(" ")[1]); + ParameterizedTypeReference parameterizedType = ParameterizedTypeReference.forType(typeReference.getType()); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(body, headers), parameterizedType); + return Optional.ofNullable(response.getBody()); + } +} diff --git a/src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java b/src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java new file mode 100644 index 0000000..8ddbd9f --- /dev/null +++ b/src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java @@ -0,0 +1,36 @@ +package inc.sdt.controlcentermanagement.presentation; + +import inc.sdt.controlcentermanagement.application.TubeService; +import inc.sdt.controlcentermanagement.domain.Slot; +import inc.sdt.controlcentermanagement.infrastructure.amqp.ResourceMapping; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +@RestController +public class TubeController { + private final TubeService tubeService; + private final Logger log; + + public TubeController(TubeService tubeService) { + this.tubeService = tubeService; + this.log = LoggerFactory.getLogger(TubeController.class); + } + + @ResourceMapping(name = "Tube_Control", method = "PATCH", uri = "/chamber/{chamberNumber}/tube/toggle", description = "Tube On/Off 제어") + @ResponseStatus(HttpStatus.OK) + @PatchMapping("/chamber/{chamberNumber}/tube/toggle") + public void toggle(@RequestHeader("Authorization") String authorization, @PathVariable String chamberNumber, @RequestBody Slot slot) { + log.info("[toggle] chamberNumber: {}, slot: {}", chamberNumber, slot); + slot.setChamberNumber(chamberNumber); + if (slot.getTube() != null) { + tubeService.controlByTubeCode(authorization, slot); + } else { + tubeService.controlAll(authorization, slot); + } + } +} diff --git a/src/main/resources/application-k8s.yaml b/src/main/resources/application-k8s.yaml new file mode 100644 index 0000000..9eb8526 --- /dev/null +++ b/src/main/resources/application-k8s.yaml @@ -0,0 +1,20 @@ +server: + port: ${SERVER_PORT} + +spring: + data: + mongodb: + uri: ${MONGODB_URL} + +deployer: + endpoint: ${DEPLOYER_ENDPOINT} + +iam: + enabled: true + amqp: + host: ${IAM_AMQP_HOST} + port: ${IAM_AMQP_PORT} + username: ${IAM_AMQP_CREDENTIALS_USERNAME} + password: ${IAM_AMQP_CREDENTIALS_PASSWORD} + exchange: iam + routing-key: resource \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8b13789..2827dc6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1 +1,22 @@ +server: + port: 8087 +spring: + application: + name: control-center-management + data: + mongodb: + uri: mongodb://sdt:251327@localhost:27017/awexomeray?authSource=admin + +deployer: + endpoint: http://localhost:8085 + +iam: + enabled: true + amqp: + host: localhost + port: 5672 + username: guest + password: guest + exchange: iam + routing-key: resource \ No newline at end of file diff --git a/src/test/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepositoryTest.java b/src/test/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepositoryTest.java new file mode 100644 index 0000000..db31b60 --- /dev/null +++ b/src/test/java/inc/sdt/controlcentermanagement/infrastructure/nosql/SlotNoSQLRepositoryTest.java @@ -0,0 +1,41 @@ +package inc.sdt.controlcentermanagement.infrastructure.nosql; + +import inc.sdt.controlcentermanagement.domain.Slot; +import inc.sdt.controlcentermanagement.domain.Tube; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +/** + * @author sunae.jang (sa.jang@sdt.inc) + */ +@SpringBootTest +@ActiveProfiles("local") +class SlotNoSQLRepositoryTest { + private final Logger log = LoggerFactory.getLogger(this.getClass()); + @Autowired + private SlotRepository slotRepository; + + @Test + void get() { + List findAllByChamberNumber = slotRepository.findAllByChamberNumber("1"); + log.info("list: {}", findAllByChamberNumber); + } + + private Slot to(SlotDocument slotDocument) { + List tube = slotDocument.getTube().stream() + .map(tubeDocument -> new Tube(tubeDocument.getCode(), tubeDocument.getOffset())).toList(); + return Slot.builder() + .id(slotDocument.getId()) + .slotNumber(slotDocument.getSlotNumber()) + .assetCode(slotDocument.getAssetCode()) + .chamberNumber(slotDocument.getChamberNumber()) + .tube(tube) + .build(); + } +} \ No newline at end of file