diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 51216a5..0000000 --- a/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -#common -target/ -out/ -classes/ - -#IDEA Project Files -*iml -*iws -.idea -*ipr \ No newline at end of file diff --git a/client/src/main/java/org/hyperskill/database/clientside/Client.java b/client/src/main/java/org/hyperskill/database/clientside/Client.java index 8659582..2b0e814 100644 --- a/client/src/main/java/org/hyperskill/database/clientside/Client.java +++ b/client/src/main/java/org/hyperskill/database/clientside/Client.java @@ -1,7 +1,82 @@ package org.hyperskill.database.clientside; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.google.gson.Gson; +import org.hyperskill.database.common.Request; +import org.hyperskill.database.common.Response; +import org.hyperskill.database.common.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; + public class Client { - public static void main(String[] args) { - System.out.println("Hello World!"); + @Parameter(names = {"-host", "-ip"}, description = "IP address") + String host; + + @Parameter(names = {"-port"}, description = "Port", validateWith = PositiveInteger.class) + int port; + + /* + @Parameter(names = "-t", description = "Command", validateWith = Commands.class) + String command; + + @Parameter(names = "-m", description = "Text to save") + String text; + + @Parameter(names = "-i", description = "Index") + String index; + */ + + @Parameter(names = "-in", description = "Input file") + String inputFile; + + @Parameter(names = "-out", description = "Output file") + String outputFile; + + public static void main(String... argv) { + Client client = new Client(); + JCommander.newBuilder().addObject(client).build().parse(argv); + + if (argv.length < 6) { + System.err.println( + "Usage: java -jar client.jar -ip -port -in [-out ]"); + System.exit(1); + } + + try ( + Socket socket = new Socket(client.host, client.port); + DataInputStream input = new DataInputStream(socket.getInputStream()); + DataOutputStream output = new DataOutputStream(socket.getOutputStream()) + ) { + System.out.println("ifile" + client.inputFile); + + Request request = Util.read(client.inputFile); + Gson gson = new Gson(); + String out = gson.toJson(request); + output.writeUTF(out); + System.out.println("Sent: " + out); + String receivedMsg = input.readUTF(); // response message + System.out.println("Received: " + receivedMsg); + Response response = gson.fromJson(receivedMsg, Response.class); + if (response.isOk()) { + System.out.println("Request was successful"); + if (client.outputFile != null && request.getType()== Request.TYPE.GET){ + System.out.println("Value saved to " + client.outputFile); + Util.write(response, client.outputFile); + } + } else { + System.out.println("Request was not successful"); + } + } catch (IOException e) { + e.printStackTrace(); + } } } + + + + + diff --git a/client/src/main/java/org/hyperskill/database/clientside/Commands.java b/client/src/main/java/org/hyperskill/database/clientside/Commands.java new file mode 100644 index 0000000..a6e32dd --- /dev/null +++ b/client/src/main/java/org/hyperskill/database/clientside/Commands.java @@ -0,0 +1,14 @@ +package org.hyperskill.database.clientside; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +public class Commands implements IParameterValidator { + public void validate(String name, String value) + throws ParameterException { + value = value.trim().toLowerCase(); + if (!(value.equals("set") || value.equals("get") || value.equals("delete"))) { + throw new ParameterException("Parameter " + name + " should be set/get/delete (found " + value + ")"); + } + } +} diff --git a/client/src/main/java/org/hyperskill/database/clientside/PositiveInteger.java b/client/src/main/java/org/hyperskill/database/clientside/PositiveInteger.java new file mode 100644 index 0000000..09661a5 --- /dev/null +++ b/client/src/main/java/org/hyperskill/database/clientside/PositiveInteger.java @@ -0,0 +1,14 @@ +package org.hyperskill.database.clientside; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +public class PositiveInteger implements IParameterValidator { + public void validate(String name, String value) + throws ParameterException { + int n = Integer.parseInt(value); + if (n < 0) { + throw new ParameterException("Parameter " + name + " should be positive (found " + value + ")"); + } + } +} diff --git a/common/src/main/java/org/hyperskill/database/common/Request.java b/common/src/main/java/org/hyperskill/database/common/Request.java new file mode 100644 index 0000000..178a89c --- /dev/null +++ b/common/src/main/java/org/hyperskill/database/common/Request.java @@ -0,0 +1,49 @@ +package org.hyperskill.database.common; + +import java.util.Objects; + +public class Request { + public enum TYPE {SET, GET, DELETE} + private TYPE type; + private String key; + private String value; + + public Request(String type, String key, String value) throws IllegalArgumentException { + Objects.requireNonNull(type); + Objects.requireNonNull(key); + + this.type = validateCommand(type); + + if (this.type == TYPE.SET && value == null) { + throw new IllegalArgumentException("Set command requires value, got null"); + } + this.key = key; + this.value = value; + } + + public TYPE getType() { + return type; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public static TYPE validateCommand(String word) { + switch (word.trim().toLowerCase()) { + case "set": + return TYPE.SET; + case "get": + return TYPE.GET; + case "delete": + return TYPE.DELETE; + default: + throw new IllegalArgumentException(word + " is not allowed command"); + } + + } +} diff --git a/common/src/main/java/org/hyperskill/database/common/Response.java b/common/src/main/java/org/hyperskill/database/common/Response.java new file mode 100644 index 0000000..a7e901d --- /dev/null +++ b/common/src/main/java/org/hyperskill/database/common/Response.java @@ -0,0 +1,48 @@ +package org.hyperskill.database.common; + +public class Response { + public enum TYPE {OK, FAIL} + + public boolean isOk() { + if (this.type == TYPE.OK) { + return true; + } + return false; + } + + public TYPE getType() { + return type; + } + + private TYPE type; + + public String getValue() { + return value; + } + + public String getReason() { + return reason; + } + + private String value; + private String reason; + + public Response(String type, String value, String reason) { + switch (type.trim().toLowerCase()) { + case "ok": + this.type = TYPE.OK; + break; + case "fail": + this.type = TYPE.FAIL; + break; + default: + throw new IllegalArgumentException(type + " is not allowed command"); + } + + if (this.type == TYPE.FAIL && reason == null) { + throw new IllegalArgumentException("FAIL response requires reason, got null"); + } + this.reason = reason; + this.value = value; + } +} diff --git a/common/src/main/java/org/hyperskill/database/common/Util.java b/common/src/main/java/org/hyperskill/database/common/Util.java new file mode 100644 index 0000000..17c20f5 --- /dev/null +++ b/common/src/main/java/org/hyperskill/database/common/Util.java @@ -0,0 +1,58 @@ +package org.hyperskill.database.common; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +public class Util { + public static Request read(String filename) throws IOException, IllegalArgumentException { + Path file = Paths.get(filename); + String command = null; + String key = null; + StringBuilder value = new StringBuilder(); + try (InputStream in = Files.newInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + String line = null; + int lineProcessing = 0; + while ((line = reader.readLine()) != null) { + switch (lineProcessing) { + case 0: + command = line; + lineProcessing++; + break; + case 1: + key = line; + lineProcessing++; + break; + case 2: + value.append(line); + lineProcessing++; + break; + default: + value.append(" "); + value.append(line); + lineProcessing++; + break; + } + } + } + return new Request(command, key, value.toString()); + } + + public static void write(Response response, String outFileName) { + Objects.requireNonNull(response, "Response can't be null in Util.write"); + Objects.requireNonNull(outFileName, "outFileName can't be null in Util.write"); + try (FileWriter fw = new FileWriter(outFileName, false); + BufferedWriter bw = new BufferedWriter(fw); + PrintWriter out = new PrintWriter(bw)) { + out.println(response.getType()); + out.println(response.getValue()); + + } catch (IOException e) { + System.out.println(e.toString()); + } + } +} diff --git a/server/pom.xml b/server/pom.xml index 144a705..ee6d69d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -18,10 +18,6 @@ - - com.google.code.gson - gson - com.beust jcommander @@ -30,6 +26,10 @@ org.hyperskill.database common + + com.google.code.gson + gson + diff --git a/server/src/main/java/org/hyperskill/database/serverside/JsonProtocol.java b/server/src/main/java/org/hyperskill/database/serverside/JsonProtocol.java new file mode 100644 index 0000000..50281f8 --- /dev/null +++ b/server/src/main/java/org/hyperskill/database/serverside/JsonProtocol.java @@ -0,0 +1,11 @@ +package org.hyperskill.database.serverside; + +import com.google.gson.JsonElement; +import org.hyperskill.database.common.Request; +import org.hyperskill.database.common.Response; + +import java.util.Objects; + +public class JsonProtocol { + +} diff --git a/server/src/main/java/org/hyperskill/database/serverside/JsonStorage.java b/server/src/main/java/org/hyperskill/database/serverside/JsonStorage.java new file mode 100644 index 0000000..2b73668 --- /dev/null +++ b/server/src/main/java/org/hyperskill/database/serverside/JsonStorage.java @@ -0,0 +1,81 @@ +package org.hyperskill.database.serverside; + +import com.google.gson.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class JsonStorage { + private static final String FILENAME = "data.json"; + + private static JsonObject storage; + private static ReadWriteLock lock; + private static Lock readLock; + private static Lock writeLock; + + private static final JsonStorage INSTANCE = new JsonStorage(); + + private JsonStorage() { + storage = new JsonObject(); + readData(); + lock = new ReentrantReadWriteLock(); + readLock = lock.readLock(); + writeLock = lock.writeLock(); + } + + public static JsonStorage getINSTANCE() { + return INSTANCE; + } + + public static void set(String property, String value) { + writeLock.lock(); + storage.addProperty(property, value); + commit(); + writeLock.unlock(); + } + + public static JsonElement get(String name) { + readLock.lock(); + JsonElement element = storage.get(name); + readLock.unlock(); + return element; + } + + public static void delete(String name) { + writeLock.lock(); + storage.remove(name); + commit(); + writeLock.unlock(); + } + + private static void commit() { + try (FileOutputStream fos = new FileOutputStream(FILENAME); + OutputStreamWriter isr = new OutputStreamWriter(fos, + StandardCharsets.UTF_8)) { + Gson gson = new Gson(); + gson.toJson(storage, isr); + } catch (Exception ioe) { + System.out.println("save db except"); + ioe.printStackTrace(); + } + } + + private static void readData() { + Gson gson = new GsonBuilder().create(); + Path path = new File(FILENAME).toPath(); + try { + JsonParser parser = new JsonParser(); + Object object = parser.parse(new FileReader(FILENAME)); + storage = (JsonObject) object; + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/server/src/main/java/org/hyperskill/database/serverside/NetworkService.java b/server/src/main/java/org/hyperskill/database/serverside/NetworkService.java new file mode 100644 index 0000000..46ff13e --- /dev/null +++ b/server/src/main/java/org/hyperskill/database/serverside/NetworkService.java @@ -0,0 +1,96 @@ +package org.hyperskill.database.serverside; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import org.hyperskill.database.common.Request; +import org.hyperskill.database.common.Response; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +class NetworkService implements Runnable { + private final ServerSocket serverSocket; + private final ExecutorService pool; + JsonStorage storage; + + + public NetworkService(int port, int poolSize) + throws IOException { + serverSocket = new ServerSocket(port); + pool = Executors.newFixedThreadPool(poolSize); + storage = JsonStorage.getINSTANCE(); + } + + public void run() { // run the service + try { + for (; ; ) { + pool.execute(new Handler(serverSocket.accept(), storage)); + } + } catch (IOException ex) { + pool.shutdown(); + } + } +} + +class Handler implements Runnable { + private final Socket socket; + private JsonStorage storage; + + Handler(Socket socket, JsonStorage storage) { + this.socket = socket; + this.storage = storage; + } + + private static Response processInput(JsonStorage storage, Request input) { + Objects.requireNonNull(input, "Request cannot be empty in JsonProtocol.processInput"); + Response response; + switch (input.getType()) { + case GET: + JsonElement result = storage.get(input.getKey()); + if (result != null) { + response = new Response("ok", result.toString(), null); + } else { + response = new Response("fail", null, "No such key"); + } + break; + case SET: + storage.set(input.getKey(), input.getValue()); + response = new Response("Ok", null, null); + break; + case DELETE: + if (storage.get(input.getKey()) != null) { + storage.delete(input.getKey()); + response = new Response("ok", null, null); + } else { + response = new Response("fail", null, "No such key"); + } + break; + default: + throw new IllegalArgumentException(input.getType().toString() + " is not allowed command"); + } + return response; + } + + public void run() { + try ( + DataInputStream input = new DataInputStream(socket.getInputStream()); + DataOutputStream output = new DataOutputStream(socket.getOutputStream()) + ) { + String inputLine, outputLine; + inputLine = input.readUTF(); // reading a message + Gson gson = new Gson(); + Request request = gson.fromJson(inputLine, Request.class); + Response response = processInput(storage, request); + String out = gson.toJson(response); + output.writeUTF(out); // resend it to the client + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/hyperskill/database/serverside/Server.java b/server/src/main/java/org/hyperskill/database/serverside/Server.java index 122143d..8ba1b9d 100644 --- a/server/src/main/java/org/hyperskill/database/serverside/Server.java +++ b/server/src/main/java/org/hyperskill/database/serverside/Server.java @@ -1,7 +1,73 @@ package org.hyperskill.database.serverside; +import java.io.IOException; + public class Server { public static void main(String[] args) { - System.out.println("Hello World!"); + if (args.length != 1) { + System.err.println("Usage: java Server "); + System.exit(1); + } + + int portNumber = Integer.parseInt(args[0]); + try { + NetworkService nws = new NetworkService(portNumber, 10); + nws.run(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } +/* + + try (ServerSocket server = new ServerSocket(portNumber)) { + while (true) { + try ( + Socket socket = server.accept(); // accepting a new client + DataInputStream input = new DataInputStream(socket.getInputStream()); + DataOutputStream output = new DataOutputStream(socket.getOutputStream()) + ) { + String inputLine; + inputLine = input.readUTF(); // reading a message + Gson gson = new Gson(); + Request request = gson.fromJson(inputLine, Request.class); + Response response = processInput(storage, request); + String out = gson.toJson(response); + output.writeUTF(out); // resend it to the client + } + } + } catch (IOException e) { + e.printStackTrace(); + } + */ + } +/* + private static Response processInput(JsonStorage storage, Request input) { + Objects.requireNonNull(input, "Request cannot be empty in JsonProtocol.processInput"); + Response response; + switch (input.getType()) { + case GET: + JsonElement result = storage.get(input.getKey()); + if (result != null) { + response = new Response("ok", result.toString(), null); + } else { + response = new Response("fail", null, "No such key"); + } + break; + case SET: + storage.set(input.getKey(), input.getValue()); + response = new Response("Ok", null, null); + break; + case DELETE: + if (storage.get(input.getKey()) != null) { + storage.delete(input.getKey()); + response = new Response("ok", null, null); + } else { + response = new Response("fail", null, "No such key"); + } + break; + default: + throw new IllegalArgumentException(input.getType().toString() + " is not allowed command"); + } + return response; } + */ }