Compare commits

...

19 Commits

Author SHA1 Message Date
979fb3c0c5
Vibes � 2025-05-18 01:33:39 +03:00
5c8f333478
Автореформат кода 2024-05-30 13:25:17 +03:00
2ffcca7eeb
Команда получить продукт по id 2024-05-30 13:20:22 +03:00
5b95fff14d
Добавлена обработка инпута на сервере 2024-05-29 18:59:45 +03:00
db490ed5ec
Добавил README.md 2024-05-27 03:23:51 +03:00
b114a45cd8
Рефакторинг Server.java 2024-05-27 03:21:11 +03:00
723aceb374
6 лаба 2024-05-27 03:17:47 +03:00
ad354595d1 Merge branch 'lab5-dev' of https://gitlab.com/itmo_programming_2023/programming_2.12/student-3 into develop 2024-05-19 20:29:16 +03:00
3ac2784373
Добавил dml диаграмму 2024-05-19 20:23:57 +03:00
cd428e532b
Добавил описание ограничений у команд добавления 2024-05-15 17:47:28 +03:00
f3e6257e8e
Исправил обработку ctrl+c, ctrl+d 2024-05-15 17:01:29 +03:00
afc63b0772
Добавил обработку неверных аргументов 2024-05-02 22:07:48 +03:00
ca28cbab7d
Добавил валидацию при вводе значений 2024-05-02 21:48:25 +03:00
96a055a29d
User-Friendly при заполнении поелй 2024-05-02 20:25:35 +03:00
f00ba84c10
remove_by_id non existing elem #1 2024-05-02 19:39:17 +03:00
a630bf28f8
Убрал логи валидатора 2024-05-02 19:31:33 +03:00
d33c983dbb
Решил execute_script не хочет #7 2024-04-17 18:20:47 +03:00
d4fe517eb8
Починил ошибку с валидацией null значений 2024-04-17 17:53:37 +03:00
722b9f4d8c
Add dockerfile 2024-04-17 13:19:48 +03:00
115 changed files with 2860 additions and 968 deletions

95
Lab.Client/pom.xml Normal file
View File

@ -0,0 +1,95 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Client</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Lab6-Client</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>5.0.0-M1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Domain</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
<mainClass>me.zinch.Lab6.Client.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,14 @@
package me.zinch.Lab6.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.console.ShutdownHook;
/**
* Main class for running the application.
*/
public class App {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
Console.run();
}
}

View File

