diff --git a/src/main/java/com/itmo/java/basics/DatabaseServer.java b/src/main/java/com/itmo/java/basics/DatabaseServer.java index fc9b5972..c5ea3e59 100644 --- a/src/main/java/com/itmo/java/basics/DatabaseServer.java +++ b/src/main/java/com/itmo/java/basics/DatabaseServer.java @@ -2,7 +2,9 @@ import com.itmo.java.basics.console.*; import com.itmo.java.basics.exceptions.DatabaseException; -import com.itmo.java.basics.initialization.impl.*; +import com.itmo.java.basics.initialization.InitializationContext; +import com.itmo.java.basics.initialization.impl.DatabaseServerInitializer; +import com.itmo.java.basics.initialization.impl.InitializationContextImpl; import com.itmo.java.protocol.model.RespArray; import java.util.concurrent.CompletableFuture; @@ -11,31 +13,36 @@ public class DatabaseServer { - private final ExecutorService executorService = Executors.newSingleThreadExecutor(); - private final ExecutionEnvironment enviroment; + private ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final ExecutionEnvironment environment; /** - * Con structor + * Конструктор * - * @param env env для инициализации. Далее работа происходит с заполненым объектом + * @param env env для инициализации. Далее работа происходит с заполненным объектом * @param initializer готовый чейн инициализации * @throws DatabaseException если произошла ошибка инициализации */ public static DatabaseServer initialize(ExecutionEnvironment env, DatabaseServerInitializer initializer) throws DatabaseException { - initializer.perform(new InitializationContextImpl(env, null, null, null)); + InitializationContext context = InitializationContextImpl.builder().executionEnvironment(env).build(); + initializer.perform(context); return new DatabaseServer(env); } private DatabaseServer(ExecutionEnvironment env) { - this.enviroment = env; + this.environment = env; } public CompletableFuture executeNextCommand(RespArray message) { - return CompletableFuture.supplyAsync(() -> - DatabaseCommands.valueOf(message.getObjects().get(DatabaseCommandArgPositions.COMMAND_NAME.getPositionIndex()).asString()).getCommand(enviroment, message.getObjects()).execute(), executorService); + return CompletableFuture.supplyAsync(() -> DatabaseCommands.valueOf(message.getObjects().get(DatabaseCommandArgPositions. + COMMAND_NAME.getPositionIndex()).asString()).getCommand(environment, message.getObjects()).execute(), executorService); } public CompletableFuture executeNextCommand(DatabaseCommand command) { return CompletableFuture.supplyAsync(command::execute, executorService); } + + public ExecutionEnvironment getEnv() { + return environment; + } } \ No newline at end of file diff --git a/src/main/java/com/itmo/java/basics/MainTest.java b/src/main/java/com/itmo/java/basics/MainTest.java deleted file mode 100644 index eec8a891..00000000 --- a/src/main/java/com/itmo/java/basics/MainTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.itmo.java.basics; - -import com.itmo.java.basics.config.DatabaseConfig; -import com.itmo.java.basics.console.impl.ExecutionEnvironmentImpl; -import com.itmo.java.basics.exceptions.DatabaseException; -import com.itmo.java.basics.initialization.*; -import com.itmo.java.basics.initialization.impl.*; -import com.itmo.java.basics.logic.*; -import com.itmo.java.basics.logic.impl.*; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.nio.file.Path; -import java.util.Comparator; -import java.util.Optional; - -public class MainTest { - public static void main(String[] args) throws DatabaseException, IOException { -// Files.walk(Path.of("db", "anime")) -// .sorted(Comparator.reverseOrder()) -// .map(Path::toFile) -// .forEach(File::delete); - try { - Database db = DatabaseImpl.create("anime", Path.of("db")); - db.createTableIfNotExists("naruto"); - db.write("naruto", "key", "value".getBytes(StandardCharsets.UTF_8)); - db.write("naruto", "key", "value2".getBytes(StandardCharsets.UTF_8)); - db.write("naruto", "key", null); - db.write("naruto", "key1", "value1".getBytes(StandardCharsets.UTF_8)); - db.write("naruto", "key2", "".getBytes(StandardCharsets.UTF_8)); - } - catch (DatabaseException ex) - {throw new DatabaseException(ex);} -// System.out.printf( -// "get = %s expected = %s\n", -// new String(db.read("naruto", "saske").get()), -// "ora" -// ); -// db.write("naruto", "saske", null); -// db.delete("naruto", "saske"); -// System.out.printf( -// "get = %s expected = %s\n", -// db.read("naruto", "saske").toString(), -// Optional.empty().toString() -// ); -// db.write("naruto", "saske", "ora".getBytes(StandardCharsets.UTF_8)); -// System.out.printf( -// "get = %s expected = %s\n", -// new String(db.read("naruto", "saske").get()), -// "ora" -// ); - -// Initializer initializer = -// new DatabaseServerInitializer( -// new DatabaseInitializer( -// new TableInitializer( -// new SegmentInitializer()))); -// var execEnv = new ExecutionEnvironmentImpl( -// new DatabaseConfig("db") -// ); -// var context = new InitializationContextImpl( -// execEnv, null, null, null -// ); -// initializer.perform(context); - } -} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/basics/config/ConfigLoader.java b/src/main/java/com/itmo/java/basics/config/ConfigLoader.java new file mode 100644 index 00000000..7846789b --- /dev/null +++ b/src/main/java/com/itmo/java/basics/config/ConfigLoader.java @@ -0,0 +1,89 @@ +package com.itmo.java.basics.config; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Класс, отвечающий за подгрузку данных из конфигурационного файла формата .properties + */ +public class ConfigLoader { + private static final String DEFAULT_PROPERTY_FILE = "server.properties"; + + private InputStream propertyInputStream; + + /** + * По умолчанию читает из server.properties + */ + public ConfigLoader() { + propertyInputStream = getClass().getClassLoader().getResourceAsStream(DEFAULT_PROPERTY_FILE); + if (this.propertyInputStream == null) { + try { + this.propertyInputStream = new FileInputStream(DEFAULT_PROPERTY_FILE); + } catch (FileNotFoundException e) { + this.propertyInputStream = null; + } + } + } + + /** + * @param name Имя конфикурационного файла, откуда читать + */ + public ConfigLoader(String name) { + this.propertyInputStream = getClass().getClassLoader().getResourceAsStream(name); + if (this.propertyInputStream == null) { + try { + this.propertyInputStream = new FileInputStream(name); + } catch (FileNotFoundException e) { + this.propertyInputStream = null; + } + } + } + + /** + * Считывает конфиг из указанного в конструкторе файла. + * Если не удалось считать из заданного файла, или какого-то конкретно значения не оказалось, + * то используют дефолтные значения из {@link DatabaseConfig} и {@link ServerConfig} + *
+ * Читаются: "kvs.workingPath", "kvs.host", "kvs.port" (но в конфигурационном файле допустимы и другие проперти) + */ + public DatabaseServerConfig readConfig() { + Properties properties = new Properties(); + try { + if (propertyInputStream == null) { + throw new IOException("Config file not found"); + } + properties.load(propertyInputStream); + String workingPath = properties.getProperty("kvs.workingPath"); + String host = properties.getProperty("kvs.host"); + String portStr = properties.getProperty("kvs.port"); + DatabaseConfig databaseConfig; + ServerConfig serverConfig; + if (workingPath == null) { + databaseConfig = new DatabaseConfig(); + } else { + databaseConfig = new DatabaseConfig(workingPath); + } + if (host == null){ + host = ServerConfig.DEFAULT_HOST; + } + try { + int port = Integer.parseInt(portStr); + serverConfig = new ServerConfig(host, port); + } catch (NumberFormatException e) { + serverConfig = new ServerConfig(host, ServerConfig.DEFAULT_PORT); + } + return DatabaseServerConfig.builder() + .dbConfig(databaseConfig) + .serverConfig(serverConfig) + .build(); + } catch (IOException e) { + return DatabaseServerConfig.builder() + .dbConfig(new DatabaseConfig()) + .serverConfig(new ServerConfig(ServerConfig.DEFAULT_HOST, ServerConfig.DEFAULT_PORT)) + .build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/basics/config/DatabaseConfig.java b/src/main/java/com/itmo/java/basics/config/DatabaseConfig.java index 6ae46737..9cab7e3a 100644 --- a/src/main/java/com/itmo/java/basics/config/DatabaseConfig.java +++ b/src/main/java/com/itmo/java/basics/config/DatabaseConfig.java @@ -1,7 +1,7 @@ package com.itmo.java.basics.config; -public class DatabaseConfig { +public class DatabaseConfig { public static final String DEFAULT_WORKING_PATH = "db_files"; private final String workingPath; @@ -9,6 +9,10 @@ public DatabaseConfig(String workingPath) { this.workingPath = workingPath; } + public DatabaseConfig(){ + this.workingPath = DEFAULT_WORKING_PATH; + } + public String getWorkingPath() { return workingPath; } diff --git a/src/main/java/com/itmo/java/basics/config/DatabaseServerConfig.java b/src/main/java/com/itmo/java/basics/config/DatabaseServerConfig.java new file mode 100644 index 00000000..50d6d36a --- /dev/null +++ b/src/main/java/com/itmo/java/basics/config/DatabaseServerConfig.java @@ -0,0 +1,14 @@ +package com.itmo.java.basics.config; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@Builder +public class DatabaseServerConfig { + private final ServerConfig serverConfig; + + private final DatabaseConfig dbConfig; +} diff --git a/src/main/java/com/itmo/java/basics/config/ServerConfig.java b/src/main/java/com/itmo/java/basics/config/ServerConfig.java new file mode 100644 index 00000000..617adda3 --- /dev/null +++ b/src/main/java/com/itmo/java/basics/config/ServerConfig.java @@ -0,0 +1,19 @@ +package com.itmo.java.basics.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Какой хост и какой порт будет слушать наш сервер + */ +@Getter +@AllArgsConstructor +public class ServerConfig { + + public static final String DEFAULT_HOST = "localhost"; + public static final int DEFAULT_PORT = 8080; + + private final String host; + private final int port; + +} diff --git a/src/main/java/com/itmo/java/basics/connector/JavaSocketServerConnector.java b/src/main/java/com/itmo/java/basics/connector/JavaSocketServerConnector.java new file mode 100644 index 00000000..97ad17df --- /dev/null +++ b/src/main/java/com/itmo/java/basics/connector/JavaSocketServerConnector.java @@ -0,0 +1,129 @@ +package com.itmo.java.basics.connector; + +import com.itmo.java.basics.DatabaseServer; +import com.itmo.java.basics.config.ServerConfig; +import com.itmo.java.basics.console.DatabaseCommandResult; +import com.itmo.java.basics.resp.CommandReader; +import com.itmo.java.protocol.RespReader; +import com.itmo.java.protocol.RespWriter; + +import java.io.Closeable; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Класс, который предоставляет доступ к серверу через сокеты + */ +public class JavaSocketServerConnector implements Closeable { + + /** + * Экзекьютор для выполнения ClientTask + */ + private final ExecutorService clientIOWorkers = Executors.newSingleThreadExecutor(); + + private final ServerSocket serverSocket; + private final ExecutorService connectionAcceptorExecutor = Executors.newSingleThreadExecutor(); + private final DatabaseServer server; + + /** + * Стартует сервер. По аналогии с сокетом открывает коннекшн в конструкторе. + */ + public JavaSocketServerConnector(DatabaseServer databaseServer, ServerConfig config) throws IOException { + this.serverSocket = new ServerSocket(config.getPort()); + this.server = databaseServer; + } + + /** + * Начинает слушать заданный порт, начинает аксептить клиентские сокеты. На каждый из них начинает клиентскую таску + */ + public void start() { + connectionAcceptorExecutor.submit(() -> { + while (true) { + Socket clientSocket = serverSocket.accept(); + clientIOWorkers.submit(new ClientTask(clientSocket, server)); + } + }); + } + + /** + * Закрывает все, что нужно ¯\_(ツ)_/¯ + */ + @Override + public void close() { + System.out.println("Stopping socket connector"); + connectionAcceptorExecutor.shutdownNow(); + clientIOWorkers.shutdownNow(); + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException ex) { + throw new RuntimeException("IOException when try to close connection", ex); + } + } + } + + + public static void main(String[] args) throws Exception { + } + + /** + * Runnable, описывающий исполнение клиентской команды. + */ + static class ClientTask implements Runnable, Closeable { + private final Socket client; + private final DatabaseServer server; + private final RespWriter respWriter; + + /** + * @param client клиентский сокет + * @param server сервер, на котором исполняется задача + */ + public ClientTask(Socket client, DatabaseServer server) { + this.client = client; + this.server = server; + try { + this.respWriter = new RespWriter(client.getOutputStream()); + } catch (IOException ex) { + throw new RuntimeException("IOException when open socket streams", ex); + } + } + + /** + * Исполняет задачи из одного клиентского сокета, пока клиент не отсоединился или текущий поток не был прерван (interrupted). + * Для кажной из задач: + * 1. Читает из сокета команду с помощью {@link CommandReader} + * 2. Исполняет ее на сервере + * 3. Записывает результат в сокет с помощью {@link RespWriter} + */ + @Override + public void run() { + try (CommandReader commandReader = new CommandReader(new RespReader(client.getInputStream()), server.getEnv())) { + while (commandReader.hasNextCommand()) { + CompletableFuture commandResult = server.executeNextCommand(commandReader.readCommand()); + respWriter.write(commandResult.get().serialize()); + } + close(); + } catch (Exception ex) { + close(); + throw new RuntimeException("Write or execute command", ex); + } + } + + /** + * Закрывает клиентский сокет + */ + @Override + public void close() { + try { + respWriter.close(); + client.close(); + } catch (IOException ex) { + throw new RuntimeException("When try to close client connection", ex); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/basics/console/DatabaseCommand.java b/src/main/java/com/itmo/java/basics/console/DatabaseCommand.java index cf861b2e..8d9c3c67 100644 --- a/src/main/java/com/itmo/java/basics/console/DatabaseCommand.java +++ b/src/main/java/com/itmo/java/basics/console/DatabaseCommand.java @@ -6,5 +6,5 @@ public interface DatabaseCommand { * * @return Сообщение о выполнении результата команды. */ - DatabaseCommandResult execute(); + DatabaseCommandResult execute() ; } diff --git a/src/main/java/com/itmo/java/basics/console/DatabaseCommandResult.java b/src/main/java/com/itmo/java/basics/console/DatabaseCommandResult.java index fb491ece..45ead06d 100644 --- a/src/main/java/com/itmo/java/basics/console/DatabaseCommandResult.java +++ b/src/main/java/com/itmo/java/basics/console/DatabaseCommandResult.java @@ -4,6 +4,8 @@ import com.itmo.java.basics.console.impl.SuccessDatabaseCommandResult; import com.itmo.java.protocol.model.RespObject; +import java.util.Arrays; + public interface DatabaseCommandResult extends DatabaseApiSerializable { /** @@ -34,7 +36,11 @@ static DatabaseCommandResult error(String message) { * @return результат команды, при выполнении которой произошла ошибка */ static DatabaseCommandResult error(Exception exception) { - return new FailedDatabaseCommandResult(exception.getMessage()); + if (exception.getMessage().isEmpty()) { + return new FailedDatabaseCommandResult(Arrays.toString(exception.getStackTrace())); + } else { + return new FailedDatabaseCommandResult(exception.getMessage()); + } } /** diff --git a/src/main/java/com/itmo/java/basics/console/impl/CreateDatabaseCommand.java b/src/main/java/com/itmo/java/basics/console/impl/CreateDatabaseCommand.java index f1f0b5b1..b8a0e350 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/CreateDatabaseCommand.java +++ b/src/main/java/com/itmo/java/basics/console/impl/CreateDatabaseCommand.java @@ -15,30 +15,25 @@ * Команда для создания базы данных */ public class CreateDatabaseCommand implements DatabaseCommand { - private final ExecutionEnvironment environment; - private final DatabaseFactory dbfactory; - private final List commandargs; - private static final int numberOfAgrguments = 3; + private final DatabaseFactory factoryDataBase; + private final String databaseName; /** * Создает команду. *
* Обратите внимание, что в конструкторе нет логики проверки валидности данных. Не проверяется, можно ли исполнить команду. Только формальные признаки (например, количество переданных значений или ненуловость объектов * - * @param env env - * @param factory функция создания базы данных (пример: DatabaseImpl::create) - * @param comArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. - * Id команды, имя команды, имя создаваемой бд + * @param env env + * @param factory функция создания базы данных (пример: DatabaseImpl::create) + * @param commandArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. + * Id команды, имя команды, имя создаваемой бд * @throws IllegalArgumentException если передано неправильное количество аргументов */ - public CreateDatabaseCommand(ExecutionEnvironment env, DatabaseFactory factory, List comArgs) { - if (comArgs.size() != numberOfAgrguments) { - throw new IllegalArgumentException("Why " + comArgs.size() + "!= 3 , in CreateDataBaseCommand"); - } + public CreateDatabaseCommand(ExecutionEnvironment env, DatabaseFactory factory, List commandArgs) { environment = env; - dbfactory = factory; - commandargs = comArgs; + factoryDataBase = factory; + databaseName = commandArgs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); } /** @@ -49,14 +44,10 @@ public CreateDatabaseCommand(ExecutionEnvironment env, DatabaseFactory factory, @Override public DatabaseCommandResult execute() { try { - String dbName = commandargs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); - if (dbName == null) { - throw new DatabaseException("Why dbname is null? "); - } - environment.addDatabase(dbfactory.createNonExistent(dbName, environment.getWorkingPath())); - return DatabaseCommandResult.success(("Success add " + dbName).getBytes(StandardCharsets.UTF_8)); - } catch (DatabaseException ex) { - return new FailedDatabaseCommandResult(ex.getMessage()); + environment.addDatabase(factoryDataBase.createNonExistent(databaseName, environment.getWorkingPath())); + } catch (DatabaseException ex){ + return DatabaseCommandResult.error("DatabaseException when try to create database " + databaseName); } + return DatabaseCommandResult.success(("Success add CreateDataBaseCommand " + databaseName).getBytes(StandardCharsets.UTF_8)); } } diff --git a/src/main/java/com/itmo/java/basics/console/impl/CreateTableCommand.java b/src/main/java/com/itmo/java/basics/console/impl/CreateTableCommand.java index b813f2ac..310f5ede 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/CreateTableCommand.java +++ b/src/main/java/com/itmo/java/basics/console/impl/CreateTableCommand.java @@ -5,12 +5,10 @@ import com.itmo.java.basics.console.DatabaseCommandResult; import com.itmo.java.basics.console.ExecutionEnvironment; import com.itmo.java.basics.exceptions.DatabaseException; -import com.itmo.java.basics.logic.Database; import com.itmo.java.protocol.model.RespObject; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Optional; /** * Команда для создания базы таблицы @@ -18,25 +16,23 @@ public class CreateTableCommand implements DatabaseCommand { private final ExecutionEnvironment environment; - private final List commandargs; - private static final int numberOfAgrguments = 4; + private final String databaseName; + private final String tableName; /** * Создает команду *
* Обратите внимание, что в конструкторе нет логики проверки валидности данных. Не проверяется, можно ли исполнить команду. Только формальные признаки (например, количество переданных значений или ненуловость объектов * - * @param env env - * @param comArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. - * Id команды, имя команды, имя бд, имя таблицы + * @param env env + * @param commandArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. + * Id команды, имя команды, имя бд, имя таблицы * @throws IllegalArgumentException если передано неправильное количество аргументов */ - public CreateTableCommand(ExecutionEnvironment env, List comArgs) { - if (comArgs.size() != numberOfAgrguments) { - throw new IllegalArgumentException("Why " + comArgs.size() + "!= 4 , in CreateTableCommand"); - } + public CreateTableCommand(ExecutionEnvironment env, List commandArgs) { environment = env; - commandargs = comArgs; + databaseName = commandArgs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); + tableName = commandArgs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); } /** @@ -47,22 +43,13 @@ public CreateTableCommand(ExecutionEnvironment env, List comArgs) { @Override public DatabaseCommandResult execute() { try { - String dbName = commandargs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); - if (dbName == null) { - throw new DatabaseException("Why dbname is null?"); - } - String tbName = commandargs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); - if (tbName == null) { - throw new DatabaseException("Why tbName is null?"); - } - Optional dataBase = environment.getDatabase(dbName); - if (dataBase.isEmpty()) { - throw new DatabaseException("We dont have" + dbName); + if (environment.getDatabase(databaseName).isEmpty()){ + return DatabaseCommandResult.error("We dont found database " + databaseName); } - dataBase.get().createTableIfNotExists(tbName); - return DatabaseCommandResult.success(("Success add " + dbName + tbName).getBytes(StandardCharsets.UTF_8)); - } catch (DatabaseException ex) { - return new FailedDatabaseCommandResult(ex.getMessage()); + environment.getDatabase(databaseName).get().createTableIfNotExists(tableName); + } catch (DatabaseException ex){ + return DatabaseCommandResult.error("DatabaseException when try to create table " + tableName); } + return DatabaseCommandResult.success(("Table " + tableName + " was created").getBytes(StandardCharsets.UTF_8)); } } diff --git a/src/main/java/com/itmo/java/basics/console/impl/DeleteKeyCommand.java b/src/main/java/com/itmo/java/basics/console/impl/DeleteKeyCommand.java index c9e426f1..95c061ee 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/DeleteKeyCommand.java +++ b/src/main/java/com/itmo/java/basics/console/impl/DeleteKeyCommand.java @@ -8,7 +8,6 @@ import com.itmo.java.basics.logic.Database; import com.itmo.java.protocol.model.RespObject; -import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; @@ -16,27 +15,26 @@ * Команда для создания удаления значения по ключу */ public class DeleteKeyCommand implements DatabaseCommand { - private final ExecutionEnvironment environment; - private final List commandargs; - private static final int numberOfAgrguments = 5; + private final String databaseName; + private final String tableName; + private final String key; /** * Создает команду. *
* Обратите внимание, что в конструкторе нет логики проверки валидности данных. Не проверяется, можно ли исполнить команду. Только формальные признаки (например, количество переданных значений или ненуловость объектов * - * @param env env - * @param comArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. - * Id команды, имя команды, имя бд, таблицы, ключ + * @param env env + * @param commandArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. + * Id команды, имя команды, имя бд, таблицы, ключ * @throws IllegalArgumentException если передано неправильное количество аргументов */ - public DeleteKeyCommand(ExecutionEnvironment env, List comArgs) { - if (comArgs.size() != numberOfAgrguments) { - throw new IllegalArgumentException("Why " + comArgs.size() + "!= 5 , in CreateTableCommand"); - } - environment = env; - commandargs = comArgs; + public DeleteKeyCommand(ExecutionEnvironment env, List commandArgs) { + environment= env; + databaseName = commandArgs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); + tableName = commandArgs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); + key = commandArgs.get(DatabaseCommandArgPositions.KEY.getPositionIndex()).asString(); } /** @@ -47,26 +45,18 @@ public DeleteKeyCommand(ExecutionEnvironment env, List comArgs) { @Override public DatabaseCommandResult execute() { try { - String dbName = commandargs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); - if (dbName == null) { - throw new DatabaseException("Why dbname is null?"); - } - String tbName = commandargs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); - if (tbName == null) { - throw new DatabaseException("Why tbName is null?"); - } - String key = commandargs.get(DatabaseCommandArgPositions.KEY.getPositionIndex()).asString(); - if (key == null) { - throw new DatabaseException("Why key is null?"); + Optional database = environment.getDatabase(databaseName); + if (database.isEmpty()){ + return DatabaseCommandResult.error("Not found database DeleteKeyCommand " + databaseName); } - Optional dataBase = environment.getDatabase(dbName); - if (dataBase.isEmpty()) { - throw new DatabaseException("We dont have" + dbName); + Optional previous = database.get().read(tableName, key); + if (previous.isEmpty()){ + return DatabaseCommandResult.error("Value with key DeleteKeyCommand" + key + " in database " + databaseName + " not found"); } - dataBase.get().delete(tbName, key); - return DatabaseCommandResult.success(("Success del " + dbName + tbName + key).getBytes(StandardCharsets.UTF_8)); - } catch (DatabaseException ex) { - return new FailedDatabaseCommandResult(ex.getMessage()); + database.get().delete(tableName, key); + return DatabaseCommandResult.success(previous.get()); + } catch (DatabaseException e){ + return DatabaseCommandResult.error("DatabaseException when try to delete value by key DeleteKeyCommand " + key + " in table " + tableName); } } } diff --git a/src/main/java/com/itmo/java/basics/console/impl/ExecutionEnvironmentImpl.java b/src/main/java/com/itmo/java/basics/console/impl/ExecutionEnvironmentImpl.java index 61006344..54d75e3c 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/ExecutionEnvironmentImpl.java +++ b/src/main/java/com/itmo/java/basics/console/impl/ExecutionEnvironmentImpl.java @@ -5,32 +5,31 @@ import com.itmo.java.basics.logic.Database; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class ExecutionEnvironmentImpl implements ExecutionEnvironment { - - private final DatabaseConfig dbConfig; - private final Map dataBase; + Map databases = new HashMap<>(); + Path workingPath; public ExecutionEnvironmentImpl(DatabaseConfig config) { - dbConfig = config; - dataBase = new HashMap<>(); + workingPath = Paths.get(config.getWorkingPath()); } @Override public Optional getDatabase(String name) { - return Optional.ofNullable(dataBase.get(name)); + return Optional.ofNullable(databases.get(name)); } @Override public void addDatabase(Database db) { - dataBase.put(db.getName(), db); + databases.put(db.getName(), db); } @Override public Path getWorkingPath() { - return Path.of(dbConfig.getWorkingPath()); + return workingPath; } } diff --git a/src/main/java/com/itmo/java/basics/console/impl/FailedDatabaseCommandResult.java b/src/main/java/com/itmo/java/basics/console/impl/FailedDatabaseCommandResult.java index fc08c3c1..3b470bb7 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/FailedDatabaseCommandResult.java +++ b/src/main/java/com/itmo/java/basics/console/impl/FailedDatabaseCommandResult.java @@ -10,12 +10,10 @@ * Зафейленная команда */ public class FailedDatabaseCommandResult implements DatabaseCommandResult { - - private final String payLoad; public FailedDatabaseCommandResult(String pload) { - payLoad = pload; + this.payLoad = pload; } /** diff --git a/src/main/java/com/itmo/java/basics/console/impl/GetKeyCommand.java b/src/main/java/com/itmo/java/basics/console/impl/GetKeyCommand.java index 07095674..b2b00dd5 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/GetKeyCommand.java +++ b/src/main/java/com/itmo/java/basics/console/impl/GetKeyCommand.java @@ -8,6 +8,7 @@ import com.itmo.java.basics.logic.Database; import com.itmo.java.protocol.model.RespObject; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; @@ -15,27 +16,25 @@ * Команда для чтения данных по ключу */ public class GetKeyCommand implements DatabaseCommand { - private final ExecutionEnvironment environment; - private final List commandargs; - private static final int numberOfAgrguments = 5; - + private final String databaseName; + private final String tableName; + private final String key; /** * Создает команду. *
* Обратите внимание, что в конструкторе нет логики проверки валидности данных. Не проверяется, можно ли исполнить команду. Только формальные признаки (например, количество переданных значений или ненуловость объектов * - * @param env env - * @param comArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. - * Id команды, имя команды, имя бд, таблицы, ключ + * @param env env + * @param commandArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. + * Id команды, имя команды, имя бд, таблицы, ключ * @throws IllegalArgumentException если передано неправильное количество аргументов */ - public GetKeyCommand(ExecutionEnvironment env, List comArgs) { - if (comArgs.size() != numberOfAgrguments) { - throw new IllegalArgumentException("Why " + comArgs.size() + "!= 5 , in CreateTableCommand"); - } + public GetKeyCommand(ExecutionEnvironment env, List commandArgs) { environment = env; - commandargs = comArgs; + databaseName = commandArgs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); + tableName = commandArgs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); + key = commandArgs.get(DatabaseCommandArgPositions.KEY.getPositionIndex()).asString(); } /** @@ -46,29 +45,14 @@ public GetKeyCommand(ExecutionEnvironment env, List comArgs) { @Override public DatabaseCommandResult execute() { try { - String dbName = commandargs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); - if (dbName == null) { - throw new DatabaseException("Why dbname is null?"); - } - String tbName = commandargs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); - if (tbName == null) { - throw new DatabaseException("Why tbName is null?"); - } - String key = commandargs.get(DatabaseCommandArgPositions.KEY.getPositionIndex()).asString(); - if (key == null) { - throw new DatabaseException("Why key is null?"); - } - Optional dataBase = environment.getDatabase(dbName); - if (dataBase.isEmpty()) { - throw new DatabaseException("We dont have" + dbName); - } - Optional value = dataBase.get().read(tbName, key); - if (value.isEmpty()) { - throw new DatabaseException("We dont have" + dbName + tbName + key); + Optional database = environment.getDatabase(databaseName); + if (database.isEmpty()) { + return DatabaseCommandResult.error("We dont have GetKeyCommand"); } - return DatabaseCommandResult.success(value.get()); - } catch (DatabaseException ex) { - return new FailedDatabaseCommandResult(ex.getMessage()); + Optional value = database.get().read(tableName, key); + return DatabaseCommandResult.success(value.orElse(null)); + } catch (DatabaseException e){ + return DatabaseCommandResult.error("Error when try ti get value bu key"); } } } diff --git a/src/main/java/com/itmo/java/basics/console/impl/SetKeyCommand.java b/src/main/java/com/itmo/java/basics/console/impl/SetKeyCommand.java index a95597b5..a5fd9e17 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/SetKeyCommand.java +++ b/src/main/java/com/itmo/java/basics/console/impl/SetKeyCommand.java @@ -18,25 +18,28 @@ public class SetKeyCommand implements DatabaseCommand { private final ExecutionEnvironment environment; - private final List commandargs; - private static final int numberOfAgrguments = 6; + private final String dbName; + private final String tbName; + private final String key; + private final String value; /** * Создает команду. *
* Обратите внимание, что в конструкторе нет логики проверки валидности данных. Не проверяется, можно ли исполнить команду. Только формальные признаки (например, количество переданных значений или ненуловость объектов * - * @param env env - * @param comArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. - * Id команды, имя команды, имя бд, таблицы, ключ, значение + * @param env env + * @param commandArgs аргументы для создания (порядок - {@link DatabaseCommandArgPositions}. + * Id команды, имя команды, имя бд, таблицы, ключ, значение * @throws IllegalArgumentException если передано неправильное количество аргументов */ - public SetKeyCommand(ExecutionEnvironment env, List comArgs) { - if (comArgs.size() != numberOfAgrguments) { - throw new IllegalArgumentException("Why " + comArgs.size() + "!= 5 , in CreateTableCommand"); - } + public SetKeyCommand(ExecutionEnvironment env, List commandArgs) { environment = env; - commandargs = comArgs; + value = commandArgs.get(DatabaseCommandArgPositions.VALUE.getPositionIndex()).asString(); + dbName = commandArgs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); + tbName = commandArgs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); + key = commandArgs.get(DatabaseCommandArgPositions.KEY.getPositionIndex()).asString(); + } /** @@ -47,27 +50,15 @@ public SetKeyCommand(ExecutionEnvironment env, List comArgs) { @Override public DatabaseCommandResult execute() { try { - String dbName = commandargs.get(DatabaseCommandArgPositions.DATABASE_NAME.getPositionIndex()).asString(); - if (dbName == null) { - throw new DatabaseException("Why dbname is null?"); - } - String tbName = commandargs.get(DatabaseCommandArgPositions.TABLE_NAME.getPositionIndex()).asString(); - if (tbName == null) { - throw new DatabaseException("Why tbName is null?"); - } - String key = commandargs.get(DatabaseCommandArgPositions.KEY.getPositionIndex()).asString(); - if (key == null) { - throw new DatabaseException("Why key is null?"); - } - Optional dataBase = environment.getDatabase(dbName); - if (dataBase.isEmpty()) { - throw new DatabaseException("We dont have" + dbName); + Optional database = environment.getDatabase(dbName); + if (database.isEmpty()) { + return DatabaseCommandResult.error("We dont have SetKeyCommand" + dbName); } - byte[] value = commandargs.get(DatabaseCommandArgPositions.VALUE.getPositionIndex()).asString().getBytes(StandardCharsets.UTF_8); - dataBase.get().write(tbName, key, value); - return DatabaseCommandResult.success(("Success add key " + dbName + tbName + key).getBytes(StandardCharsets.UTF_8)); - } catch (DatabaseException ex) { - return new FailedDatabaseCommandResult(ex.getMessage()); + Optional previous = database.get().read(tbName, key); + database.get().write(tbName, key, value.getBytes(StandardCharsets.UTF_8)); + return DatabaseCommandResult.success(previous.orElse(null)); + } catch (DatabaseException e){ + return DatabaseCommandResult.error("Error when try to set value by key"); } } } diff --git a/src/main/java/com/itmo/java/basics/console/impl/SuccessDatabaseCommandResult.java b/src/main/java/com/itmo/java/basics/console/impl/SuccessDatabaseCommandResult.java index ae8fad44..5ac44ad7 100644 --- a/src/main/java/com/itmo/java/basics/console/impl/SuccessDatabaseCommandResult.java +++ b/src/main/java/com/itmo/java/basics/console/impl/SuccessDatabaseCommandResult.java @@ -4,23 +4,24 @@ import com.itmo.java.protocol.model.RespBulkString; import com.itmo.java.protocol.model.RespObject; +import java.nio.charset.StandardCharsets; + /** * Результат успешной команды */ public class SuccessDatabaseCommandResult implements DatabaseCommandResult { - private final byte[] payLoad; public SuccessDatabaseCommandResult(byte[] pload) { - payLoad = pload; + payLoad = pload; } @Override public String getPayLoad() { - if(payLoad != null) { - return new String(payLoad); - } else { + if (payLoad == null) { return null; + } else { + return new String(payLoad, StandardCharsets.UTF_8); } } diff --git a/src/main/java/com/itmo/java/basics/index/KvsIndex.java b/src/main/java/com/itmo/java/basics/index/KvsIndex.java index 15534257..a20ba4b9 100644 --- a/src/main/java/com/itmo/java/basics/index/KvsIndex.java +++ b/src/main/java/com/itmo/java/basics/index/KvsIndex.java @@ -18,4 +18,4 @@ public interface KvsIndex { * @return {@code Optional} */ Optional searchForKey(K key); -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/basics/initialization/impl/DatabaseServerInitializer.java b/src/main/java/com/itmo/java/basics/initialization/impl/DatabaseServerInitializer.java index 72f311d7..1ac3c494 100644 --- a/src/main/java/com/itmo/java/basics/initialization/impl/DatabaseServerInitializer.java +++ b/src/main/java/com/itmo/java/basics/initialization/impl/DatabaseServerInitializer.java @@ -27,29 +27,26 @@ public DatabaseServerInitializer(DatabaseInitializer databaseInitializer) { @Override public void perform(InitializationContext context) throws DatabaseException { - if (context.executionEnvironment() == null) { - throw new DatabaseException("Context executionEnvironment is null"); - } +// if (context.executionEnvironment() == null) { +// throw new DatabaseException("Context executionEnvironment is null"); +// } ExecutionEnvironment ExecutionEnvironment = context.executionEnvironment(); Path path = ExecutionEnvironment.getWorkingPath(); + File workingDir = new File(String.valueOf(path)); - if (!Files.exists(path)) { - try { - Files.createDirectory(path); - } catch (IOException ex) { - throw new DatabaseException("Error while creating " + path.toString(), ex); + if (!workingDir.exists()) { + if (!workingDir.mkdir()) { + throw new DatabaseException("While creating dir DVSIit"); } } - File curFile = new File(path.toString()); - File[] directory = curFile.listFiles(); - if (directory == null) { - throw new DatabaseException("Error while working with" + curFile.toString()); - } - - for (File in : directory) { - DatabaseInitializationContextImpl dbContext = new DatabaseInitializationContextImpl(in.getName(), path); - databaseInitializer.perform(new InitializationContextImpl(context.executionEnvironment(), dbContext, null, null)); + File[] curFiles = workingDir.listFiles(File::isDirectory); + for (File dir : curFiles) { + DatabaseInitializationContextImpl dbContext = new DatabaseInitializationContextImpl(dir.getName(),path); + InitializationContextImpl newContext = InitializationContextImpl.builder() + .currentDatabaseContext(dbContext) + .executionEnvironment(context.executionEnvironment()).build(); + databaseInitializer.perform(newContext); } } } diff --git a/src/main/java/com/itmo/java/basics/initialization/impl/InitializationContextImpl.java b/src/main/java/com/itmo/java/basics/initialization/impl/InitializationContextImpl.java index 114e0dae..bc67bfb8 100644 --- a/src/main/java/com/itmo/java/basics/initialization/impl/InitializationContextImpl.java +++ b/src/main/java/com/itmo/java/basics/initialization/impl/InitializationContextImpl.java @@ -5,6 +5,7 @@ import com.itmo.java.basics.initialization.InitializationContext; import com.itmo.java.basics.initialization.SegmentInitializationContext; import com.itmo.java.basics.initialization.TableInitializationContext; +import lombok.Builder; public class InitializationContextImpl implements InitializationContext { @@ -12,7 +13,7 @@ public class InitializationContextImpl implements InitializationContext { private final DatabaseInitializationContext currentDatabaseContext; private final TableInitializationContext currentTableContext; private final SegmentInitializationContext currentSegmentContext; - +@Builder public InitializationContextImpl(ExecutionEnvironment executionEnvironment, DatabaseInitializationContext currentDatabaseContext, TableInitializationContext currentTableContext, diff --git a/src/main/java/com/itmo/java/basics/initialization/impl/SegmentInitializer.java b/src/main/java/com/itmo/java/basics/initialization/impl/SegmentInitializer.java index 90409c2a..47b6be78 100644 --- a/src/main/java/com/itmo/java/basics/initialization/impl/SegmentInitializer.java +++ b/src/main/java/com/itmo/java/basics/initialization/impl/SegmentInitializer.java @@ -63,7 +63,7 @@ public void perform(InitializationContext context) throws DatabaseException { Segment newSegment = SegmentImpl.initializeFromContext(new SegmentInitializationContextImpl(segmentinitialContext.getSegmentName(), segmentinitialContext.getSegmentPath(), size, segIndex)); for (String in : keys) { - context.currentTableContext().getTableIndex().onIndexedEntityUpdated(in, segment); + context.currentTableContext().getTableIndex().onIndexedEntityUpdated(in,segment); } context.currentTableContext().updateCurrentSegment(newSegment); } diff --git a/src/main/java/com/itmo/java/basics/logic/Database.java b/src/main/java/com/itmo/java/basics/logic/Database.java index 912a1536..893b1416 100644 --- a/src/main/java/com/itmo/java/basics/logic/Database.java +++ b/src/main/java/com/itmo/java/basics/logic/Database.java @@ -2,6 +2,7 @@ import com.itmo.java.basics.exceptions.DatabaseException; +import java.io.IOException; import java.util.Optional; public interface Database { @@ -18,7 +19,7 @@ public interface Database { * @param tableName имя таблицы * @throws DatabaseException если таблица с данным именем уже существует или если произошла ошибка ввода-вывода */ - void createTableIfNotExists(String tableName) throws DatabaseException, IOException; + void createTableIfNotExists(String tableName) throws DatabaseException; /** * Записывает значение в указанную таблицу по переданному ключу. @@ -28,7 +29,7 @@ public interface Database { * @param objectValue значение, которое нужно записать * @throws DatabaseException если указанная таблица не была найдена или если произошла ошибка ввода-вывода */ - void write(String tableName, String objectKey, byte[] objectValue) throws DatabaseException, IOException; + void write(String tableName, String objectKey, byte[] objectValue) throws DatabaseException; /** * Считывает значение из указанной таблицы по заданному ключу. @@ -36,9 +37,9 @@ public interface Database { * @param tableName таблица, из которой нужно считать значение * @param objectKey ключ, по которому нужно получить значение * @return значение, которое находится по ключу - * @throws DatabaseException если не была найдена указанная таблица, или произошла ошибка ввода-вывода + * @throws DatabaseException если не была найдена указанная таблица, или если не была найдена запись по данному ключу, или произошла ошибка ввода-вывода */ - Optional read(String tableName, String objectKey) throws DatabaseException, IOException; + Optional read(String tableName, String objectKey) throws DatabaseException; - void delete(String tableName, String objectKey) throws DatabaseException, IOException; + void delete(String tableName, String objectKey) throws DatabaseException ; } \ No newline at end of file diff --git a/src/main/java/com/itmo/java/basics/logic/Segment.java b/src/main/java/com/itmo/java/basics/logic/Segment.java index 6168fdcb..128db64d 100644 --- a/src/main/java/com/itmo/java/basics/logic/Segment.java +++ b/src/main/java/com/itmo/java/basics/logic/Segment.java @@ -1,15 +1,9 @@ package com.itmo.java.basics.logic; + import java.io.IOException; import java.util.Optional; -/** - * Сегмент - append-only файл, хранящий пары ключ-значение, разделенные специальным символом. - * - имеет ограниченный размер, большие значения (>100000) записываются в последний сегмент, если он не read-only - * - при превышении размера сегмента создается новый сегмент и дальнейшие операции записи производятся в него - * - именование файла-сегмента должно позволять установить очередность их появления - * - является неизменяемым после появления более нового сегмента - */ public interface Segment { /** * Возвращает имя сегмента. @@ -18,6 +12,9 @@ public interface Segment { */ String getName(); + // todo sukhoa in future may return something like SegmentWriteResult .. with report and error details? + // for new returns false if cannot allocate requested capacity + // exception is questionable /** * Записывает значение по указанному ключу в сегмент. * diff --git a/src/main/java/com/itmo/java/basics/logic/Table.java b/src/main/java/com/itmo/java/basics/logic/Table.java index 37e82917..cc70e342 100644 --- a/src/main/java/com/itmo/java/basics/logic/Table.java +++ b/src/main/java/com/itmo/java/basics/logic/Table.java @@ -2,6 +2,7 @@ import com.itmo.java.basics.exceptions.DatabaseException; +import java.io.IOException; import java.util.Optional; /** @@ -28,17 +29,17 @@ public interface Table { * @param objectValue значение, которое нужно записать * @throws DatabaseException если произошла ошибка ввода-вывода */ - void write(String objectKey, byte[] objectValue) throws DatabaseException, IOException; + void write(String objectKey, byte[] objectValue) throws DatabaseException; /** * Считывает значение из таблицы по заданному ключу. * * @param objectKey ключ, по которому нужно получить значение * @return значение, которое находится по ключу - * @throws DatabaseException если произошла ошибка ввода-вывода + * @throws DatabaseException если не была найдена запись по данному ключу или произошла ошибка ввода-вывода */ - Optional read(String objectKey) throws DatabaseException, IOException; + Optional read(String objectKey) throws DatabaseException; - void delete(String objectKey) throws DatabaseException, IOException; + void delete(String objectKey) throws DatabaseException; } diff --git a/src/main/java/com/itmo/java/basics/logic/impl/CachingTable.java b/src/main/java/com/itmo/java/basics/logic/impl/CachingTable.java index 05f09cbc..5d0e92dc 100644 --- a/src/main/java/com/itmo/java/basics/logic/impl/CachingTable.java +++ b/src/main/java/com/itmo/java/basics/logic/impl/CachingTable.java @@ -4,6 +4,7 @@ import com.itmo.java.basics.logic.DatabaseCache; import com.itmo.java.basics.logic.Table; + import java.util.Optional; public class CachingTable implements Table { diff --git a/src/main/java/com/itmo/java/basics/logic/impl/DatabaseImpl.java b/src/main/java/com/itmo/java/basics/logic/impl/DatabaseImpl.java index 69f8b702..14f5ffb2 100644 --- a/src/main/java/com/itmo/java/basics/logic/impl/DatabaseImpl.java +++ b/src/main/java/com/itmo/java/basics/logic/impl/DatabaseImpl.java @@ -72,7 +72,7 @@ public void createTableIfNotExists(String tableName) throws DatabaseException { } @Override - public void write(String tableName, String objectKey, byte[] objectValue) throws DatabaseException { + public void write(String tableName, String objectKey, byte[] objectValue) throws DatabaseException{ if (tableName == null) { throw new DatabaseException("Error while writing in , null name"); @@ -95,7 +95,7 @@ public Optional read(String tableName, String objectKey) throws Database } @Override - public void delete(String tableName, String objectKey) throws DatabaseException { + public void delete(String tableName, String objectKey) throws DatabaseException{ if (!tableDictionary.containsKey(tableName)) { throw new DatabaseException("Table " + tableName + " doesnt exist in database" + dbName); } diff --git a/src/main/java/com/itmo/java/basics/logic/impl/RemoveDatabaseRecord.java b/src/main/java/com/itmo/java/basics/logic/impl/RemoveDatabaseRecord.java index 6d09b739..3bccb5fa 100644 --- a/src/main/java/com/itmo/java/basics/logic/impl/RemoveDatabaseRecord.java +++ b/src/main/java/com/itmo/java/basics/logic/impl/RemoveDatabaseRecord.java @@ -43,4 +43,4 @@ public int getKeySize() { public int getValueSize() { return -1; } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/basics/logic/io/DatabaseOutputStream.java b/src/main/java/com/itmo/java/basics/logic/io/DatabaseOutputStream.java index 5c46dfa5..ec1ad1c4 100644 --- a/src/main/java/com/itmo/java/basics/logic/io/DatabaseOutputStream.java +++ b/src/main/java/com/itmo/java/basics/logic/io/DatabaseOutputStream.java @@ -1,5 +1,4 @@ package com.itmo.java.basics.logic.io; - import com.itmo.java.basics.logic.WritableDatabaseRecord; import java.io.DataOutputStream; diff --git a/src/main/java/com/itmo/java/basics/resp/CommandReader.java b/src/main/java/com/itmo/java/basics/resp/CommandReader.java new file mode 100644 index 00000000..a4078fc8 --- /dev/null +++ b/src/main/java/com/itmo/java/basics/resp/CommandReader.java @@ -0,0 +1,48 @@ +package com.itmo.java.basics.resp; + +import com.itmo.java.basics.console.DatabaseCommand; +import com.itmo.java.basics.console.DatabaseCommandArgPositions; +import com.itmo.java.basics.console.DatabaseCommands; +import com.itmo.java.basics.console.ExecutionEnvironment; +import com.itmo.java.protocol.RespReader; +import com.itmo.java.protocol.model.RespObject; + +import java.io.IOException; +import java.util.List; + +public class CommandReader implements AutoCloseable { + private final RespReader reader; + private final ExecutionEnvironment env; + + public CommandReader(RespReader reader, ExecutionEnvironment env) { + this.reader = reader; + this.env = env; + } + + /** + * Есть ли следующая команда в ридере? + */ + public boolean hasNextCommand() throws IOException { + return this.reader.hasArray(); + } + + /** + * Считывает комманду с помощью ридера и возвращает ее + * + * @throws IllegalArgumentException если нет имени команды и id + */ + public DatabaseCommand readCommand() throws IOException { + final List commandArgs = this.reader.readArray().getObjects(); + if (commandArgs.size() < 2) { + throw new IllegalArgumentException("There is no id and command name"); + } + return DatabaseCommands.valueOf(commandArgs + .get(DatabaseCommandArgPositions.COMMAND_NAME.getPositionIndex()).asString()) + .getCommand(env, commandArgs); + } + + @Override + public void close() throws Exception { + this.reader.close(); + } +} diff --git a/src/main/java/com/itmo/java/client/client/SimpleKvsClient.java b/src/main/java/com/itmo/java/client/client/SimpleKvsClient.java index 0cd550af..72fcc2ae 100644 --- a/src/main/java/com/itmo/java/client/client/SimpleKvsClient.java +++ b/src/main/java/com/itmo/java/client/client/SimpleKvsClient.java @@ -21,7 +21,7 @@ public class SimpleKvsClient implements KvsClient { * @param connectionSupplier метод создания подключения к базе */ public SimpleKvsClient(String databaseName, Supplier connectionSupplier) { - dbName = databaseName; + this.dbName = databaseName; this.connectionSupplier = connectionSupplier.get(); } @@ -48,7 +48,7 @@ public String createTable(String tableName) throws DatabaseExecutionException { throw new DatabaseExecutionException(obj.asString()); } return obj.asString(); - } catch (ConnectionException ex) { + } catch (ConnectionException ex){ return DatabaseCommandResult.error(ex).getPayLoad(); } } @@ -94,4 +94,4 @@ public String delete(String tableName, String key) throws DatabaseExecutionExcep return DatabaseCommandResult.error(ex).getPayLoad(); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/command/CreateDatabaseKvsCommand.java b/src/main/java/com/itmo/java/client/command/CreateDatabaseKvsCommand.java index 964abfab..18934cd9 100644 --- a/src/main/java/com/itmo/java/client/command/CreateDatabaseKvsCommand.java +++ b/src/main/java/com/itmo/java/client/command/CreateDatabaseKvsCommand.java @@ -10,10 +10,9 @@ * Команда для создания бд */ public class CreateDatabaseKvsCommand implements KvsCommand { - private static final String COMMAND_NAME = "CREATE_DATABASE"; - private final String dbName; - private final int dbID; + private final String databaseName; + private final int commandId; /** * Создает объект @@ -21,8 +20,8 @@ public class CreateDatabaseKvsCommand implements KvsCommand { * @param databaseName имя базы данных */ public CreateDatabaseKvsCommand(String databaseName) { - dbID = idGen.get(); - dbName = databaseName; + this.databaseName = databaseName; + this.commandId = idGen.getAndIncrement(); } /** @@ -32,13 +31,13 @@ public CreateDatabaseKvsCommand(String databaseName) { */ @Override public RespArray serialize() { - return new RespArray(new RespCommandId(dbID), + return new RespArray(new RespCommandId(commandId), new RespBulkString(COMMAND_NAME.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(dbName.getBytes(StandardCharsets.UTF_8))); + new RespBulkString(databaseName.getBytes(StandardCharsets.UTF_8))); } @Override public int getCommandId() { - return dbID; + return commandId; } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/command/CreateTableKvsCommand.java b/src/main/java/com/itmo/java/client/command/CreateTableKvsCommand.java index edcd1746..51068cd2 100644 --- a/src/main/java/com/itmo/java/client/command/CreateTableKvsCommand.java +++ b/src/main/java/com/itmo/java/client/command/CreateTableKvsCommand.java @@ -10,16 +10,15 @@ * Команда для создания таблицы */ public class CreateTableKvsCommand implements KvsCommand { - private static final String COMMAND_NAME = "CREATE_TABLE"; - private final String dbName; - private final int tbID; - private final String tbName; + private final String databaseName; + private final String tableName; + private final int commandId; public CreateTableKvsCommand(String databaseName, String tableName) { - dbName = databaseName; - tbName = tableName; - tbID = idGen.get(); + this.databaseName = databaseName; + this.tableName = tableName; + this.commandId = idGen.getAndIncrement(); } /** @@ -29,14 +28,14 @@ public CreateTableKvsCommand(String databaseName, String tableName) { */ @Override public RespArray serialize() { - return new RespArray(new RespCommandId(tbID), + return new RespArray(new RespCommandId(commandId), new RespBulkString(COMMAND_NAME.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(dbName.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(tbName.getBytes(StandardCharsets.UTF_8))); + new RespBulkString(databaseName.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(tableName.getBytes(StandardCharsets.UTF_8))); } @Override public int getCommandId() { - return tbID; + return commandId; } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/command/DeleteKvsCommand.java b/src/main/java/com/itmo/java/client/command/DeleteKvsCommand.java index ea7e1827..cbd51fe0 100644 --- a/src/main/java/com/itmo/java/client/command/DeleteKvsCommand.java +++ b/src/main/java/com/itmo/java/client/command/DeleteKvsCommand.java @@ -7,18 +7,17 @@ import java.nio.charset.StandardCharsets; public class DeleteKvsCommand implements KvsCommand { - private static final String COMMAND_NAME = "DELETE_KEY"; - private final String dbName; - private final String tbName; - private final String Key; - private final int delID; + private final String databaseName; + private final String tableName; + private final String key; + private final int commandId; public DeleteKvsCommand(String databaseName, String tableName, String key) { - dbName = databaseName; - tbName = tableName; - Key = key; - delID = idGen.get(); + this.databaseName = databaseName; + this.tableName = tableName; + this.key = key; + this.commandId = idGen.getAndIncrement(); } /** @@ -28,15 +27,15 @@ public DeleteKvsCommand(String databaseName, String tableName, String key) { */ @Override public RespArray serialize() { - return new RespArray(new RespCommandId(delID), + return new RespArray(new RespCommandId(commandId), new RespBulkString(COMMAND_NAME.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(dbName.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(tbName.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(Key.getBytes(StandardCharsets.UTF_8))); + new RespBulkString(databaseName.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(tableName.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(key.getBytes(StandardCharsets.UTF_8))); } @Override public int getCommandId() { - return delID; + return commandId; } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/command/GetKvsCommand.java b/src/main/java/com/itmo/java/client/command/GetKvsCommand.java index 43009e30..56e70a67 100644 --- a/src/main/java/com/itmo/java/client/command/GetKvsCommand.java +++ b/src/main/java/com/itmo/java/client/command/GetKvsCommand.java @@ -9,16 +9,16 @@ public class GetKvsCommand implements KvsCommand { private static final String COMMAND_NAME = "GET_KEY"; - private final String dbName; - private final String tbName; - private final String Key; - private final int getID; + private final String databaseName; + private final String tableName; + private final String key; + private final int commandId; public GetKvsCommand(String databaseName, String tableName, String key) { - dbName = databaseName; - tbName = tableName; - Key = key; - getID = idGen.get(); + this.databaseName = databaseName; + this.tableName = tableName; + this.key = key; + this.commandId = idGen.getAndIncrement(); } /** @@ -28,15 +28,15 @@ public GetKvsCommand(String databaseName, String tableName, String key) { */ @Override public RespArray serialize() { - return new RespArray(new RespCommandId(getID), + return new RespArray(new RespCommandId(commandId), new RespBulkString(COMMAND_NAME.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(dbName.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(tbName.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(Key.getBytes(StandardCharsets.UTF_8))); + new RespBulkString(databaseName.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(tableName.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(key.getBytes(StandardCharsets.UTF_8))); } @Override public int getCommandId() { - return getID; + return commandId; } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/command/SetKvsCommand.java b/src/main/java/com/itmo/java/client/command/SetKvsCommand.java index cb704e90..316c4981 100644 --- a/src/main/java/com/itmo/java/client/command/SetKvsCommand.java +++ b/src/main/java/com/itmo/java/client/command/SetKvsCommand.java @@ -9,18 +9,18 @@ public class SetKvsCommand implements KvsCommand { private static final String COMMAND_NAME = "SET_KEY"; - private final String dbName; - private final String tbName; - private final String Key; - private final int setID; - private final String Value; + private final String databaseName; + private final String tableName; + private final String key; + private final String value; + private final int commandId; public SetKvsCommand(String databaseName, String tableName, String key, String value) { - dbName = databaseName; - tbName = tableName; - Key = key; - setID = idGen.get(); - Value = value; + this.databaseName = databaseName; + this.tableName = tableName; + this.key = key; + this.value = value; + this.commandId = idGen.getAndIncrement(); } /** @@ -30,16 +30,16 @@ public SetKvsCommand(String databaseName, String tableName, String key, String v */ @Override public RespArray serialize() { - return new RespArray(new RespCommandId(setID), + return new RespArray(new RespCommandId(commandId), new RespBulkString(COMMAND_NAME.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(dbName.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(tbName.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(Key.getBytes(StandardCharsets.UTF_8)), - new RespBulkString(Value.getBytes(StandardCharsets.UTF_8))); + new RespBulkString(databaseName.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(tableName.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(key.getBytes(StandardCharsets.UTF_8)), + new RespBulkString(value.getBytes(StandardCharsets.UTF_8))); } @Override public int getCommandId() { - return setID; + return commandId; } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/connection/ConnectionConfig.java b/src/main/java/com/itmo/java/client/connection/ConnectionConfig.java new file mode 100644 index 00000000..776ca8ab --- /dev/null +++ b/src/main/java/com/itmo/java/client/connection/ConnectionConfig.java @@ -0,0 +1,22 @@ +package com.itmo.java.client.connection; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +/** + * Класс содержит информацию, что слушает сервер, по какому адресу с ним взаимодействовать. + * (По идее они должны совпадать с тем, какие мы используем в server.properties) + */ +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor +public class ConnectionConfig { + public static final String DEFAULT_HOST = "localhost"; + public static final int DEFAULT_PORT = 8080; + + private final String host; + private final int port; +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/connection/DirectReferenceKvsConnection.java b/src/main/java/com/itmo/java/client/connection/DirectReferenceKvsConnection.java index 40d0ccbd..f2837885 100644 --- a/src/main/java/com/itmo/java/client/connection/DirectReferenceKvsConnection.java +++ b/src/main/java/com/itmo/java/client/connection/DirectReferenceKvsConnection.java @@ -3,6 +3,7 @@ import com.itmo.java.basics.DatabaseServer; import com.itmo.java.client.exception.ConnectionException; import com.itmo.java.protocol.model.RespArray; +import com.itmo.java.protocol.model.RespCommandId; import com.itmo.java.protocol.model.RespObject; import java.util.concurrent.ExecutionException; @@ -12,19 +13,22 @@ * (пока еще нет реализации сокетов) */ public class DirectReferenceKvsConnection implements KvsConnection { - - private final DatabaseServer dbServer; + private final DatabaseServer databaseServer; public DirectReferenceKvsConnection(DatabaseServer databaseServer) { - dbServer = databaseServer; + this.databaseServer = databaseServer; } @Override public RespObject send(int commandId, RespArray command) throws ConnectionException { try { - return dbServer.executeNextCommand(command).get().serialize(); - } catch (ExecutionException | InterruptedException ex) { - throw new ConnectionException(ex.getMessage(), ex.getCause()); + return databaseServer.executeNextCommand(command).get().serialize(); + } catch (InterruptedException e) { + throw new ConnectionException("ConnectionException when try to get result from server because of interruption when message is '" + + command.asString() + "'", e); + } catch (ExecutionException e) { + throw new ConnectionException("ConnectionException when try to get result from server because of ExecutionException when message is '" + + command.asString() + "'", e); } } @@ -34,4 +38,4 @@ public RespObject send(int commandId, RespArray command) throws ConnectionExcept @Override public void close() { } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/connection/SocketKvsConnection.java b/src/main/java/com/itmo/java/client/connection/SocketKvsConnection.java new file mode 100644 index 00000000..f2a9eb6b --- /dev/null +++ b/src/main/java/com/itmo/java/client/connection/SocketKvsConnection.java @@ -0,0 +1,67 @@ +package com.itmo.java.client.connection; + +import com.itmo.java.client.exception.ConnectionException; +import com.itmo.java.protocol.RespReader; +import com.itmo.java.protocol.RespWriter; +import com.itmo.java.protocol.model.RespArray; +import com.itmo.java.protocol.model.RespObject; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * С помощью {@link RespWriter} и {@link RespReader} читает/пишет в сокет + */ +public class SocketKvsConnection implements KvsConnection { + private final int port; + private final String host; + private final Socket clientSocket; + private final RespWriter respWriter; + private final RespReader respReader; + + public SocketKvsConnection(ConnectionConfig config) { + this.port = config.getPort(); + this.host = config.getHost(); + try { + this.clientSocket = new Socket(host, port); + this.respReader = new RespReader(clientSocket.getInputStream()); + respWriter = new RespWriter(clientSocket.getOutputStream()); + } catch (IOException e) { + throw new RuntimeException("IOException when try to connect by " + host + " " + port, e); + } + } + + /** + * Отправляет с помощью сокета команду и получает результат. + * @param commandId id команды (номер) + * @param command команда + * @throws ConnectionException если сокет закрыт или если произошла другая ошибка соединения + */ + @Override + public synchronized RespObject send(int commandId, RespArray command) throws ConnectionException { + try { + RespWriter respWriter = new RespWriter(clientSocket.getOutputStream()); + respWriter.write(command); + RespReader respReader = new RespReader(clientSocket.getInputStream()); + return respReader.readObject(); + } catch (IOException e) { + close(); + throw new ConnectionException("IOException when send " + command.asString() + " with " + host + " and port " + port + " ___IOMessage___: " + e.getMessage(), e); + } + } + + /** + * Закрывает сокет (и другие использованные ресурсы) + */ + @Override + public void close() { + try { + respWriter.close(); + respReader.close(); + clientSocket.close(); + } catch (IOException e) { + throw new RuntimeException("IOException when try to close client socket"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/client/exception/ConnectionException.java b/src/main/java/com/itmo/java/client/exception/ConnectionException.java index 5cb9da57..da99fdc8 100644 --- a/src/main/java/com/itmo/java/client/exception/ConnectionException.java +++ b/src/main/java/com/itmo/java/client/exception/ConnectionException.java @@ -4,6 +4,10 @@ * Ошибка подключения */ public class ConnectionException extends Exception { + public ConnectionException(String message) { + super(message); + } + public ConnectionException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/com/itmo/java/protocol/RespReader.java b/src/main/java/com/itmo/java/protocol/RespReader.java new file mode 100644 index 00000000..435c406f --- /dev/null +++ b/src/main/java/com/itmo/java/protocol/RespReader.java @@ -0,0 +1,156 @@ +package com.itmo.java.protocol; + +import com.itmo.java.protocol.model.*; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +public class RespReader implements AutoCloseable { + private static final int READ_AHEAD_LIMIT = 3; + private final BufferedReader reader; + + /** + * Специальные символы окончания элемента + */ + private static final byte CR = '\r'; + private static final byte LF = '\n'; + + public RespReader(InputStream is) { + reader = new BufferedReader(new InputStreamReader(is)); + } + + /** + * Есть ли следующий массив в стриме? + */ + public boolean hasArray() throws IOException { + byte code = (byte) reader.read(); + return code == RespArray.CODE; + } + + /** + * Считывает из input stream следующий объект. Может прочитать любой объект, сам определит его тип на основе кода объекта. + * Например, если первый элемент "-", то вернет ошибку. Если "$" - bulk строку + * + * @throws EOFException если stream пустой + * @throws IOException при ошибке чтения + */ + public RespObject readObject() throws IOException { + int codeInt = reader.read(); + if (codeInt == -1) { + throw new EOFException("InputStream is empty when try to read RespObject"); + } + byte code = (byte) codeInt; + switch (code) { + case RespArray.CODE: + return readArray(); + case RespBulkString.CODE: + return readBulkString(); + case RespCommandId.CODE: + return readCommandId(); + case RespError.CODE: + return readError(); + default: + throw new IOException("Code character is not correct"); + } + } + + /** + * Считывает объект ошибки + * + * @throws EOFException если stream пустой + * @throws IOException при ошибке чтения + */ + public RespError readError() throws IOException { + return new RespError(readBytesToEndOfLine()); + } + + /** + * Читает bulk строку + * + * @throws EOFException если stream пустой + * @throws IOException при ошибке чтения + */ + public RespBulkString readBulkString() throws IOException { + byte[] stringSizeBytes = readBytesToEndOfLine(); + int stringSize = Integer.parseInt(new String(stringSizeBytes, StandardCharsets.UTF_8)); + if (stringSize == RespBulkString.NULL_STRING_SIZE) { + return RespBulkString.NULL_STRING; + } + byte[] stringData = readBytesToEndOfLine(); + if (stringData.length != stringSize) { + throw new IOException("String length is not equal with StringBulk size"); + } + return new RespBulkString(stringData); + } + + /** + * Считывает массив RESP элементов + * + * @throws EOFException если stream пустой + * @throws IOException при ошибке чтения + */ + public RespArray readArray() throws IOException { + byte[] arraySizeBytes = readBytesToEndOfLine(); + int arraySize = Integer.parseInt(new String(arraySizeBytes, StandardCharsets.UTF_8)); + RespObject[] respObjectArray = new RespObject[arraySize]; + for (int i = 0; i < arraySize; i++) { + respObjectArray[i] = readObject(); + } + return new RespArray(respObjectArray); + } + + /** + * Считывает id команды + * + * @throws EOFException если stream пустой + * @throws IOException при ошибке чтения + */ + public RespCommandId readCommandId() throws IOException { + byte[] idBytes = readBytesToEndOfLine(); + if (idBytes.length != 4) { + throw new IOException("Command Id is not integer"); + } + return new RespCommandId(bytesToInt(idBytes)); + } + + + @Override + public void close() throws IOException { + reader.close(); + } + + private static int bytesToInt(byte[] bytes) { + return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | (bytes[3]); + } + + private byte[] readBytesToEndOfLine() throws IOException { + ArrayList message = new ArrayList<>(); + while (true) { + int currentByte = reader.read(); + if (currentByte == -1) { + throw new EOFException("Stream is empty when try to read "); + } + if (currentByte == CR) { + reader.mark(READ_AHEAD_LIMIT); + int nextByte = reader.read(); + if (nextByte == -1) { + throw new EOFException("Stream is empty when try to read "); + } + if (nextByte == LF) { + reader.reset(); + reader.read(); + break; + } else { + reader.reset(); + } + } + message.add((byte) currentByte); + } + byte[] bytes = new byte[message.size()]; + for (int i = 0; i < message.size(); i++) { + bytes[i] = message.get(i); + } + return bytes; + } +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/protocol/RespWriter.java b/src/main/java/com/itmo/java/protocol/RespWriter.java new file mode 100644 index 00000000..61da5bda --- /dev/null +++ b/src/main/java/com/itmo/java/protocol/RespWriter.java @@ -0,0 +1,26 @@ +package com.itmo.java.protocol; + +import com.itmo.java.protocol.model.RespObject; + +import java.io.IOException; +import java.io.OutputStream; + +public class RespWriter implements AutoCloseable { + private final OutputStream outputStream; + + public RespWriter(OutputStream os) { + this.outputStream = os; + } + + /** + * Записывает в output stream объект + */ + public void write(RespObject object) throws IOException { + object.write(outputStream); + } + + @Override + public void close() throws IOException { + outputStream.close(); + } +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/protocol/model/RespArray.java b/src/main/java/com/itmo/java/protocol/model/RespArray.java index a3200f35..6399fb00 100644 --- a/src/main/java/com/itmo/java/protocol/model/RespArray.java +++ b/src/main/java/com/itmo/java/protocol/model/RespArray.java @@ -5,21 +5,20 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; /** * Массив RESP объектов */ public class RespArray implements RespObject { + private final List objects; /** * Код объекта */ public static final byte CODE = '*'; - private List objects; - public RespArray(RespObject... obj) { - objects = Arrays.asList(obj); + public RespArray(RespObject... objects) { + this.objects = Arrays.asList(objects); } /** @@ -39,20 +38,27 @@ public boolean isError() { */ @Override public String asString() { - return objects.stream().map(object -> asString()).collect(Collectors.joining(" ")); + StringBuilder stringBuilder = new StringBuilder(); + for (RespObject object : objects) { + stringBuilder.append(object.asString()); + stringBuilder.append(" "); + } + stringBuilder.deleteCharAt(stringBuilder.length() - 1); + return stringBuilder.toString(); } @Override - public void write(OutputStream output) throws IOException { - output.write(CODE); - output.write(Integer.toString(objects.size()).getBytes(StandardCharsets.UTF_8)); - output.write(CRLF); - for (RespObject obj : objects) { - obj.write(output); + public void write(OutputStream os) throws IOException { + os.write(CODE); + os.write(String.valueOf(objects.size()).getBytes(StandardCharsets.UTF_8)); + os.write(CRLF); + os.flush(); + for (RespObject object : objects) { + object.write(os); } } public List getObjects() { return objects; } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/protocol/model/RespBulkString.java b/src/main/java/com/itmo/java/protocol/model/RespBulkString.java index c3a53d83..da2d6ee1 100644 --- a/src/main/java/com/itmo/java/protocol/model/RespBulkString.java +++ b/src/main/java/com/itmo/java/protocol/model/RespBulkString.java @@ -8,16 +8,19 @@ * Строка */ public class RespBulkString implements RespObject { + private final byte[] data; + /** * Код объекта */ public static final byte CODE = '$'; + public static final int NULL_STRING_SIZE = -1; - private final byte[] data; + public static final RespBulkString NULL_STRING = new RespBulkString(null); - public RespBulkString(byte[] inform) { - data = inform; + public RespBulkString(byte[] data) { + this.data = data; } /** @@ -37,19 +40,23 @@ public boolean isError() { */ @Override public String asString() { - return new String(data); + if (data == null) { + return null; + } + return new String(data, StandardCharsets.UTF_8); } @Override - public void write(OutputStream output) throws IOException { - output.write(CODE); + public void write(OutputStream os) throws IOException { + os.write(CODE); if (data == null) { - output.write(Integer.toString(NULL_STRING_SIZE).getBytes(StandardCharsets.UTF_8)); + os.write(String.valueOf(NULL_STRING_SIZE).getBytes(StandardCharsets.UTF_8)); } else { - output.write(Integer.toString(data.length).getBytes(StandardCharsets.UTF_8)); - output.write(CRLF); - output.write(data); + os.write(String.valueOf(data.length).getBytes(StandardCharsets.UTF_8)); + os.write(CRLF); + os.write(data); } - output.write(CRLF); + os.write(CRLF); + os.flush(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/protocol/model/RespCommandId.java b/src/main/java/com/itmo/java/protocol/model/RespCommandId.java index d7213a79..50a2913a 100644 --- a/src/main/java/com/itmo/java/protocol/model/RespCommandId.java +++ b/src/main/java/com/itmo/java/protocol/model/RespCommandId.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; /** * Id @@ -41,7 +40,7 @@ public void write(OutputStream output) throws IOException { output.write((commandByte >>> 24) & 0xFF); output.write((commandByte >>> 16) & 0xFF); output.write((commandByte >>> 8) & 0xFF); - output.write(commandByte &0xFF); + output.write(commandByte & 0xFF); output.write(CRLF); } -} +} \ No newline at end of file diff --git a/src/main/java/com/itmo/java/protocol/model/RespError.java b/src/main/java/com/itmo/java/protocol/model/RespError.java index 8688757d..68740e83 100644 --- a/src/main/java/com/itmo/java/protocol/model/RespError.java +++ b/src/main/java/com/itmo/java/protocol/model/RespError.java @@ -2,20 +2,21 @@ import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; /** * Сообщение об ошибке в RESP протоколе */ public class RespError implements RespObject { + private final byte[] message; /** * Код объекта */ public static final byte CODE = '-'; - public byte[] message; - public RespError(byte[] mes) { - message = mes; + public RespError(byte[] message) { + this.message = message; } /** @@ -30,13 +31,17 @@ public boolean isError() { @Override public String asString() { - return new String(message); + if (message == null) { + return null; + } + return new String(message, StandardCharsets.UTF_8); } @Override - public void write(OutputStream output) throws IOException { - output.write(CODE); - output.write(message); - output.write(CRLF); + public void write(OutputStream os) throws IOException { + os.write(CODE); + os.write(message); + os.write(CRLF); + os.flush(); } -} +} \ No newline at end of file