diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..c009ab52 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..712ab9d9 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..accd6296 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 00000000..797acea5 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 32f74df9..2ace37f6 100644 --- a/README.md +++ b/README.md @@ -1,82 +1 @@ -# Тестовое задание для Java стажеров - -Привет! - -Мы ищем стажера, который в перспективе станет Junior Java-разработчиком в нашей команде. -Чтобы понять, что мы подходим друг другу, предлагаем вам написать простое web-приложение. Такое задание поможет нам понять, что вы: - -* можете понимать поставленную задачу; -* умеете находить необходимую техническую информацию для реализации решения; -* просто умеете кодить. - -На задание у вас уйдет ориентировочно один-два вечера. Главное условие — решение должно быть написано с использованием платформы JVM. Библиотеки и фреймворки можно выбирать на свой вкус. - -## Что нужно сделать - -Реализовать приложение для автоматизации учёта носков на складе магазина. Кладовщик должен иметь возможность: - -* учесть приход и отпуск носков; -* узнать общее количество носков определенного цвета и состава в данный момент времени. - -Внешний интерфейс приложения представлен в виде HTTP API (REST, если хочется). - -## Список URL HTTP-методов - -### POST /api/socks/income - -Регистрирует приход носков на склад. - -Параметры запроса передаются в теле запроса в виде JSON-объекта со следующими атрибутами: - -* color — цвет носков, строка (например, black, red, yellow); -* cottonPart — процентное содержание хлопка в составе носков, целое число от 0 до 100 (например, 30, 18, 42); -* quantity — количество пар носков, целое число больше 0. - -Результаты: - -* HTTP 200 — удалось добавить приход; -* HTTP 400 — параметры запроса отсутствуют или имеют некорректный формат; -* HTTP 500 — произошла ошибка, не зависящая от вызывающей стороны (например, база данных недоступна). - -### POST /api/socks/outcome - -Регистрирует отпуск носков со склада. Здесь параметры и результаты аналогичные, но общее количество носков указанного цвета и состава не увеличивается, а уменьшается. - -### GET /api/socks - -Возвращает общее количество носков на складе, соответствующих переданным в параметрах критериям запроса. - -Параметры запроса передаются в URL: - -* color — цвет носков, строка; -* operation — оператор сравнения значения количества хлопка в составе носков, одно значение из: moreThan, lessThan, equal; -* cottonPart — значение процента хлопка в составе носков из сравнения. - -Результаты: - -* HTTP 200 — запрос выполнен, результат в теле ответа в виде строкового представления целого числа; -* HTTP 400 — параметры запроса отсутствуют или имеют некорректный формат; -* HTTP 500 — произошла ошибка, не зависящая от вызывающей стороны (например, база данных недоступна). - -Примеры запросов: - -* /api/socks?color=red&operation=moreThan&cottonPart=90 — должен вернуть общее количество красных носков с долей хлопка более 90%; -* /api/socks?color=black&operation=lessThan?cottonPart=10 — должен вернуть общее количество черных носков с долей хлопка менее 10%. - -Для хранения данных системы можно использовать любую реляционную базу данных. Схему БД желательно хранить в репозитории в любом удобном виде. - -## Как это сделать - -Мы ждем, что решение будет: - -* написано на языке Java; -* standalone - состоять из одного выполняемого компонента верхнего уровня; -* headless - без UI; -* оформлено как форк к репозитарию и создан пул реквест. - -Будет плюсом, если: - -* приложение будет основано на Spring(Boot) Framework; -* для версионирования схемы базы данных будет использоваться Liquibase или Flyway; -* база данных будет подниматься рядом с приложением в докер-контейнере; -* приложение будет развернуто на любом облачном сервисе, например Heroku, и его API будет доступно для вызова. +Считает носки гет и пост запросами с помощью сервлетов и джетти \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..dea40267 --- /dev/null +++ b/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + raif + raif + 1.0 + + + 11 + 11 + + + + + + org.eclipse.jetty + jetty-server + 9.3.0.M0 + + + + org.eclipse.jetty + jetty-webapp + 9.3.0.M0 + + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + + maven-assembly-plugin + + + + jar-with-dependencies + + ${basedir} + serverSocks + false + + + main.Main + + + + + + + \ No newline at end of file diff --git a/serverSocks.jar b/serverSocks.jar new file mode 100644 index 00000000..dca7a19d Binary files /dev/null and b/serverSocks.jar differ diff --git a/src/main/java/main/Main.java b/src/main/java/main/Main.java new file mode 100644 index 00000000..460f9821 --- /dev/null +++ b/src/main/java/main/Main.java @@ -0,0 +1,27 @@ +package main; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import servlets.*; +import socks.*; + + + +public class Main { + public static void main(String[] args) throws Exception { + SocksService socksService=new SocksService(); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.addServlet(new ServletHolder(new ShowServlet(socksService)),"/show"); + context.addServlet(new ServletHolder(new IncomeServlet(socksService)), "/api/socks/income"); + context.addServlet(new ServletHolder(new OutcomeServlet(socksService)), "/api/socks/outcome"); + context.addServlet(new ServletHolder(new SocksServlet(socksService)), "/api/socks"); + + Server server = new Server(8080); + server.setHandler(context); + + server.start(); + server.join(); + } +} diff --git a/src/main/java/servlets/IncomeServlet.java b/src/main/java/servlets/IncomeServlet.java new file mode 100644 index 00000000..03ce5036 --- /dev/null +++ b/src/main/java/servlets/IncomeServlet.java @@ -0,0 +1,101 @@ +package servlets; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import socks.Socks; +import socks.SocksService; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class IncomeServlet extends HttpServlet { + private final SocksService socksService; + + public IncomeServlet(SocksService socksService){ + this.socksService=socksService; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + //super.doPost(req, resp); + JSONParser parser=new JSONParser(); + JSONObject jsonObject; + try { + jsonObject=(JSONObject)parser.parse(getBody(req)); + }catch (Exception e){ + throw new RuntimeException(); + } + Socks socks=new Socks(jsonObject); + + //пустые параметры + if(socks.getColor()==null||socks.getCottonPart()==null|| + socks.getQuantity()==null){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + //содержание хлопка + if(socks.getCottonPart()<0 || socks.getCottonPart()>100){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + //Отрицательное число + if(socks.getQuantity()<=0){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + if(socksService.addSocks(socks)){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_OK); + }else{ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + } + } + //сам не знаю как объект json обработать в строку нашел на сайте + // https://question-it.com/questions/192029/poluchenie-dannyh-iz-vhodjaschego-json-v-servlete-java + public static String getBody(HttpServletRequest request) { + + String body = null; + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = null; + + try { + InputStream inputStream = request.getInputStream(); + if (inputStream != null) { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + char[] charBuffer = new char[128]; + int bytesRead = -1; + while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { + stringBuilder.append(charBuffer, 0, bytesRead); + } + } else { + stringBuilder.append(""); + } + } catch (IOException ex) { + // throw ex; + return ""; + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException ex) { + + } + } + } + + body = stringBuilder.toString(); + return body; + } +} + diff --git a/src/main/java/servlets/OutcomeServlet.java b/src/main/java/servlets/OutcomeServlet.java new file mode 100644 index 00000000..0733d828 --- /dev/null +++ b/src/main/java/servlets/OutcomeServlet.java @@ -0,0 +1,101 @@ +package servlets; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import socks.Socks; +import socks.SocksService; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class OutcomeServlet extends HttpServlet { + private final SocksService socksService; + + public OutcomeServlet(SocksService socksService){ + this.socksService=socksService; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + //просматриваем json + JSONParser parser=new JSONParser(); + JSONObject jsonObject; + try { + jsonObject=(JSONObject)parser.parse(getBody(req)); + }catch (Exception e){ + throw new RuntimeException(); + } + Socks socks=new Socks(jsonObject); + + //пустые параметры + if(socks.getColor()==null||socks.getCottonPart()==null|| + socks.getQuantity()==null){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + //содержание хлопка + if(socks.getCottonPart()<0 || socks.getCottonPart()>100){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + //Отрицательное число + if(socks.getQuantity()<=0){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + if(socksService.delSocks(socks)){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_OK); + }else{ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + } + } + //сам не знаю как объект json обработать в строку нашел на сайте + // https://question-it.com/questions/192029/poluchenie-dannyh-iz-vhodjaschego-json-v-servlete-java + public static String getBody(HttpServletRequest request) { + + String body = null; + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = null; + + try { + InputStream inputStream = request.getInputStream(); + if (inputStream != null) { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + char[] charBuffer = new char[128]; + int bytesRead = -1; + while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { + stringBuilder.append(charBuffer, 0, bytesRead); + } + } else { + stringBuilder.append(""); + } + } catch (IOException ex) { + // throw ex; + return ""; + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException ex) { + + } + } + } + + body = stringBuilder.toString(); + return body; + } +} + diff --git a/src/main/java/servlets/ShowServlet.java b/src/main/java/servlets/ShowServlet.java new file mode 100644 index 00000000..7ede979c --- /dev/null +++ b/src/main/java/servlets/ShowServlet.java @@ -0,0 +1,23 @@ +package servlets; + +import socks.SocksService; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class ShowServlet extends HttpServlet { + private final SocksService socksService; + + public ShowServlet(SocksService socksService){ + this.socksService=socksService; + } + public void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/html;charset=utf-8"); + response.getWriter().print(socksService); + response.setStatus(HttpServletResponse.SC_OK); + } +} diff --git a/src/main/java/servlets/SocksServlet.java b/src/main/java/servlets/SocksServlet.java new file mode 100644 index 00000000..be2000ca --- /dev/null +++ b/src/main/java/servlets/SocksServlet.java @@ -0,0 +1,46 @@ +package servlets; +import socks.SocksService; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class SocksServlet extends HttpServlet{ + private final SocksService socksService; + + public SocksServlet(SocksService socksService){ + this.socksService=socksService; + } + + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + String color=req.getParameter("color"); + String operation=req.getParameter("operation"); + Long cottonPart=Long.parseLong(req.getParameter("cottonPart")); + + //пустые параметры + if(color==null||operation==null||cottonPart==null){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + //содержание хлопка + if(cottonPart<0 || cottonPart>100){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + //Корректная операция + if(!operation.equals("moreThan")&& !operation.equals("lessThan")&& !operation.equals("equal")){ + resp.setContentType("text/html;charset=utf-8"); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + //отобразить кол-во нужных носков + resp.setContentType("text/html;charset=utf-8"); + resp.getWriter().print(socksService.get(color,operation,cottonPart)); + resp.setStatus(HttpServletResponse.SC_OK); + } +} diff --git a/src/main/java/socks/Socks.java b/src/main/java/socks/Socks.java new file mode 100644 index 00000000..e08af217 --- /dev/null +++ b/src/main/java/socks/Socks.java @@ -0,0 +1,60 @@ +package socks; + +import org.json.simple.JSONObject; + +public class Socks { + String color; + Long cottonPart; + Long quantity; + + public String getColor() { + return color; + } + + public Long getCottonPart() { + return cottonPart; + } + + public Long getQuantity() { + return quantity; + } + + public Socks(String color, Long cottonPart, Long quantity){ + this.color=color; + this.cottonPart=cottonPart; + this.quantity=quantity; + } + public Socks(JSONObject jsonObject){ + this.color=(String)jsonObject.get("color"); + this.cottonPart=(Long) jsonObject.get("cottonPart"); + this.quantity=(Long) jsonObject.get("quantity"); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Socks socks = (Socks) o; + if(color.equals(socks.color)) + if(cottonPart.equals(socks.cottonPart)) + return true; + return false; + } + + @Override + public int hashCode() { + int result = color != null ? color.hashCode() : 0; + result = 31 * result + cottonPart.hashCode(); + return result; + } + + @Override + public String toString() { + return "Socks{" + + "color='" + color + '\'' + + ", cottonPart=" + cottonPart + + ", hash= "+hashCode()+ + '}'; + } +} diff --git a/src/main/java/socks/SocksService.java b/src/main/java/socks/SocksService.java new file mode 100644 index 00000000..03a0e907 --- /dev/null +++ b/src/main/java/socks/SocksService.java @@ -0,0 +1,59 @@ +package socks; + + +import java.util.*; + +public class SocksService { + private final Map socksMap; + + public SocksService(){ + socksMap=new HashMap<>(); + } + + public boolean addSocks(Socks socks){ + if(socks.quantity<=0) + return false; + if(socksMap.containsKey(socks)){ + socksMap.put(socks,socksMap.get(socks)+socks.quantity); + return true; + } else{ + socksMap.put(socks,socks.quantity); + return true; + } + } + public boolean delSocks(Socks socks){ + if(socks.quantity<=0) + return false; + if(socksMap.containsKey(socks)){ + if(socks.quantitycottonPart) + result+=socksMap.get(socks); + } else if(operation.equals(("lessThan"))){ + if(socks.cottonPart