@ -0,0 +1,84 @@
package me.zinch.Lab6.Client.client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.ConnectionErrorException;
import me.zinch.Lab6.Client.exceptions.ResponseException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.Message;
import me.zinch.Lab6.Domain.dto.MessageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
public class Client {
private static final Logger log = LoggerFactory.getLogger(Client.class);
private static final int MAX_RETRIES = 3;
private static final int RETRY_DELAY_MS = 1000;
private static final int SOCKET_TIMEOUT_MS = 5000;
private final String address;
private final int port;
public Client(String address, int port) throws ConnectionErrorException, ResponseException {
this.address = address;
this.port = port;
try {
if (sendMessage(new BodylessMessage(MessageType.HELLO)).getType() == MessageType.HELLO) {
Console.log(String.format("Connected to server %s:%s", address, port));
}
} catch (IOException | ClassNotFoundException e) {
throw new ResponseException();
}
}
public Message sendMessage(Message message) throws ClassNotFoundException, IOException {
int retries = 0;
while (true) {
try {
return trySendMessage(message);
} catch (ConnectException | SocketTimeoutException e) {
if (++retries > MAX_RETRIES) {
throw new IOException("Server is unavailable after " + MAX_RETRIES + " retries");
}
log.warn("Connection failed, retrying in {} ms... ({}/{})",
RETRY_DELAY_MS, retries, MAX_RETRIES);
try {
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting to retry");
}
}
}
}
private Message trySendMessage(Message message) throws IOException, ClassNotFoundException {
try (var socket = new Socket(address, port)) {
socket.setSoTimeout(SOCKET_TIMEOUT_MS);
try (var bufferedSocketOutputStream = new BufferedOutputStream(socket.getOutputStream());
var byteArrayOutputStream = new ByteArrayOutputStream();
var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
objectOutputStream.writeObject(message);
objectOutputStream.flush();
bufferedSocketOutputStream.write(byteArrayOutputStream.toByteArray());
bufferedSocketOutputStream.flush();
try (var objectInputStream = new ObjectInputStream(socket.getInputStream())) {
return (Message) objectInputStream.readObject();
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodyfulMessage;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* The Add class represents a command to add a new element to a collection.
* It extends the Command class.
*/
public class Add extends Command {
public Add() {
super("add {element}", "добавить новый элемент в коллекцию", Pattern.compile("^add"));
}
@Override
public String action(Client client) throws CommandActionException {
try {
var productDTO = Console.readProductDTO();
var request = new BodyfulMessage(MessageType.POST, Console.getLastCommand(), productDTO);
var response = (BodylessMessage) client.sendMessage(request);
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -0,0 +1,11 @@
package me.zinch.Lab6.Client.commands;
/**
* The Clear class represents a command to clear a collection.
* It extends the Command class.
*/
public class Clear extends Command {
public Clear() {
super("clear", "очистить коллекцию");
}
}

View File

@ -0,0 +1,59 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* Represents a command that can be executed.
*/
public abstract class Command {
private final String name;
private final String description;
private final Pattern pattern;
public Command(String name, String description, Pattern pattern) {
this.name = name;
this.description = description;
this.pattern = pattern;
}
public Command(String name, String description) {
this.name = name;
this.description = description;
this.pattern = Pattern.compile("^" + name);
}
public final String getName() {
return name;
}
public final String getSignature() {
return name.split(" ")[0];
}
public final String getDescription() {
return description;
}
public final Pattern getPattern() {
return pattern;
}
public final boolean checkPattern(String input) {
return pattern.matcher(input).matches();
}
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, this.getName()));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -0,0 +1,47 @@
package me.zinch.Lab6.Client.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Manages the registration and retrieval of commands.
*/
public class CommandManager {
private static final List<Command> commandList = new ArrayList<>();
static {
registerCommand(new Help());
registerCommand(new Info());
registerCommand(new Show());
registerCommand(new Add());
registerCommand(new Update());
registerCommand(new Remove());
registerCommand(new Clear());
registerCommand(new ExecuteScript());
registerCommand(new Exit());
registerCommand(new Head());
registerCommand(new RemoveHead());
registerCommand(new History());
registerCommand(new MaxByWeight());
registerCommand(new GroupCountingByType());
registerCommand(new FilterLessThanCharacter());
}
public static void registerCommand(Command command) {
commandList.add(command);
}
public static String getRegisteredCommand() {
return String.join("\n", commandList
.stream()
.map(command -> String.format("%s - %s", command.getName(), command.getDescription()))
.toList());
}
public static Optional<Command> getCommandByInput(String input) {
return commandList.stream()
.filter(command -> command.checkPattern(input))
.findFirst();
}
}

View File

@ -1,8 +1,8 @@
package me.zinch.commands; package me.zinch.Lab6.Client.commands;
import me.zinch.console.Console; import me.zinch.Lab6.Client.client.Client;
import me.zinch.files.ScriptLoader; import me.zinch.Lab6.Client.console.Console;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Client.files.ScriptLoader;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -15,6 +15,10 @@ import java.util.regex.Pattern;
public class ExecuteScript extends Command { public class ExecuteScript extends Command {
private static final List<String> executeStack = new ArrayList<>(); private static final List<String> executeStack = new ArrayList<>();
public ExecuteScript() {
super("execute_script file_name", "считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме", Pattern.compile("^execute_script .+"));
}
private void addFileToStack(String filePath) { private void addFileToStack(String filePath) {
executeStack.add(filePath); executeStack.add(filePath);
} }
@ -27,17 +31,15 @@ public class ExecuteScript extends Command {
return executeStack.contains(filePath); return executeStack.contains(filePath);
} }
public ExecuteScript() {
super("execute_script file_name", "считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме", Pattern.compile("^execute_script .+"));
}
@Override @Override
public String action(ProductCollection productCollection) { public String action(Client client) {
try { try {
var result = new StringBuilder();
var scriptFile = Console.getLastCommand().split(" ")[1]; var scriptFile = Console.getLastCommand().split(" ")[1];
result.append(String.format("Скрипт %s начинает работу.", scriptFile)).append("\n");
addFileToStack(scriptFile); addFileToStack(scriptFile);
var commandList = ScriptLoader.loadList(scriptFile); var commandList = ScriptLoader.loadList(scriptFile);
for (var input: commandList) { for (var input : commandList) {
input = input.trim(); input = input.trim();
var command = CommandManager.getCommandByInput(input); var command = CommandManager.getCommandByInput(input);
if (command.isPresent()) { if (command.isPresent()) {
@ -47,13 +49,14 @@ public class ExecuteScript extends Command {
if (checkFileInStack(nextFile)) continue; if (checkFileInStack(nextFile)) continue;
addFileToStack(nextFile); addFileToStack(nextFile);
} }
command.get().action(productCollection); result.append(command.get().action(client)).append("\n");
} else { } else {
new UnknownCommand().action(productCollection); new UnknownCommand().action(client);
} }
} }
clearStack(); clearStack();
return String.format("Скрипт %s завершил работу.%n", scriptFile); result.append(String.format("Скрипт %s завершил работу.", scriptFile)).append("\n");
return result.toString();
} catch (IOException e) { } catch (IOException e) {
return e.getMessage(); return e.getMessage();
} }

View File

@ -1,7 +1,7 @@
package me.zinch.commands; package me.zinch.Lab6.Client.commands;
import me.zinch.console.Console; import me.zinch.Lab6.Client.client.Client;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Client.console.Console;
/** /**
* A command to exit the program without saving to a file. * A command to exit the program without saving to a file.
@ -12,8 +12,8 @@ public class Exit extends Command {
} }
@Override @Override
public String action(ProductCollection productCollection) { public String action(Client client) {
Console.stopApp(); Console.stopAppWithoutSaving();
return ""; return "";
} }
} }

View File

@ -0,0 +1,33 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import java.io.IOException;
import java.util.regex.Pattern;
public class FilterLessThanCharacter extends Command {
public FilterLessThanCharacter() {
super(
"filter_less_than_character",
"вывести элементы, значение поля character которых меньше заданного",
Pattern.compile("^filter_less_than_character\\s+(CUNNING|WISE|EVIL|CHAOTIC|FICKLE)\\s*$")
);
}
@Override
public String action(Client client) throws CommandActionException {
try {
var character = DragonCharacter.valueOf(getName().split("\\s+")[1]);
var response = (BodylessMessage) client.sendMessage(
new BodylessMessage(MessageType.GET, "filter_less_than_character " + character.name())
);
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -0,0 +1,24 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class GroupCountingByType extends Command {
public GroupCountingByType() {
super("group_counting_by_type", "сгруппировать элементы коллекции по значению поля type, вывести количество элементов в каждой группе");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "group_counting_by_type"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -0,0 +1,24 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class Head extends Command {
public Head() {
super("head", "вывести первый элемент коллекции");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "head"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,6 +1,6 @@
package me.zinch.commands; package me.zinch.Lab6.Client.commands;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Client.client.Client;
/** /**
* A command to display help for available commands. * A command to display help for available commands.
@ -11,7 +11,7 @@ public class Help extends Command {
} }
@Override @Override
public String action(ProductCollection productCollection) { public String action(Client client) {
return CommandManager.getRegisteredCommand(); return CommandManager.getRegisteredCommand();
} }
} }

View File

@ -0,0 +1,31 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
public class History extends Command {
private static final int HISTORY_SIZE = 10;
private static final Queue<String> commandHistory = new LinkedList<>();
public History() {
super("history", "вывести последние 10 команд (без их аргументов)");
}
public static void addCommand(String command) {
commandHistory.offer(command);
if (commandHistory.size() > HISTORY_SIZE) {
commandHistory.poll();
}
}
@Override
public String action(Client client) throws CommandActionException {
return String.join("\n", commandHistory);
}
}

View File

@ -0,0 +1,10 @@
package me.zinch.Lab6.Client.commands;
/**
* A command to display information about the collection (type, initialization date, number of elements, etc.) to the standard output stream.
*/
public class Info extends Command {
public Info() {
super("info", "вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)");
}
}

View File

@ -0,0 +1,24 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class MaxByWeight extends Command {
public MaxByWeight() {
super("max_by_weight", "вывести любой объект из коллекции, значение поля weight которого является максимальным");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "max_by_weight"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -0,0 +1,33 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodyfulMessage;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* A command to remove an element from the collection by its id.
*/
public class Remove extends Command {
public Remove() {
super("remove_by_id id", "удалить элемент из коллекции по его id", Pattern.compile("^remove_by_id .+"));
}
@Override
public String action(Client client) throws CommandActionException {
try {
Long id = Long.parseLong(Console.getLastCommand().split(" ")[1]);
var response = (BodylessMessage) client.sendMessage(new BodyfulMessage(MessageType.POST, Console.getLastCommand(), id));
return response.getBody().toString();
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -0,0 +1,24 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class RemoveHead extends Command {
public RemoveHead() {
super("remove_head", "вывести первый элемент коллекции и удалить его");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "remove_head"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,6 +1,4 @@
package me.zinch.commands; package me.zinch.Lab6.Client.commands;
import me.zinch.wrapper.ProductCollection;
/** /**
* A command to display all elements of the collection in string representation to the standard output stream. * A command to display all elements of the collection in string representation to the standard output stream.
@ -9,9 +7,4 @@ public class Show extends Command {
public Show() { public Show() {
super("show", "вывести в стандартный поток вывода все элементы коллекции в строковом представлении"); super("show", "вывести в стандартный поток вывода все элементы коллекции в строковом представлении");
} }
@Override
public String action(ProductCollection productCollection) {
return productCollection.toString();
}
} }

View File

@ -1,6 +1,6 @@
package me.zinch.commands; package me.zinch.Lab6.Client.commands;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Client.client.Client;
/** /**
* Represents an unknown command. * Represents an unknown command.
@ -11,7 +11,7 @@ public class UnknownCommand extends Command {
} }
@Override @Override
public String action(ProductCollection productCollection) { public String action(Client client) {
return "Команда не распознана."; return "Команда не распознана.";
} }
} }

View File

@ -0,0 +1,48 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodyfulMessage;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageBody;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* A command to update the value of an element in the collection, whose id is specified.
*/
public class Update extends Command {
public Update() {
super("update id {element}", "обновить значение элемента коллекции, id которого равен заданному", Pattern.compile("^update .+"));
}
@Override
public String action(Client client) throws CommandActionException {
try {
var command = Console.getLastCommand();
Long id = Long.parseLong(command.split(" ")[1]);
var isIdExists = new IsIdExists();
Console.appendHistory(String.format("%s %s", isIdExists.getSignature(), id));
isIdExists.action(client);
var getProductById = new GetProductById();
Console.appendHistory(String.format("%s %s", getProductById.getSignature(), id));
var product = getProductById.action(client);
Console.log("Вы собираетесь обновить следующий продукт");
Console.log(product);
var productDTO = Console.readProductDTO();
var response = (BodylessMessage) client.sendMessage(
new BodyfulMessage(MessageType.POST, command,
new MessageBody(String.format("%s %s", command, id), productDTO)));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException | NumberFormatException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -0,0 +1,208 @@
package me.zinch.Lab6.Client.console;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.commands.CommandManager;
import me.zinch.Lab6.Client.exceptions.ColorFormatException;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Client.exceptions.ConnectionErrorException;
import me.zinch.Lab6.Client.exceptions.ResponseException;
import me.zinch.Lab6.Client.exceptions.UnitOfMeasureFormatException;
import me.zinch.Lab6.Domain.models.Color;
import me.zinch.Lab6.Domain.models.Coordinates;
import me.zinch.Lab6.Domain.models.Location;
import me.zinch.Lab6.Domain.models.Person;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.models.UnitOfMeasure;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* Console class provides methods for interacting with the console, reading user input, and managing application flow.
* This class includes methods for appending input history, stopping the application, reading product DTO from the console, and running the application loop.
*/
public class Console {
private static final Scanner scanner = new Scanner(System.in);
private static final List<String> history = new ArrayList<>();
private static boolean isRunning = true;
private static boolean isExitMessageShowed = false;
private static Client client;
public static void appendHistory(String input) {
history.add(input);
}
public static void log(Object msg) {
if (isRunning) System.out.println(msg.toString());
}
public static void log() {
log("");
}
public static void stopAppWithoutSaving() {
showExitMessage();
isRunning = false;
}
public static void stopAppWithSaving() {
stopAppWithoutSaving();
}
public static void showExitMessage() {
if (!isExitMessageShowed) log("\nВсего хорошего!");
isExitMessageShowed = true;
}
public static String getLastCommand() {
return history.get(history.size() - 1);
}
private static String readString() {
if (!scanner.hasNextLine()) Console.stopAppWithSaving();
var input = scanner.nextLine().trim();
return input.isEmpty() ? null : input;
}
private static Long readLong() {
var input = readString();
if (input == null || input.isEmpty()) return null;
try {
return Long.parseLong(input);
} catch (NumberFormatException e) {
throw new NumberFormatException(String.format("Не могу распознать %s как число", input));
}
}
private static Color readColor() throws ColorFormatException {
System.out.format("Цвет волос(%s): ", Color.getColorsString());
var input = readString();
if (input == null || input.isEmpty()) return null;
try {
return Color.create(Integer.parseInt(input));
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
try {
return Color.create(input);
} catch (IllegalArgumentException ex) {
throw new ColorFormatException(ex.getMessage());
}
}
}
private static UnitOfMeasure readUnitOfMeasure() throws UnitOfMeasureFormatException {
System.out.format("Единица измерения(%s): ", UnitOfMeasure.getUnitOfMeasuerString());
var input = readString();
try {
return UnitOfMeasure.create(Integer.parseInt(input));
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
try {
return UnitOfMeasure.create(input);
} catch (IllegalArgumentException ex) {
throw new ColorFormatException(ex.getMessage());
}
}
}
private static Location readLocation() throws NumberFormatException {
System.out.print("Создать поле Location? (Y/n): ");
var input = readString();
if (input != null && input.equalsIgnoreCase("n")) return null;
System.out.print("Координата x локации: ");
var locationX = Float.parseFloat(readString());
System.out.print("Координата y локации: ");
var locationY = Integer.parseInt(readString());
System.out.print("Название: ");
var locationName = readString();
return new Location(locationX, locationY, locationName);
}
private static void setField(String fieldName, Runnable function) {
while (isRunning) {
if (fieldName != null) System.out.format("%s: ", fieldName);
try {
function.run();
return;
} catch (NullPointerException e) {
log("Произошла ошибка при заполнении поля\оле не может быть пустым");
} catch (Exception e) {
System.out.format("Произошла ошибка при заполнении поля%n%s%n", e.getMessage());
}
}
}
private static void setField(Runnable function) {
setField(null, function);
}
private static String readField(String message) {
System.out.print(message + ": ");
return readString();
}
public static ProductDTO readProductDTO() {
var productDTO = new ProductDTO();
var coordinates = new Coordinates();
var owner = new Person();
log("Заполните следующие поля: ");
setField("Название [не может быть пустым]", () -> productDTO.setName(readString()));
setField("Координата x [должна быть меньше 884]", () -> coordinates.setX(readLong()));
setField("Координата y [должна быть больше -427, не может быть пустой]", () -> coordinates.setY(readLong()));
setField("Цена [должна быть больше 0, не может быть пустой]", () -> productDTO.setPrice(readLong()));
setField("Номер части [не больше 82 символов, должен быть уникальным]", () -> productDTO.setPartNumber(readString()));
setField("Себестоимость [не может быть пустым]", () -> productDTO.setManufactureCost(readLong()));
setField(() -> productDTO.setUnitOfMeasure(readUnitOfMeasure()));
setField("Имя владельца [не может быть пустым]", () -> owner.setName(readString()));
setField("Данные паспорта [длина строки от 5 до 22, не может быть пустым]", () -> owner.setPassportID(readString()));
setField(() -> owner.setHairColor(readColor()));
setField(() -> owner.setLocation(readLocation()));
productDTO.setCoordinates(coordinates);
productDTO.setOwner(owner);
return productDTO;
}
public static void run() {
while (client == null) {
try {
var address = readField("Хост");
int port = Integer.parseInt(readField("Порт"));
client = new Client(address, port);
} catch (NumberFormatException e) {
log("Порт должен быть числом от 1 до 65535");
} catch (ResponseException | ConnectionErrorException e) {
log("Ошибка при подключении\опробуйте ещё раз");
}
}
log("Добро пожаловать! Для просмотра команд введите help");
while (isRunning) {
System.out.print(":");
try {
var input = scanner.nextLine().trim();
var command = CommandManager.getCommandByInput(input);
if (command.isPresent()) {
appendHistory(input);
try {
log(command.get().action(client));
} catch (CommandActionException e) {
log(e.getMessage());
}
} else {
log("Такой команды не существует. Напишите help, чтобы посмотреть список доступных команд.");
}
} catch (NoSuchElementException e) {
stopAppWithoutSaving();
}
log();
}
scanner.close();
}
}

View File

@ -0,0 +1,10 @@
package me.zinch.Lab6.Client.console;
/**
* Represents a shutdown hook that is executed when the application is stopped.
*/
public class ShutdownHook extends Thread {
public ShutdownHook() {
super(new Thread(Console::stopAppWithSaving));
}
}

View File

@ -1,4 +1,4 @@
package me.zinch.exceptions; package me.zinch.Lab6.Client.exceptions;
public class ColorFormatException extends IllegalArgumentException { public class ColorFormatException extends IllegalArgumentException {
public ColorFormatException() { public ColorFormatException() {

View File

@ -1,4 +1,4 @@
package me.zinch.exceptions; package me.zinch.Lab6.Client.exceptions;
/** /**
* Represents an exception that occurs during the execution of a command action. * Represents an exception that occurs during the execution of a command action.

View File

@ -0,0 +1,9 @@
package me.zinch.Lab6.Client.exceptions;
import java.io.IOException;
public class ConnectionErrorException extends IOException {
public ConnectionErrorException() {
super("Произошла ошибка при подключении, сервер не доступен");
}
}

View File

@ -1,4 +1,4 @@
package me.zinch.exceptions; package me.zinch.Lab6.Client.exceptions;
import java.io.IOException; import java.io.IOException;
@ -9,4 +9,8 @@ public class DbInitializationException extends IOException {
public DbInitializationException() { public DbInitializationException() {
super("Ошибка! Не удалось инициализировать БД. Проверьте, что структура БД верна."); super("Ошибка! Не удалось инициализировать БД. Проверьте, что структура БД верна.");
} }
public DbInitializationException(String message) {
super(message);
}
} }

View File

@ -1,4 +1,4 @@
package me.zinch.exceptions; package me.zinch.Lab6.Client.exceptions;
/** /**
* Represents an exception that occurs when an illegal product DTO is encountered during the creation or modification of a product. * Represents an exception that occurs when an illegal product DTO is encountered during the creation or modification of a product.

View File

@ -0,0 +1,7 @@
package me.zinch.Lab6.Client.exceptions;
public class ResponseException extends ClassNotFoundException {
public ResponseException() {
super("Ошибка при обработке данных от сервера.");
}
}

View File

@ -1,4 +1,4 @@
package me.zinch.exceptions; package me.zinch.Lab6.Client.exceptions;
public class UnitOfMeasureFormatException extends IllegalArgumentException { public class UnitOfMeasureFormatException extends IllegalArgumentException {
public UnitOfMeasureFormatException() { public UnitOfMeasureFormatException() {

View File

@ -1,4 +1,4 @@
package me.zinch.files; package me.zinch.Lab6.Client.files;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.FileInputStream; import java.io.FileInputStream;

37
Lab.Domain/pom.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Domain</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.17.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,10 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public class BodyfulMessage extends Message {
public BodyfulMessage(MessageType type, String command, Serializable body) {
this.setType(type);
this.setBody(new MessageBody(command, body));
}
}

View File

@ -0,0 +1,12 @@
package me.zinch.Lab6.Domain.dto;
public class BodylessMessage extends Message {
public BodylessMessage(MessageType type) {
this.setType(type);
}
public BodylessMessage(MessageType type, String command) {
this.setType(type);
this.setBody(command);
}
}

View File

@ -0,0 +1,32 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public abstract class Message implements Serializable {
private MessageType type;
private Serializable body;
public final MessageType getType() {
return type;
}
public final void setType(MessageType type) {
this.type = type;
}
public final Object getBody() {
return body;
}
public final void setBody(Serializable body) {
this.body = body;
}
@Override
public String toString() {
return "Message{" +
"type=" + type +
", body=" + body +
'}';
}
}

View File

@ -0,0 +1,29 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public class MessageBody implements Serializable {
private String command;
private Serializable body;
public MessageBody(String command, Serializable body) {
this.command = command;
this.body = body;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public Serializable getBody() {
return body;
}
public void setBody(Serializable body) {
this.body = body;
}
}

View File

@ -0,0 +1,11 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public enum MessageType implements Serializable {
GET,
POST,
ERROR,
OK,
HELLO
}

View File

@ -0,0 +1,31 @@
package me.zinch.Lab6.Domain.dto;
import me.zinch.Lab6.Domain.models.ProductDTO;
import java.io.Serializable;
public class UpdateBody implements Serializable {
private Long id;
private ProductDTO productDTO;
public UpdateBody(Long id, ProductDTO productDTO) {
this.id = id;
this.productDTO = productDTO;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ProductDTO getProductDTO() {
return productDTO;
}
public void setProductDTO(ProductDTO productDTO) {
this.productDTO = productDTO;
}
}

View File

@ -1,10 +1,11 @@
package me.zinch.exceptions; package me.zinch.Lab6.Domain.exceptions;
/** /**
* Represents an exception related to validation errors. * Represents an exception related to validation errors.
*/ */
public class ValidationException extends jakarta.validation.ValidationException { public class ValidationException extends jakarta.validation.ValidationException {
public ValidationException() {} public ValidationException() {
}
public ValidationException(String message) { public ValidationException(String message) {
super(message); super(message);

View File

@ -1,5 +1,6 @@
package me.zinch.models; package me.zinch.Lab6.Domain.models;
import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -7,7 +8,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Represents a color enumeration. * Represents a color enumeration.
*/ */
public enum Color { public enum Color implements Serializable {
GREEN("Зелёный"), GREEN("Зелёный"),
BLACK("Чёрный"), BLACK("Чёрный"),
BLUE("Синий"), BLUE("Синий"),
@ -20,10 +21,6 @@ public enum Color {
this.color = color; this.color = color;
} }
private String getColor() {
return color;
}
public static Color create(String input) throws IllegalArgumentException { public static Color create(String input) throws IllegalArgumentException {
for (var unit : List.of(Color.values())) { for (var unit : List.of(Color.values())) {
if (unit.getColor().equalsIgnoreCase(input)) return unit; if (unit.getColor().equalsIgnoreCase(input)) return unit;
@ -42,6 +39,10 @@ public enum Color {
.toList()); .toList());
} }
private String getColor() {
return color;
}
@Override @Override
public String toString() { public String toString() {
return "Color{" + return "Color{" +

View File

@ -0,0 +1,56 @@
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
/**
* Represents coordinates with x and y values.
*/
public class Coordinates implements Serializable {
@Max(value = 353, message = "Coordinates: Максимальное значение поля x: 353")
@JsonProperty("x")
private long x; // Максимальное значение поля: 353
@NotNull(message = "Coordinates: Поле y не может быть null")
@JsonProperty("y")
private Integer y; // Поле не может быть null
public Coordinates() {}
public Coordinates(long x, Integer y) {
this.x = x;
this.y = y;
}
public long getX() {
return x;
}
public Integer getY() {
return y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Coordinates that = (Coordinates) o;
return x == that.x && Objects.equals(y, that.y);
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Coordinates{" +
"x=" + x +
", y=" + y +
'}';
}
}

View File

@ -0,0 +1,148 @@
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Objects;
public class Dragon implements Serializable, Comparable<Dragon> {
@NotNull(message = "Dragon: Поле id не может быть null")
@Min(value = 1, message = "Dragon: Значение поля id должно быть больше 0")
@JsonProperty("id")
private Integer id; // Поле не может быть null, Значение поля должно быть больше 0, Значение этого поля должно быть уникальным, Значение этого поля должно генерироваться автоматически
@NotBlank(message = "Dragon: Строка name не может быть пустой или null")
@JsonProperty("name")
private String name; // Поле не может быть null, Строка не может быть пустой
@NotNull(message = "Dragon: Поле coordinates не может быть null")
@JsonProperty("coordinates")
private Coordinates coordinates; // Поле не может быть null
@NotNull(message = "Dragon: Поле creationDate не может быть null")
@JsonProperty("creationDate")
private LocalDateTime creationDate; // Поле не может быть null, Значение этого поля должно генерироваться автоматически
@NotNull(message = "Dragon: Поле age не может быть null")
@Min(value = 1, message = "Dragon: Значение поля age должно быть больше 0")
@JsonProperty("age")
private Integer age; // Значение поля должно быть больше 0, Поле не может быть null
@Min(value = 1, message = "Dragon: Значение поля weight должно быть больше 0")
@JsonProperty("weight")
private long weight; // Значение поля должно быть больше 0
@NotNull(message = "Dragon: Поле type не может быть null")
@JsonProperty("type")
private DragonType type; // Поле не может быть null
@NotNull(message = "Dragon: Поле character не может быть null")
@JsonProperty("character")
private DragonCharacter character; // Поле не может быть null
@JsonProperty("head")
private DragonHead head;
public Dragon() {}
public Dragon(Integer id,
String name,
Coordinates coordinates,
LocalDateTime creationDate,
Integer age,
long weight,
DragonType type,
DragonCharacter character,
DragonHead head) {
this.id = id;
this.name = name;
this.coordinates = coordinates;
this.creationDate = creationDate;
this.age = age;
this.weight = weight;
this.type = type;
this.character = character;
this.head = head;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Coordinates getCoordinates() {
return coordinates;
}
public LocalDateTime getCreationDate() {
return creationDate;
}
public Integer getAge() {
return age;
}
public long getWeight() {
return weight;
}
public DragonType getType() {
return type;
}
public DragonCharacter getCharacter() {
return character;
}
public DragonHead getHead() {
return head;
}
@Override
public String toString() {
return "Dragon{" +
"id=" + id +
", name='" + name + '\'' +
", coordinates=" + coordinates +
", creationDate=" + creationDate +
", age=" + age +
", weight=" + weight +
", type=" + type +
", character=" + character +
", head=" + head +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dragon dragon = (Dragon) o;
return weight == dragon.weight &&
Objects.equals(id, dragon.id) &&
Objects.equals(name, dragon.name) &&
Objects.equals(coordinates, dragon.coordinates) &&
Objects.equals(creationDate, dragon.creationDate) &&
Objects.equals(age, dragon.age) &&
type == dragon.type &&
character == dragon.character &&
Objects.equals(head, dragon.head);
}
@Override
public int hashCode() {
return Objects.hash(id, name, coordinates, creationDate, age, weight, type, character, head);
}
@Override
public int compareTo(Dragon other) {
return this.name.compareTo(other.name);
}
}

View File

@ -0,0 +1,9 @@
package me.zinch.Lab6.Domain.models;
public enum DragonCharacter {
CUNNING,
WISE,
EVIL,
CHAOTIC,
FICKLE
}

View File

@ -0,0 +1,40 @@
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.Objects;
public class DragonHead implements Serializable {
@JsonProperty("size")
private Float size; // Поле может быть null
public DragonHead() {}
public DragonHead(Float size) {
this.size = size;
}
public Float getSize() {
return size;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DragonHead that = (DragonHead) o;
return Objects.equals(size, that.size);
}
@Override
public int hashCode() {
return Objects.hash(size);
}
@Override
public String toString() {
return "DragonHead{" +
"size=" + size +
'}';
}
}

View File

@ -0,0 +1,7 @@
package me.zinch.Lab6.Domain.models;
public enum DragonType {
WATER,
AIR,
FIRE
}

View File

@ -1,15 +1,16 @@
package me.zinch.models; package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import me.zinch.validator.NotEmpty; import me.zinch.Lab6.Domain.validator.NotEmpty;
import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
/** /**
* Represents a location with x and y coordinates and a name. * Represents a location with x and y coordinates and a name.
*/ */
public class Location { public class Location implements Serializable {
@JsonProperty("x") @JsonProperty("x")
private float x; private float x;
@ -21,7 +22,8 @@ public class Location {
@JsonProperty("name") @JsonProperty("name")
private String name; //Строка не может быть пустой, Поле может быть null private String name; //Строка не может быть пустой, Поле может быть null
public Location() {} public Location() {
}
public Location(float x, Integer y, String name) { public Location(float x, Integer y, String name) {
this.x = x; this.x = x;

View File

@ -1,16 +1,18 @@
package me.zinch.models; package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
/** /**
* Represents a person with a name, passport ID, hair color, and location. * Represents a person with a name, passport ID, hair color, and location.
*/ */
public class Person { public class Person implements Serializable {
@NotBlank(message = "Person: Имя персоны не может быть пустым или null") @NotBlank(message = "Person: Имя персоны не может быть пустым или null")
@JsonProperty("name") @JsonProperty("name")
private String name; //Поле не может быть null, Строка не может быть пустой private String name; //Поле не может быть null, Строка не может быть пустой
@ -26,7 +28,8 @@ public class Person {
@JsonProperty("location") @JsonProperty("location")
private Location location; //Поле может быть null private Location location; //Поле может быть null
public Person() {} public Person() {
}
public Person(String name, String passportID, Color hairColor, Location location) { public Person(String name, String passportID, Color hairColor, Location location) {
this.name = name; this.name = name;
@ -36,10 +39,14 @@ public class Person {
} }
public void setName(String name) { public void setName(String name) {
if (name == null || name.isEmpty()) throw new ValidationException("Имя персоны не может быть пустым или null");
this.name = name; this.name = name;
} }
public void setPassportID(String passportID) { public void setPassportID(String passportID) {
if (passportID == null) throw new ValidationException("Поле passportID не может быть null");
if (passportID.length() < 5 || passportID.length() > 22)
throw new ValidationException("Длина строки passportID должна быть не меньше 5 и не должна быть больше 22");
this.passportID = passportID; this.passportID = passportID;
} }

View File

@ -1,19 +1,20 @@
package me.zinch.models; package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import me.zinch.validator.NotEmpty; import me.zinch.Lab6.Domain.validator.NotEmpty;
import java.io.Serializable;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Objects; import java.util.Objects;
/** /**
* Represents a product with various attributes such as ID, name, coordinates, creation date, price, part number, manufacture cost, unit of measure, and owner. * Represents a product with various attributes such as ID, name, coordinates, creation date, price, part number, manufacture cost, unit of measure, and owner.
*/ */
public class Product { public class Product implements Serializable, Comparable<Product> {
@NotNull(message = "Product: Поле id не может быть null") @NotNull(message = "Product: Поле id не может быть null")
@Min(value = 1, message = "Product: Значение поля id должно быть больше 0") @Min(value = 1, message = "Product: Значение поля id должно быть больше 0")
@JsonProperty("id") @JsonProperty("id")
@ -52,18 +53,19 @@ public class Product {
@JsonProperty("owner") @JsonProperty("owner")
private Person owner; //Поле не может быть null private Person owner; //Поле не может быть null
public Product() {} public Product() {
}
public Product (Long id, public Product(Long id,
String name, String name,
Coordinates coordinates, Coordinates coordinates,
ZonedDateTime creationDate, ZonedDateTime creationDate,
Long price, Long price,
String partNumber, String partNumber,
long manufactureCost, long manufactureCost,
UnitOfMeasure unitOfMeasure, UnitOfMeasure unitOfMeasure,
Person owner Person owner
) { ) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.coordinates = coordinates; this.coordinates = coordinates;
@ -138,4 +140,9 @@ public class Product {
public int hashCode() { public int hashCode() {
return Objects.hash(id, name, coordinates, creationDate, price, partNumber, manufactureCost, unitOfMeasure, owner); return Objects.hash(id, name, coordinates, creationDate, price, partNumber, manufactureCost, unitOfMeasure, owner);
} }
@Override
public int compareTo(Product other) {
return this.name.compareTo(other.name);
}
} }

View File

@ -1,20 +1,20 @@
package me.zinch.models; package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.validator.NotEmpty;
import me.zinch.validator.NotEmpty; import java.io.Serializable;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
/** /**
* Represents a Data Transfer Object (DTO) for a product with attributes such as name, coordinates, price, part number, manufacture cost, unit of measure, and owner. * Represents a Data Transfer Object (DTO) for a product with attributes such as name, coordinates, price, part number, manufacture cost, unit of measure, and owner.
*/ */
public class ProductDTO { public class ProductDTO implements Serializable {
@NotBlank(message = "ProductDTO: Строка name не может быть пустой или null") @NotBlank(message = "ProductDTO: Строка name не может быть пустой или null")
@JsonProperty("name") @JsonProperty("name")
private String name; //Поле не может быть null, Строка не может быть пустой private String name; //Поле не может быть null, Строка не может быть пустой
@ -24,7 +24,7 @@ public class ProductDTO {
private Coordinates coordinates; //Поле не может быть null private Coordinates coordinates; //Поле не может быть null
@NotNull(message = "ProductDTO: Поле price не может быть null") @NotNull(message = "ProductDTO: Поле price не может быть null")
@Min(value = 1, message = "Поле price не может быть null") @Min(value = 1, message = "ProductDTO: Значение поля price должно быть больше 0")
@JsonProperty("price") @JsonProperty("price")
private Long price; //Поле не может быть null, Значение поля должно быть больше 0 private Long price; //Поле не может быть null, Значение поля должно быть больше 0
@ -44,7 +44,8 @@ public class ProductDTO {
@JsonProperty("owner") @JsonProperty("owner")
private Person owner; //Поле не может быть null private Person owner; //Поле не может быть null
public ProductDTO() {} public ProductDTO() {
}
public ProductDTO(String name, Coordinates coordinates, Long price, String partNumber, long manufactureCost, UnitOfMeasure unitOfMeasure, Person owner) { public ProductDTO(String name, Coordinates coordinates, Long price, String partNumber, long manufactureCost, UnitOfMeasure unitOfMeasure, Person owner) {
this.name = name; this.name = name;
@ -72,55 +73,63 @@ public class ProductDTO {
return name; return name;
} }
public void setName(String name) {
if (name == null || name.isEmpty()) throw new ValidationException("Строка name не может быть пустой или null");
this.name = name;
}
public Coordinates getCoordinates() { public Coordinates getCoordinates() {
return coordinates; return coordinates;
} }
public void setCoordinates(Coordinates coordinates) {
if (coordinates == null) throw new ValidationException("Поле coordinates не может быть null");
this.coordinates = coordinates;
}
public Long getPrice() { public Long getPrice() {
return price; return price;
} }
public void setPrice(Long price) {
if (price == null) throw new ValidationException("Поле price не может быть null");
if (price <= 0) throw new ValidationException("Поле price не может быть null");
this.price = price;
}
public String getPartNumber() { public String getPartNumber() {
return partNumber; return partNumber;
} }
public void setPartNumber(String partNumber) {
if (partNumber.isEmpty()) throw new ValidationException("Строка partNumber не может быть пустой");
if (partNumber.length() > 82) throw new ValidationException("Длина строки partNumber не должна быть больше 82");
this.partNumber = partNumber;
}
public long getManufactureCost() { public long getManufactureCost() {
return manufactureCost; return manufactureCost;
} }
public void setManufactureCost(long manufactureCost) {
this.manufactureCost = manufactureCost;
}
public UnitOfMeasure getUnitOfMeasure() { public UnitOfMeasure getUnitOfMeasure() {
return unitOfMeasure; return unitOfMeasure;
} }
public void setUnitOfMeasure(UnitOfMeasure unitOfMeasure) {
if (unitOfMeasure == null) throw new ValidationException("Поле unitOfMeasure не может быть null");
this.unitOfMeasure = unitOfMeasure;
}
public Person getOwner() { public Person getOwner() {
return owner; return owner;
} }
public void setName(String name) {
this.name = name;
}
public void setCoordinates(Coordinates coordinates) {
this.coordinates = coordinates;
}
public void setPrice(Long price) {
this.price = price;
}
public void setPartNumber(String partNumber) {
this.partNumber = partNumber;
}
public void setManufactureCost(long manufactureCost) {
this.manufactureCost = manufactureCost;
}
public void setUnitOfMeasure(UnitOfMeasure unitOfMeasure) {
this.unitOfMeasure = unitOfMeasure;
}
public void setOwner(Person owner) { public void setOwner(Person owner) {
if (owner == null) throw new ValidationException("Поле owner не может быть null");
this.owner = owner; this.owner = owner;
} }
} }

View File

@ -1,5 +1,6 @@
package me.zinch.models; package me.zinch.Lab6.Domain.models;
import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -7,7 +8,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Represents a unit of measure enumeration. * Represents a unit of measure enumeration.
*/ */
public enum UnitOfMeasure { public enum UnitOfMeasure implements Serializable {
KILOGRAMS("Килограммы"), KILOGRAMS("Килограммы"),
CENTIMETERS("Сантиметры"), CENTIMETERS("Сантиметры"),
GRAMS("Граммы"); GRAMS("Граммы");
@ -18,10 +19,6 @@ public enum UnitOfMeasure {
this.unitOfMeasure = unitOfMeasure; this.unitOfMeasure = unitOfMeasure;
} }
public String getUnitOfMeasure() {
return unitOfMeasure;
}
public static UnitOfMeasure create(String input) throws IllegalArgumentException { public static UnitOfMeasure create(String input) throws IllegalArgumentException {
for (var unit : List.of(UnitOfMeasure.values())) { for (var unit : List.of(UnitOfMeasure.values())) {
if (unit.getUnitOfMeasure().equalsIgnoreCase(input)) return unit; if (unit.getUnitOfMeasure().equalsIgnoreCase(input)) return unit;
@ -40,6 +37,9 @@ public enum UnitOfMeasure {
.toList()); .toList());
} }
public String getUnitOfMeasure() {
return unitOfMeasure;
}
@Override @Override
public String toString() { public String toString() {

View File

@ -1,4 +1,4 @@
package me.zinch.validator; package me.zinch.Lab6.Domain.validator;
import jakarta.validation.Constraint; import jakarta.validation.Constraint;
import jakarta.validation.Payload; import jakarta.validation.Payload;
@ -16,6 +16,8 @@ import java.lang.annotation.Target;
@Constraint(validatedBy = NotEmptyValidator.class) @Constraint(validatedBy = NotEmptyValidator.class)
public @interface NotEmpty { public @interface NotEmpty {
String message() default "Value must not be blank"; String message() default "Value must not be blank";
Class<?>[] groups() default {}; Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {}; Class<? extends Payload>[] payload() default {};
} }

View File

@ -1,4 +1,4 @@
package me.zinch.validator; package me.zinch.Lab6.Domain.validator;
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;

View File

@ -1,12 +1,13 @@
package me.zinch.validator; package me.zinch.Lab6.Domain.validator;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import jakarta.validation.ValidationException; import me.zinch.Lab6.Domain.exceptions.ValidationException;
import java.util.Set; import java.util.Set;
/** /**
* Represents the result of a validation process. * Represents the result of a validation process.
*
* @param <T> the type of object being validated * @param <T> the type of object being validated
*/ */
public class ValidateResult<T> { public class ValidateResult<T> {

View File

@ -1,12 +1,13 @@
package me.zinch.validator; package me.zinch.Lab6.Domain.validator;
import jakarta.validation.Validation; import jakarta.validation.Validation;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory; import jakarta.validation.ValidatorFactory;
import me.zinch.models.Product; import me.zinch.Lab6.Domain.models.Product;
import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidator;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
@ -30,7 +31,7 @@ public class Validators {
public static List<ValidateResult<Product>> validateProductList(List<Product> db) { public static List<ValidateResult<Product>> validateProductList(List<Product> db) {
var ids = db.stream().map(Product::getId).toList(); var ids = db.stream().map(Product::getId).toList();
var partNumbers = db.stream().map(Product::getPartNumber).toList(); var partNumbers = db.stream().map(Product::getPartNumber).filter(Objects::nonNull).toList();
var validations = new java.util.ArrayList<>(db.stream().map(Validators::validateObject).toList()); var validations = new java.util.ArrayList<>(db.stream().map(Validators::validateObject).toList());
if (ids.size() != Set.copyOf(ids).size()) { if (ids.size() != Set.copyOf(ids).size()) {
validations.add(new ValidateResult<>("Поле id должно быть уникальным")); validations.add(new ValidateResult<>("Поле id должно быть уникальным"));

View File

@ -0,0 +1,23 @@
package me.zinch.Lab6.Domain.wrapper;
import me.zinch.Lab6.Domain.models.Dragon;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import me.zinch.Lab6.Domain.models.DragonType;
import java.util.List;
import java.util.Map;
public interface IStorage {
Dragon getDragonById(Integer id);
Dragon addDragon(Dragon dragon);
Dragon updateDragon(Integer id, Dragon dragon);
Dragon removeDragon(Integer id);
String getInfo();
void clear();
Dragon getHead();
Dragon removeHead();
Dragon getMaxByWeight();
Map<DragonType, Long> groupCountingByType();
List<Dragon> filterLessThanCharacter(DragonCharacter character);
List<Dragon> toList();
}

116
Lab.Server/pom.xml Normal file
View File

@ -0,0 +1,116 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Server</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Lab6-Server</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>5.0.0-M1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Domain</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.17.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline</artifactId>
<version>3.26.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
<mainClass>me.zinch.Lab6.Server.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,14 @@
package me.zinch.Lab6.Server;
import me.zinch.Lab6.Server.console.Console;
import java.io.IOException;
/**
* Main class for running the application.
*/
public class App {
public static void main(String[] args) throws IOException {
Console.run();
}
}

View File

@ -0,0 +1,97 @@
package me.zinch.Lab6.Server;
import me.zinch.Lab6.Domain.dto.Message;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.files.DbController;
import me.zinch.Lab6.Server.modules.CommandModule;
import me.zinch.Lab6.Server.modules.ConnectionModule;
import me.zinch.Lab6.Server.modules.RequestModule;
import me.zinch.Lab6.Server.modules.ResponseModule;
import me.zinch.Lab6.Server.wrapper.ProductCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class Server {
private static final Logger log = LoggerFactory.getLogger(Server.class);
private final Selector selector;
private final Map<SocketChannel, Message> clientMessages;
private final ConnectionModule connectionModule;
private final RequestModule requestModule;
private final ResponseModule responseModule;
private final CommandModule commandModule;
private final IStorage storage;
private boolean isRunning;
public Server(int port, IStorage collection) throws IOException {
selector = Selector.open();
clientMessages = new HashMap<>();
if (collection == null) {
collection = new ProductCollection(new ArrayList<>());
}
storage = collection;
commandModule = new CommandModule(storage);
connectionModule = new ConnectionModule(port, selector);
requestModule = new RequestModule(clientMessages);
responseModule = new ResponseModule(clientMessages, commandModule);
isRunning = true;
log.info("Server initialized on port {}", port);
}
public void run() throws IOException {
run(() -> {});
}
public void run(Runnable middleware) throws IOException {
log.info("Server is running");
while (isRunning) {
selector.select(this::handleKey);
middleware.run();
}
}
private void handleKey(SelectionKey key) {
try {
if (!key.isValid()) {
return;
}
if (key.isAcceptable()) {
connectionModule.handleAccept(key);
}
if (key.isReadable()) {
requestModule.handleRead(key);
}
if (key.isWritable()) {
responseModule.handleWrite(key);
}
} catch (IOException | ClassNotFoundException e) {
log.error("Error handling key: {}", e.getMessage());
key.cancel();
try {
key.channel().close();
} catch (IOException ex) {
log.error("Error closing channel: {}", ex.getMessage());
}
}
}
public void stop() throws IOException {
isRunning = false;
connectionModule.close();
selector.close();
DbController.saveDb(storage.toList());
log.info("Server stopped");
}
}

View File

@ -0,0 +1,37 @@
package me.zinch.Lab6.Server;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.configs.ServerConfig;
import me.zinch.Lab6.Server.wrapper.ProductCollection;
import java.io.IOException;
import java.util.ArrayList;
public class ServerBuilder {
private ServerConfig serverConfig = new ServerConfig();
public ServerBuilder() {
serverConfig.setPort(3000);
serverConfig.setCollection(new ProductCollection(new ArrayList<>()));
}
public ServerBuilder(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
}
public ServerBuilder setPort(int port) {
if (port < 1 || port > 65535) throw new IllegalArgumentException("Port must be between 1 and 65535");
serverConfig.setPort(port);
return new ServerBuilder(serverConfig);
}
public ServerBuilder setCollection(IStorage collection) {
serverConfig.setCollection(collection);
return new ServerBuilder(serverConfig);
}
public Server build() throws IOException {
return new Server(serverConfig.getPort(),
serverConfig.getCollection());
}
}

View File

@ -0,0 +1,31 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
/**
* The Add class represents a command to add a new element to a collection.
* It extends the Command class.
*/
public class Add extends PostCommand {
public Add() {
super("add {element}", "добавить новый элемент в коллекцию", Pattern.compile("^add"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
var productDTO = (ProductDTO) object;
try {
Validators.validateObject(productDTO).throwIfNotValid();
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
} catch (ValidationException e) {
throw new CommandActionException(String.format("Произошла ошибка при добавлении%n%s", e.getMessage()));
}
}
}

View File

@ -0,0 +1,38 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
/**
* The AddIfMax class represents a command to add a new element to a collection if its price value exceeds the maximum price value in the collection.
* It extends the Command class.
*/
public class AddIfMax extends PostCommand {
public AddIfMax() {
super("add_if_max {element}", "добавить новый элемент в коллекцию, если его значение цены превышает значение наибольшей цены этой коллекции", Pattern.compile("^add_if_max .+"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
try {
var productDTO = (ProductDTO) object;
try {
Validators.validateObject(productDTO).throwIfNotValid();
if (productCollection.getMaxPrice() == null || productCollection.getMaxPrice() < productDTO.getPrice()) {
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
}
return "Продукт не подходит под условие";
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -0,0 +1,38 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
/**
* The AddIfMin class represents a command to add a new element to a collection if its price is lower than the lowest price in the collection.
* It extends the Command class.
*/
public class AddIfMin extends PostCommand {
public AddIfMin() {
super("add_if_min {element}", "добавить новый элемент в коллекцию, если его значение цены меньше, чем у наименьшей цены этой коллекции", Pattern.compile("^add_if_min .+"));
}
@Override
public String action(IStorage productCollection, Object object) {
try {
var productDTO = (ProductDTO) object;
try {
Validators.validateObject(productDTO).throwIfNotValid();
if (productCollection.getMinPrice() == null || productCollection.getMinPrice() > productDTO.getPrice()) {
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
}
return "Продукт не подходит под условие";
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -1,18 +1,18 @@
package me.zinch.commands; package me.zinch.Lab6.Server.commands;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Domain.wrapper.IStorage;
/** /**
* The Clear class represents a command to clear a collection. * The Clear class represents a command to clear a collection.
* It extends the Command class. * It extends the Command class.
*/ */
public class Clear extends Command { public class Clear extends GetCommand {
public Clear() { public Clear() {
super("clear", "очистить коллекцию"); super("clear", "очистить коллекцию");
} }
@Override @Override
public String action(ProductCollection productCollection) { public String action(IStorage productCollection) {
productCollection.clear(); productCollection.clear();
return "Коллекция была очищена"; return "Коллекция была очищена";
} }

View File

@ -1,7 +1,4 @@
package me.zinch.commands; package me.zinch.Lab6.Server.commands;
import me.zinch.exceptions.CommandActionException;
import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -40,6 +37,4 @@ public abstract class Command {
public final boolean checkPattern(String input) { public final boolean checkPattern(String input) {
return pattern.matcher(input).matches(); return pattern.matcher(input).matches();
} }
public abstract String action(ProductCollection productCollection) throws CommandActionException;
} }

View File

@ -1,4 +1,4 @@
package me.zinch.commands; package me.zinch.Lab6.Server.commands;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -10,6 +10,27 @@ import java.util.Optional;
public class CommandManager { public class CommandManager {
private static final List<Command> commandList = new ArrayList<>(); private static final List<Command> commandList = new ArrayList<>();
static {
registerCommand(new Show());
registerCommand(new Exit());
registerCommand(new Info());
registerCommand(new Clear());
registerCommand(new PrintAscending());
registerCommand(new PrintUniqueManufactureCost());
registerCommand(new FilterContainsName());
registerCommand(new Add());
registerCommand(new IsIdExists());
registerCommand(new Update());
registerCommand(new Remove());
registerCommand(new AddIfMax());
registerCommand(new AddIfMin());
registerCommand(new RemoveLower());
registerCommand(new Save());
registerCommand(new GetProduct());
registerCommand(new Help());
}
public static void registerCommand(Command command) { public static void registerCommand(Command command) {
commandList.add(command); commandList.add(command);
} }
@ -24,24 +45,4 @@ public class CommandManager {
public static Optional<Command> getCommandByInput(String input) { public static Optional<Command> getCommandByInput(String input) {
return commandList.stream().filter(command -> command.checkPattern(input)).findFirst(); return commandList.stream().filter(command -> command.checkPattern(input)).findFirst();
} }
static {
registerCommand(new Show());
registerCommand(new Exit());
registerCommand(new Info());
registerCommand(new Clear());
registerCommand(new PrintAscending());
registerCommand(new PrintUniqueManufactureCost());
registerCommand(new FilterContainsName());
registerCommand(new Add());
registerCommand(new Update());
registerCommand(new Remove());
registerCommand(new AddIfMax());
registerCommand(new AddIfMin());
registerCommand(new RemoveLower());
registerCommand(new Save());
registerCommand(new ExecuteScript());
registerCommand(new Help());
}
} }

View File

@ -0,0 +1,19 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.console.Console;
/**
* A command to exit the program without saving to a file.
*/
public class Exit extends GetCommand {
public Exit() {
super("exit", "terminate the program (without saving to a file)");
}
@Override
public String action(IStorage productCollection) {
Console.stopApp();
return "";
}
}

View File

@ -0,0 +1,21 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import java.util.regex.Pattern;
/**
* A command to filter elements whose 'name' field contains the specified substring.
*/
public class FilterContainsName extends PostCommand {
public FilterContainsName() {
super("filter_contains_name name", "вывести элементы, значение поля name которых содержит заданную подстроку", Pattern.compile("^filter_contains_name .+"));
}
@Override
public String action(IStorage productCollection, Object object) {
String name = object.toString();
var result = productCollection.filterContainsName(name);
return result.isEmpty() ? "Таких элементов не найдено" : result;
}
}

View File

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public abstract class GetCommand extends Command {
public GetCommand(String name, String description, Pattern pattern) {
super(name, description, pattern);
}
public GetCommand(String name, String description) {
super(name, description);
}
public abstract String action(IStorage productCollection) throws CommandActionException;
}

View File

@ -0,0 +1,19 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public class GetProduct extends PostCommand {
public GetProduct() {
super("get_product_by_id", "", Pattern.compile("^get_product_by_id +."));
}
@Override
public String action(IStorage productCollection, Object obj) throws CommandActionException {
if (!productCollection.isProductIdExists((Long) obj))
throw new CommandActionException("Продукт с таким ID не существует");
return productCollection.getProductById((Long) obj).toString();
}
}

View File

@ -0,0 +1,17 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* A command to display help for available commands.
*/
public class Help extends GetCommand {
public Help() {
super("help", "display help for available commands");
}
@Override
public String action(IStorage productCollection) {
return SystemCommandManager.getRegisteredCommand();
}
}

View File

@ -1,17 +1,17 @@
package me.zinch.commands; package me.zinch.Lab6.Server.commands;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Domain.wrapper.IStorage;
/** /**
* A command to display information about the collection (type, initialization date, number of elements, etc.) to the standard output stream. * A command to display information about the collection (type, initialization date, number of elements, etc.) to the standard output stream.
*/ */
public class Info extends Command { public class Info extends GetCommand {
public Info() { public Info() {
super("info", "вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)"); super("info", "вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)");
} }
@Override @Override
public String action(ProductCollection productCollection) { public String action(IStorage productCollection) {
return productCollection.getInfo(); return productCollection.getInfo();
} }
} }

View File

@ -0,0 +1,19 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public class IsIdExists extends PostCommand {
public IsIdExists() {
super("is_id_exists", "", Pattern.compile("^is_id_exists +."));
}
@Override
public String action(IStorage productCollection, Object obj) throws CommandActionException {
if (!productCollection.isProductIdExists((Long) obj))
throw new CommandActionException("Продукт с таким ID не существует");
return "Exists";
}
}

View File

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public abstract class PostCommand extends Command {
public PostCommand(String name, String description, Pattern pattern) {
super(name, description, pattern);
}
public PostCommand(String name, String description) {
super(name, description);
}
public abstract String action(IStorage productCollection, Object obj) throws CommandActionException;
}

View File

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* A command to print the elements of the collection in ascending order.
*/
public class PrintAscending extends GetCommand {
public PrintAscending() {
super("print_ascending", "вывести элементы коллекции в порядке возрастания");
}
@Override
public String action(IStorage productCollection) {
var result = productCollection.toString();
return result.isEmpty() ? "Коллекция пуста" : result;
}
}

View File

@ -1,17 +1,18 @@
package me.zinch.commands; package me.zinch.Lab6.Server.commands;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Domain.wrapper.IStorage;
/** /**
* A command to print the unique values of the 'manufactureCost' field of all elements in the collection. * A command to print the unique values of the 'manufactureCost' field of all elements in the collection.
*/ */
public class PrintUniqueManufactureCost extends Command { public class PrintUniqueManufactureCost extends GetCommand {
public PrintUniqueManufactureCost() { public PrintUniqueManufactureCost() {
super("print_unique_manufacture_cost", "вывести уникальные значения поля manufactureCost всех элементов в коллекции"); super("print_unique_manufacture_cost", "вывести уникальные значения поля manufactureCost всех элементов в коллекции");
} }
@Override @Override
public String action(ProductCollection productCollection) { public String action(IStorage productCollection) {
return productCollection.getUniqueManufactureCost(); var result = productCollection.getUniqueManufactureCost();
return result.isEmpty() ? "Коллекция пуста" : result;
} }
} }

View File

@ -0,0 +1,29 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
/**
* A command to remove an element from the collection by its id.
*/
public class Remove extends PostCommand {
public Remove() {
super("remove_by_id id", "удалить элемент из коллекции по его id", Pattern.compile("^remove_by_id .+"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
try {
Long id = (Long) object;
if (productCollection.isProductIdExists(id)) {
var product = productCollection.removeProduct(id);
return String.format("Продукт %s был удалён", product.getName());
}
return "Продукта с таким id не существует";
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -0,0 +1,25 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import java.util.regex.Pattern;
/**
* A command to remove all elements from the collection that have ids lower than the specified id.
*/
public class RemoveLower extends PostCommand {
public RemoveLower() {
super("remove_lower id", "удалить из коллекции все элементы, меньшие, чем заданный по id", Pattern.compile("^remove_lower .+"));
}
@Override
public String action(IStorage productCollection, Object object) {
try {
Long id = (Long) object;
var size = productCollection.removeLover(id);
return String.format("Было удалено %d продуктов", size);
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -1,21 +1,21 @@
package me.zinch.commands; package me.zinch.Lab6.Server.commands;
import me.zinch.exceptions.CommandActionException; import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.files.DbController; import me.zinch.Lab6.Server.exceptions.CommandActionException;
import me.zinch.wrapper.ProductCollection; import me.zinch.Lab6.Server.files.DbController;
import java.io.IOException; import java.io.IOException;
/** /**
* A command to save the collection to a file. * A command to save the collection to a file.
*/ */
public class Save extends Command { public class Save extends GetCommand {
public Save() { public Save() {
super("save", "сохранить коллекцию в файл"); super("save", "сохранить коллекцию в файл");
} }
@Override @Override
public String action(ProductCollection productCollection) throws CommandActionException { public String action(IStorage productCollection) throws CommandActionException {
try { try {
DbController.saveDb(productCollection.toList()); DbController.saveDb(productCollection.toList());
return "Коллекция была сохранена"; return "Коллекция была сохранена";

View File

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* A command to display all elements of the collection in string representation to the standard output stream.
*/
public class Show extends GetCommand {
public Show() {
super("show", "вывести в стандартный поток вывода все элементы коллекции в строковом представлении");
}
@Override
public String action(IStorage productCollection) {
var result = productCollection.toString();
return result.isEmpty() ? "Коллекция пуста" : result;
}
}

View File

@ -0,0 +1,34 @@
package me.zinch.Lab6.Server.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Manages the registration and retrieval of commands.
*/
public class SystemCommandManager {
private static final List<Command> commandList = new ArrayList<>();
static {
registerCommand(new Exit());
registerCommand(new Save());
registerCommand(new Help());
}
public static void registerCommand(Command command) {
commandList.add(command);
}
public static String getRegisteredCommand() {
return String.join("\n", commandList
.stream()
.map(command -> String.format("%s - %s", command.getName(), command.getDescription()))
.toList());
}
public static Optional<Command> getCommandByInput(String input) {
return commandList.stream().filter(command -> command.checkPattern(input)).findFirst();
}
}

View File

@ -0,0 +1,17 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* Represents an unknown command.
*/
public class UnknownCommand extends GetCommand {
public UnknownCommand() {
super("", "");
}
@Override
public String action(IStorage productCollection) {
return "Команда не распознана.";
}
}

View File

@ -0,0 +1,40 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.dto.MessageBody;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
/**
* A command to update the value of an element in the collection, whose id is specified.
*/
public class Update extends PostCommand {
public Update() {
super("update id {element}", "обновить значение элемента коллекции, id которого равен заданному", Pattern.compile("^update .+"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
try {
var body = (MessageBody) object;
var id = Long.parseLong(body.getCommand().split(" ")[1]);
if (productCollection.isProductIdExists(id)) {
var productDTO = (ProductDTO) body.getBody();
try {
Validators.validateObject(productDTO).throwIfNotValid();
var product = productCollection.updateProduct(id, productDTO);
return String.format("Продукт %s был изменён", product.getName());
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
}
return "Продукта с таким id не существует";
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -0,0 +1,24 @@
package me.zinch.Lab6.Server.configs;
import me.zinch.Lab6.Domain.wrapper.IStorage;
public class ServerConfig {
private Integer port;
private IStorage collection;
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public IStorage getCollection() {
return collection;
}
public void setCollection(IStorage collection) {
this.collection = collection;
}
}

View File

@ -0,0 +1,86 @@
package me.zinch.Lab6.Server.console;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.ServerBuilder;
import me.zinch.Lab6.Server.commands.GetCommand;
import me.zinch.Lab6.Server.commands.SystemCommandManager;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import me.zinch.Lab6.Server.files.DbController;
import me.zinch.Lab6.Server.wrapper.ProductCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* Console class provides methods for interacting with the console, reading user input, and managing application flow.
* This class includes methods for appending input history, stopping the application, reading product DTO from the console, and running the application loop.
*/
public class Console {
private static final Scanner scanner = new Scanner(System.in);
private static final Logger log = LoggerFactory.getLogger(Console.class);
private static boolean isRunning = true;
private static boolean isExitMessageShowed = false;
private static IStorage collection;
public static void stopApp() {
showExitMessage();
isRunning = false;
System.exit(0);
}
public static void showExitMessage() {
if (!isExitMessageShowed) log.info("Have a nice day!");
isExitMessageShowed = true;
}
public static void run() throws IOException, ValidationException {
collection = new ProductCollection(DbController.loadDb());
var server = new ServerBuilder()
.setPort(3000)
.setCollection(collection)
.build();
Console.handleInput();
server.run();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
Console.stopApp();
try {
server.stop();
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
}
public static void handleInput() {
new Thread(() -> {
while (isRunning) {
try {
var input = scanner.nextLine().trim();
var command = SystemCommandManager.getCommandByInput(input);
if (command.isPresent()) {
var action = (GetCommand) command.get();
try {
log.info(action.action(collection));
} catch (CommandActionException e) {
log.info(e.getMessage());
}
} else {
log.info("This command does not exist. Write help to see a list of available commands.");
}
} catch (NoSuchElementException e) {
stopApp();
}
}
scanner.close();
}).start();
}
}

View File

@ -1,10 +1,10 @@
package me.zinch.console; package me.zinch.Lab6.Server.console;
/** /**
* Represents a shutdown hook that is executed when the application is stopped. * Represents a shutdown hook that is executed when the application is stopped.
*/ */
public class ShutdownHook extends Thread { public class ShutdownHook extends Thread {
public ShutdownHook() { public ShutdownHook() {
super(new Thread(Console::stopApp)); super();
} }
} }

View File

@ -0,0 +1,11 @@
package me.zinch.Lab6.Server.exceptions;
public class ColorFormatException extends IllegalArgumentException {
public ColorFormatException() {
super();
}
public ColorFormatException(String message) {
super(message);
}
}

View File

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.exceptions;
/**
* Represents an exception that occurs during the execution of a command action.
*/
public class CommandActionException extends RuntimeException {
public CommandActionException() {
super("Ошибка во время выполнения команды");
}
public CommandActionException(String message) {
super(message);
}
public CommandActionException(Throwable e) {
super(e);
}
}

View File

@ -0,0 +1,16 @@
package me.zinch.Lab6.Server.exceptions;
import java.io.IOException;
/**
* Represents an exception that occurs when the database initialization fails.
*/
public class DbInitializationException extends IOException {
public DbInitializationException() {
super("Ошибка! Не удалось инициализировать БД. Проверьте, что структура БД верна.");
}
public DbInitializationException(String message) {
super(message);
}
}

View File

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.exceptions;
/**
* Represents an exception that occurs when an illegal product DTO is encountered during the creation or modification of a product.
*/
public class IllegalProductDtoException extends CommandActionException {
public IllegalProductDtoException() {
super("Ошибка при создании или изменении продукта. Были введены некорректные значения.");
}
public IllegalProductDtoException(String message) {
super(message);
}
public IllegalProductDtoException(Throwable e) {
super(e);
}
}

View File

@ -0,0 +1,11 @@
package me.zinch.Lab6.Server.exceptions;
public class UnitOfMeasureFormatException extends IllegalArgumentException {
public UnitOfMeasureFormatException() {
super();
}
public UnitOfMeasureFormatException(String message) {
super(message);
}
}

View File

@ -0,0 +1,80 @@
package me.zinch.Lab6.Server.files;
import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;
import com.opencsv.exceptions.CsvValidationException;
import me.zinch.Lab6.Domain.models.Coordinates;
import me.zinch.Lab6.Domain.models.Dragon;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import me.zinch.Lab6.Domain.models.DragonHead;
import me.zinch.Lab6.Domain.models.DragonType;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class CsvController {
private static final String[] HEADERS = {
"id", "name", "coordinates_x", "coordinates_y", "creation_date",
"age", "weight", "type", "character", "head_size"
};
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
public static List<Dragon> loadFromCsv(String filename) throws IOException {
List<Dragon> dragons = new ArrayList<>();
try (CSVReader reader = new CSVReader(new FileReader(filename))) {
// Skip headers
reader.readNext();
String[] line;
while ((line = reader.readNext()) != null) {
dragons.add(parseDragon(line));
}
} catch (CsvValidationException e) {
throw new IOException("Error reading CSV file: " + e.getMessage());
}
return dragons;
}
public static void saveToCsv(List<Dragon> dragons, String filename) throws IOException {
try (CSVWriter writer = new CSVWriter(new FileWriter(filename))) {
writer.writeNext(HEADERS);
for (Dragon dragon : dragons) {
writer.writeNext(new String[]{
String.valueOf(dragon.getId()),
dragon.getName(),
String.valueOf(dragon.getCoordinates().getX()),
String.valueOf(dragon.getCoordinates().getY()),
dragon.getCreationDate().format(DATE_FORMATTER),
String.valueOf(dragon.getAge()),
String.valueOf(dragon.getWeight()),
dragon.getType().name(),
dragon.getCharacter().name(),
dragon.getHead() != null ? String.valueOf(dragon.getHead().getSize()) : ""
});
}
}
}
private static Dragon parseDragon(String[] fields) {
return new Dragon(
Integer.parseInt(fields[0]),
fields[1],
new Coordinates(
Long.parseLong(fields[2]),
Integer.parseInt(fields[3])
),
LocalDateTime.parse(fields[4], DATE_FORMATTER),
Integer.parseInt(fields[5]),
Long.parseLong(fields[6]),
DragonType.valueOf(fields[7]),
DragonCharacter.valueOf(fields[8]),
fields[9].isEmpty() ? null : new DragonHead(Float.parseFloat(fields[9]))
);
}
}

View File

@ -1,20 +1,18 @@
package me.zinch.files; package me.zinch.Lab6.Server.files;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import me.zinch.exceptions.DbInitializationException; import me.zinch.Lab6.Domain.models.Product;
import me.zinch.models.Product;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -27,10 +25,32 @@ public class DbController {
static { static {
Map<String, String> env = System.getenv(); Map<String, String> env = System.getenv();
path = env.getOrDefault("DB_FILE", ""); path = env.getOrDefault("DB_FILE", "db.xml");
xmlMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); xmlMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
} }
public static List<Product> loadDb() {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
List<Product> collection = xmlMapper.readValue(bufferedInputStream, Products.class).getProducts();
bufferedInputStream.close();
if (collection == null) return new ArrayList<>();
return collection;
} catch (IOException e) {
return new ArrayList<>();
}
}
public static void saveDb(List<Product> list) throws IOException {
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(path));
xmlMapper.writeValue(outputStreamWriter, new Products(list));
outputStreamWriter.close();
} catch (IOException e) {
throw new IOException("Не удалось записать в файл");
}
}
/** /**
* Represents a collection of products. * Represents a collection of products.
*/ */
@ -39,7 +59,8 @@ public class DbController {
@JacksonXmlProperty(localName = "Product") @JacksonXmlProperty(localName = "Product")
private List<Product> products; private List<Product> products;
public Products() {} public Products() {
}
public Products(List<Product> list) { public Products(List<Product> list) {
this.products = list; this.products = list;
@ -49,31 +70,4 @@ public class DbController {
return products; return products;
} }
} }
public static List<Product> loadDb() throws IOException {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
Products collection = xmlMapper.readValue(bufferedInputStream, Products.class);
bufferedInputStream.close();
return collection.getProducts();
} catch (FileNotFoundException e) {
throw new FileNotFoundException("Не удалось найти файл " + path);
} catch (DatabindException e) {
throw new DbInitializationException();
} catch (IOException e) {
throw new IOException("Произошла неожиданная ошибка во время работы с файлом!");
}
}
public static void saveDb(List<Product> list) throws IOException {
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(path));
xmlMapper.writeValue(outputStreamWriter, new Products(list));
outputStreamWriter.close();
} catch (FileNotFoundException e) {
throw new FileNotFoundException(e.getMessage());
} catch (IOException e) {
throw new IOException(e);
}
}
} }

View File

@ -0,0 +1,32 @@
package me.zinch.Lab6.Server.files;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* The ScriptLoader class provides methods to load scripts from files.
*/
public class ScriptLoader {
public static List<String> loadList(String path) throws IOException {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
Scanner scanner = new Scanner(bufferedInputStream);
List<String> list = new ArrayList<>();
while (scanner.hasNextLine()) {
list.add(scanner.nextLine());
}
bufferedInputStream.close();
scanner.close();
return list;
} catch (FileNotFoundException e) {
throw new FileNotFoundException(String.format("Файл %s не найден.", path));
} catch (IOException e) {
throw new IOException("Произошла неожиданная ошибка во время работы с файлом!");
}
}
}

View File

@ -0,0 +1,81 @@
package me.zinch.Lab6.Server.modules;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.Message;
import me.zinch.Lab6.Domain.dto.MessageBody;
import me.zinch.Lab6.Domain.dto.MessageType;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.commands.CommandManager;
import me.zinch.Lab6.Server.commands.GetCommand;
import me.zinch.Lab6.Server.commands.PostCommand;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
public class CommandModule {
private static final Logger log = LoggerFactory.getLogger(CommandModule.class);
private final IStorage storage;
public CommandModule(IStorage storage) {
this.storage = storage;
}
public ByteBuffer processCommand(Message message) throws IOException {
return switch (message.getType()) {
case HELLO -> serialize(new BodylessMessage(MessageType.HELLO));
case GET -> handleGetCommand((String) message.getBody());
case POST -> handlePostCommand((MessageBody) message.getBody());
default -> {
log.error("Message type not supported");
yield null;
}
};
}
private ByteBuffer handleGetCommand(String inputCommand) throws IOException {
var command = CommandManager.getCommandByInput(inputCommand);
if (command.isEmpty()) {
log.error("Command {} not found", inputCommand);
return serialize(new BodylessMessage(MessageType.ERROR, String.format("Command %s not found", inputCommand)));
}
try {
var action = (GetCommand) command.get();
return serialize(new BodylessMessage(MessageType.OK, action.action(storage)));
} catch (CommandActionException e) {
return serialize(new BodylessMessage(MessageType.ERROR, e.getMessage()));
}
}
private ByteBuffer handlePostCommand(MessageBody request) throws IOException {
var inputCommand = request.getCommand();
var body = request.getBody();
var command = CommandManager.getCommandByInput(inputCommand);
if (command.isEmpty()) {
log.error("Command {} not found", inputCommand);
return serialize(new BodylessMessage(MessageType.ERROR, String.format("Command %s not found", inputCommand)));
}
try {
var action = (PostCommand) command.get();
return serialize(new BodylessMessage(MessageType.OK, action.action(storage, body)));
} catch (CommandActionException e) {
return serialize(new BodylessMessage(MessageType.ERROR, e.getMessage()));
}
}
private static ByteBuffer serialize(Serializable obj) throws IOException {
try (var bOut = new ByteArrayOutputStream();
var oOut = new ObjectOutputStream(bOut)) {
oOut.writeObject(obj);
oOut.flush();
return ByteBuffer.wrap(bOut.toByteArray());
}
}
}

View File

@ -0,0 +1,40 @@
package me.zinch.Lab6.Server.modules;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ConnectionModule {
private static final Logger log = LoggerFactory.getLogger(ConnectionModule.class);
private final ServerSocketChannel serverSocketChannel;
private final Selector selector;
public ConnectionModule(int port, Selector selector) throws IOException {
this.selector = selector;
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
log.info("Connection module initialized on port {}", port);
}
public void handleAccept(SelectionKey key) throws IOException {
var server = (ServerSocketChannel) key.channel();
var client = server.accept();
if (client != null) {
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
log.info("Accepted connection from {}", client.getRemoteAddress());
}
}
public void close() throws IOException {
serverSocketChannel.close();
}
}

View File

@ -0,0 +1,41 @@
package me.zinch.Lab6.Server.modules;
import me.zinch.Lab6.Domain.dto.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Map;
public class RequestModule {
private static final Logger log = LoggerFactory.getLogger(RequestModule.class);
private final Map<SocketChannel, Message> clientMessages;
public RequestModule(Map<SocketChannel, Message> clientMessages) {
this.clientMessages = clientMessages;
}
public void handleRead(SelectionKey key) throws IOException, ClassNotFoundException {
var client = (SocketChannel) key.channel();
var buffer = ByteBuffer.allocate(8192);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
return;
}
buffer.flip();
try (var oIn = new ObjectInputStream(new ByteArrayInputStream(buffer.array()))) {
var msg = (Message) oIn.readObject();
log.info("Received message {} from {}", msg, client.getRemoteAddress());
clientMessages.put(client, msg);
client.register(key.selector(), SelectionKey.OP_WRITE);
}
}
}

View File

@ -0,0 +1,42 @@
package me.zinch.Lab6.Server.modules;
import me.zinch.Lab6.Domain.dto.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Map;
public class ResponseModule {
private static final Logger log = LoggerFactory.getLogger(ResponseModule.class);
private final Map<SocketChannel, Message> clientMessages;
private final CommandModule commandModule;
public ResponseModule(Map<SocketChannel, Message> clientMessages, CommandModule commandModule) {
this.clientMessages = clientMessages;
this.commandModule = commandModule;
}
public void handleWrite(SelectionKey key) throws IOException {
var client = (SocketChannel) key.channel();
var message = clientMessages.get(client);
if (message == null) {
log.error("Message for {} not found", client.getRemoteAddress());
client.close();
return;
}
ByteBuffer responseBuffer = commandModule.processCommand(message);
if (responseBuffer != null) {
client.write(responseBuffer);
log.info("Sent response to {}", client.getRemoteAddress());
}
clientMessages.remove(client);
client.close();
}
}

View File

@ -0,0 +1,123 @@
package me.zinch.Lab6.Server.wrapper;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.Dragon;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import me.zinch.Lab6.Domain.models.DragonType;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
public class DragonCollection implements IStorage {
private final LinkedList<Dragon> dragons;
private Integer idIncrementor;
public DragonCollection(List<Dragon> list) throws ValidationException {
dragons = new LinkedList<>(list);
idIncrementor = dragons.stream()
.mapToInt(Dragon::getId)
.max()
.orElse(0) + 1;
}
private Integer generateId() {
return idIncrementor++;
}
public Dragon getDragonById(Integer id) {
return dragons.stream()
.filter(d -> Objects.equals(d.getId(), id))
.findFirst()
.orElse(null);
}
public Dragon addDragon(Dragon dragon) {
dragons.add(dragon);
return dragon;
}
public Dragon updateDragon(Integer id, Dragon newDragon) {
var index = dragons.indexOf(getDragonById(id));
if (index != -1) {
dragons.set(index, newDragon);
}
return newDragon;
}
public Dragon removeDragon(Integer id) {
var dragon = getDragonById(id);
dragons.remove(dragon);
return dragon;
}
public String getInfo() {
var initDate = dragons.stream()
.map(Dragon::getCreationDate)
.min(Comparator.naturalOrder())
.orElse(LocalDateTime.MIN);
return String.format("Type: LinkedList%nInitialization date: %s%nNumber of elements: %s",
initDate, dragons.size());
}
public void clear() {
dragons.clear();
}
public Dragon getHead() {
return dragons.isEmpty() ? null : dragons.getFirst();
}
public Dragon removeHead() {
return dragons.isEmpty() ? null : dragons.removeFirst();
}
public Dragon getMaxByWeight() {
return dragons.stream()
.max(Comparator.comparingLong(Dragon::getWeight))
.orElse(null);
}
public Map<DragonType, Long> groupCountingByType() {
return dragons.stream()
.collect(Collectors.groupingBy(
Dragon::getType,
Collectors.counting()
));
}
public List<Dragon> filterLessThanCharacter(DragonCharacter character) {
return dragons.stream()
.filter(d -> d.getCharacter().compareTo(character) < 0)
.collect(Collectors.toList());
}
public List<Dragon> toList() {
return new LinkedList<>(dragons);
}
@Override
public String toString() {
return dragons.stream()
.map(Dragon::toString)
.collect(Collectors.joining("\n"));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DragonCollection that = (DragonCollection) o;
return Objects.equals(dragons, that.dragons) && Objects.equals(idIncrementor, that.idIncrementor);
}
@Override
public int hashCode() {
return Objects.hash(dragons, idIncrementor);
}
}

View File

@ -1,8 +1,9 @@
package me.zinch.wrapper; package me.zinch.Lab6.Server.wrapper;
import me.zinch.exceptions.ValidationException; import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.models.Product; import me.zinch.Lab6.Domain.models.Product;
import me.zinch.models.ProductDTO; import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
@ -17,23 +18,28 @@ import java.util.TreeSet;
/** /**
* Represents a collection of products. * Represents a collection of products.
*/ */
public class ProductCollection { public class ProductCollection implements IStorage {
private final TreeSet<Product> productList; private final TreeSet<Product> productList;
private Long idIncrementor; private Long idIncrementor;
public ProductCollection(List<Product> list) throws ValidationException { public ProductCollection(List<Product> list) throws ValidationException {
productList = new TreeSet<>(Comparator.comparingLong(Product::getId)); productList = new TreeSet<>(Comparator.comparing(Product::getName));
productList.addAll(list); productList.addAll(list);
idIncrementor = productList.last().getId() + 1; idIncrementor = productList.stream()
.mapToLong(Product::getId)
.max()
.orElse(0) + 1;
} }
private Long generateId() { private Long generateId() {
return idIncrementor++; return idIncrementor++;
} }
private Product getProductById(Long id) { public Product getProductById(Long id) {
Optional<Product> product = productList.stream().filter(i -> Objects.equals(i.getId(), id)).findFirst(); return productList.stream()
return product.orElse(null); .filter(p -> Objects.equals(p.getId(), id))
.findFirst()
.orElse(null);
} }
public Product addProduct(ProductDTO productDTO) { public Product addProduct(ProductDTO productDTO) {
@ -61,48 +67,62 @@ public class ProductCollection {
} }
public String getInfo() { public String getInfo() {
var optionalInitDate = productList.stream().map(Product::getCreationDate).sorted().findFirst(); var initDate = productList.stream()
ZonedDateTime initDate = optionalInitDate.orElse(ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.systemDefault())); .map(Product::getCreationDate)
return String.format("TreeSet%nInit Date: %s%nNumber of elements: %s", initDate, productList.size()); .min(Comparator.naturalOrder())
.orElse(ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.systemDefault()));
return String.format("Type: TreeSet%nInitialization date: %s%nNumber of elements: %s",
initDate, productList.size());
} }
public void clear() { public void clear() {
productList.clear(); productList.clear();
} }
public Long getMaxPrice() { public Long getMaxPrice() {
return productList.stream().max(Comparator.comparingLong(Product::getPrice)).map(Product::getPrice).orElse(null); return productList.stream()
.mapToLong(Product::getPrice)
.max()
.orElse(0);
} }
public Long getMinPrice() { public Long getMinPrice() {
return productList.stream().min(Comparator.comparingLong(Product::getPrice)).map(Product::getPrice).orElse(null); return productList.stream()
.mapToLong(Product::getPrice)
.min()
.orElse(0);
} }
public boolean isProductIdExists(Long id) { public boolean isProductIdExists(Long id) {
return getProductById(id) != null; return productList.stream()
.anyMatch(p -> Objects.equals(p.getId(), id));
} }
public Integer removeLover(Long id) { public Integer removeLover(Long id) {
var size = productList.size(); var size = productList.size();
productList.headSet(getProductById(id)).stream().toList().forEach(productList::remove); var targetProduct = getProductById(id);
productList.stream()
.filter(p -> p.compareTo(targetProduct) < 0)
.toList()
.forEach(productList::remove);
return size - productList.size(); return size - productList.size();
} }
public String filterContainsName(String name) { public String filterContainsName(String name) {
return String.join("\n", productList.stream() return productList.stream()
.filter(product -> product.getName().toLowerCase().contains(name.toLowerCase())) .filter(product -> product.getName().toLowerCase().contains(name.toLowerCase()))
.map(Product::toString) .map(Product::toString)
.toList()); .reduce((a, b) -> a + "\n" + b)
.orElse("");
} }
public String getUniqueManufactureCost() { public String getUniqueManufactureCost() {
return String.join(", ", Set.copyOf(productList.stream() return productList.stream()
.map(Product::getManufactureCost) .map(Product::getManufactureCost)
.toList()) .distinct()
.stream() .map(Object::toString)
.map(Object::toString) .reduce((a, b) -> a + ", " + b)
.toList()); .orElse("");
} }
public List<Product> toList() { public List<Product> toList() {
@ -111,7 +131,10 @@ public class ProductCollection {
@Override @Override
public String toString() { public String toString() {
return String.join("\n", productList.stream().map(Product::toString).toList()); return productList.stream()
.map(Product::toString)
.reduce((a, b) -> a + "\n" + b)
.orElse("");
} }
@Override @Override

View File

@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

Some files were not shown because too many files have changed in this diff Show More