From 3264e0815b1f4e4bbc7c9480ac1e947cea7c5fc1 Mon Sep 17 00:00:00 2001 From: slbogdanov Date: Wed, 21 Dec 2016 06:29:47 +0300 Subject: [PATCH 1/5] first push, wannacry, some errors and warnings --- homework-g597-bogdanov/pom.xml | 42 +++ .../task3/MyOptimisedKeyValueStorage.java | 2 +- .../g597/bogdanov/task4/CURL commands.txt | 27 ++ .../task4/CalculatorWithTokens/Token.java | 48 +++ .../CalculatorWithTokens/TokenCalculator.java | 186 ++++++++++ .../task4/REST/IFunctionalCalculator.java | 30 ++ .../bogdanov/task4/REST/RESTCalculator.java | 120 +++++++ .../REST/functions/CalculateableFunction.java | 323 ++++++++++++++++++ .../functions/CalculatorFunctionObject.java | 57 ++++ .../REST/functions/IEvaluateableFunction.java | 18 + .../REST/functions/PredefinedFunction.java | 131 +++++++ .../task4/server/CalculatorApplication.java | 39 +++ .../task4/server/CalculatorController.java | 134 ++++++++ .../bogdanov/task4/server/CalculatorDao.java | 210 ++++++++++++ .../CalculatorDatabaseConfiguration.java | 29 ++ .../bogdanov/task4/server/CalculatorUser.java | 68 ++++ .../server/SecurityServiceConfiguration.java | 60 ++++ 17 files changed, 1523 insertions(+), 1 deletion(-) create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/Token.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/TokenCalculator.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/IFunctionalCalculator.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/RESTCalculator.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculateableFunction.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculatorFunctionObject.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/IEvaluateableFunction.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorApplication.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDatabaseConfiguration.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorUser.java create mode 100644 homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/SecurityServiceConfiguration.java diff --git a/homework-g597-bogdanov/pom.xml b/homework-g597-bogdanov/pom.xml index b77da7194..c2e8465f6 100644 --- a/homework-g597-bogdanov/pom.xml +++ b/homework-g597-bogdanov/pom.xml @@ -10,7 +10,12 @@ 4.0.0 homework-g597-bogdanov + pom + 1.0.0 + + 1.4.2.RELEASE + @@ -19,6 +24,43 @@ 1.0.0 + + net.sourceforge.jeval + jeval + 0.9.4 + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-jdbc + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-security + ${spring.boot.version} + + + + com.zaxxer + HikariCP + 2.3.13 + + + + com.h2database + h2 + 1.4.190 + + ru.mipt.java2016 homework-tests diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java index 5592b68f8..a9f1902df 100644 --- a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java @@ -221,8 +221,8 @@ private void dumpMemtable() { } private void clearDeletedElements() { - dumpMemtable(); readWriteLock.writeLock().lock(); + dumpMemtable(); try { HashMap updatedOffsets = new HashMap<>(); File tmpFileForValues = new File(path + File.separator + this.fileName + "_values"); diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt new file mode 100644 index 000000000..420582b24 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt @@ -0,0 +1,27 @@ +curl http://localhost:9001/user/add/supersanic99?password=gottagofast -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" + +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "44*3+2" + +curl http://localhost:9001/user/add/krusher99?password=mlgpro -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" + +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "44*3+2" + +curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "1" + +curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "99999" + +curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" + +curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" + +curl http://localhost:9001/variable/ml -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "max(1.010, 1.009)" + +curl http://localhost:9001/function/myfunc?args=x&args=y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "pow(2, x)" + +curl http://localhost:9001/function/myfunc1?args=x,y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "log2(x) + y" + +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "15 + myfunc1(64, 4)" + +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "15 + myfunc1(64, 4)" + +curl http://localhost:9001/function/myfunc1 -X GET -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" \ No newline at end of file diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/Token.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/Token.java new file mode 100644 index 000000000..1de6f1201 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/Token.java @@ -0,0 +1,48 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.CalculatorWithTokens; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public class Token { + public enum TokenType { + PLUS, MINUS, MULTIPLY, DIVIDE, + NUMBER, NAME, + LEFT_BRACE, RIGHT_BRACE, COMMA, + UNKNOWN + } + + private final TokenType type; + private Double number = null; + private String name = null; + + public Token(TokenType type) { + this.type = type; + } + + public Token(String name) { + this.name = name; + this.type = TokenType.NAME; + } + + public Token(Double number) { + this.number = number; + this.type = TokenType.NUMBER; + } + + public TokenType getType() { + return type; + } + + public Double getNumber() { + return number; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return String.format("Type: %s, Number: %s, Name: %s \n", type, number, name); + } +} \ No newline at end of file diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/TokenCalculator.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/TokenCalculator.java new file mode 100644 index 000000000..8027752b0 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/TokenCalculator.java @@ -0,0 +1,186 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.CalculatorWithTokens; + +import ru.mipt.java2016.homework.base.task1.Calculator; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.ArrayList; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public class TokenCalculator implements Calculator { + private final ArrayList tokens = new ArrayList<>(); + private int tokensIndex = 0; + + + private void parse(String expression) throws ParsingException { + if (expression == null) { + throw new ParsingException("Expression is null."); + } + + for (int expressionIndex = 0; expressionIndex < expression.length(); ++expressionIndex) { + Token.TokenType currentTokenType; + Double currentNumber = null; + + if (Character.isWhitespace(expression.charAt(expressionIndex)) || + Character.isSpaceChar(expression.charAt(expressionIndex))) { + continue; + } + + switch (expression.charAt(expressionIndex)) { + case '+': + currentTokenType = Token.TokenType.PLUS; + break; + + case '-': + currentTokenType = Token.TokenType.MINUS; + break; + + case '*': + currentTokenType = Token.TokenType.MULTIPLY; + break; + + case '/': + currentTokenType = Token.TokenType.DIVIDE; + break; + + case '(': + currentTokenType = Token.TokenType.LEFT_BRACE; + break; + + case ')': + currentTokenType = Token.TokenType.RIGHT_BRACE; + break; + + default: + if (!Character.isDigit(expression.charAt(expressionIndex))) { + throw new ParsingException(String.format("Unexpected symbol at %d", expressionIndex)); + } + + boolean readDot = false; + int numberStartIndex = expressionIndex; + for (; expressionIndex < expression.length(); ++expressionIndex) { + Character currentCharacter = expression.charAt(expressionIndex); + if (currentCharacter == '.' && !readDot) { + readDot = true; + } else if (!Character.isDigit(currentCharacter)) { + break; + } + } + + currentNumber = Double.parseDouble(expression.substring(numberStartIndex, expressionIndex)); + --expressionIndex; + currentTokenType = Token.TokenType.NUMBER; + break; + } + + if (currentTokenType != Token.TokenType.NUMBER) { + tokens.add(new Token(currentTokenType)); + } else { + tokens.add(new Token(currentNumber)); + } + } + } + + private void regressTokensIndex() { + --tokensIndex; + } + + private Token progressTokens() { + if (tokensIndex >= tokens.size()) { + return null; + } + + return tokens.get(tokensIndex++); + } + + private Double expression() throws ParsingException { + Double result = multiple(); + + for (Token token = progressTokens(); token != null; token = progressTokens()) { + if (token.getType() == Token.TokenType.PLUS) { + result += multiple(); + } else if (token.getType() == Token.TokenType.MINUS) { + result -= multiple(); + } else { + regressTokensIndex(); + break; + } + } + + return result; + } + + private Double multiple() throws ParsingException { + Double result = bracedExpression(); + + for (Token token = progressTokens(); token != null; token = progressTokens()) { + if (token.getType() == Token.TokenType.MULTIPLY) { + result *= bracedExpression(); + } else if (token.getType() == Token.TokenType.DIVIDE) { + result /= bracedExpression(); + } else { + regressTokensIndex(); + break; + } + } + + return result; + } + + private Double bracedExpression() throws ParsingException { + Double result; + + Token token = progressTokens(); + if (token == null) { + throw new ParsingException("Invalid amount of numbers."); + } + + if (token.getType() == Token.TokenType.LEFT_BRACE) { + result = expression(); + token = progressTokens(); + + if (token == null || token.getType() != Token.TokenType.RIGHT_BRACE) { + throw new ParsingException("Wrong number of left/right braces"); + } + } else { + regressTokensIndex(); + result = numberExpression(); + } + + return result; + } + + private Double numberExpression() throws ParsingException { + Double result; + + Token token = progressTokens(); + if (token == null) { + throw new ParsingException("Invalid amount of numbers"); + } + + if (token.getType() == Token.TokenType.MINUS) { + result = -expression(); + } else if (token.getType() == Token.TokenType.NUMBER) { + result = token.getNumber(); + } else { + throw new ParsingException("Invalid order of operations"); + } + + return result; + } + + public double calculate(String expression) throws ParsingException { + tokens.clear(); + tokensIndex = 0; + + parse(expression); + Double result = expression(); + + if (tokensIndex != tokens.size()) { + throw new ParsingException("Invalid number of tokens (too many)."); + } + + return result; + } +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/IFunctionalCalculator.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/IFunctionalCalculator.java new file mode 100644 index 000000000..dd75fc5ea --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/IFunctionalCalculator.java @@ -0,0 +1,30 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.REST; + +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject; + +import java.util.List; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public interface IFunctionalCalculator { + + Double getVariable(String variableAlias); + + boolean putVariable(String variableAlias, Double value); + + boolean deleteVariable(String variableAlias); + + List getVariableList(); + + CalculatorFunctionObject getFunction(String functionAlias); + + boolean putFunction(String functionAlias, String expression, List arguments); + + boolean deleteFunction(String functionAlias); + + List getFunctionList(); + + Double calculate(String expression) throws ParsingException; +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/RESTCalculator.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/RESTCalculator.java new file mode 100644 index 000000000..5915e9e18 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/RESTCalculator.java @@ -0,0 +1,120 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.REST; + +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculateableFunction; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.IEvaluateableFunction; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.PredefinedFunction; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public class RESTCalculator implements IFunctionalCalculator { + private Map functions = new ConcurrentHashMap<>(); + private Map variables = new ConcurrentHashMap<>(); + + private void initializePredefinedFunctions() { + functions.put("sin", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.SIN)); + functions.put("cos", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.COS)); + functions.put("tg", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.TG)); + functions.put("sqrt", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.SQRT)); + functions.put("pow", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.POW)); + functions.put("abs", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.ABS)); + functions.put("sign", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.SIGN)); + functions.put("log", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.LOG)); + functions.put("log2", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.LOG2)); + functions.put("rnd", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.RND)); + functions.put("max", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.MAX)); + functions.put("min", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.MIN)); + } + + public RESTCalculator() { + initializePredefinedFunctions(); + } + + @Override + public Double getVariable(String variableAlias) { + return variables.get(variableAlias); + } + + @Override + public boolean putVariable(String variableAlias, Double value) { + if (functions.containsKey(variableAlias)) { + return false; + } else { + variables.put(variableAlias, value); + return true; + } + } + + @Override + public boolean deleteVariable(String variableAlias) { + if (variables.containsKey(variableAlias)) { + variables.remove(variableAlias); + return true; + } + + return false; + } + + @Override + public List getVariableList() { + return variables.keySet().stream().collect(Collectors.toList()); + } + + public CalculatorFunctionObject getFunction(String functionAlias) { + if (!functions.containsKey(functionAlias) || functions.get(functionAlias).isPredefined()) { + return null; + } + + CalculateableFunction function = (CalculateableFunction) functions.get(functionAlias); + return new CalculatorFunctionObject(function.getFunctionExpression(), function.getParameterList()); + } + + @Override + public boolean putFunction(String functionAlias, String expression, List arguments) { + if (variables.containsKey(functionAlias) || + (functions.containsKey(functionAlias) && functions.get(functionAlias).isPredefined())) { + return false; + } + + try { + functions.put(functionAlias, new CalculateableFunction(expression, arguments, functions, variables)); + } catch (ParsingException e) { + return false; + } + + return true; + } + + @Override + public boolean deleteFunction(String functionAlias) { + if (functions.containsKey(functionAlias)) { + IEvaluateableFunction function = functions.get(functionAlias); + if (function == null || function.isPredefined()) { + return false; + } else { + functions.remove(functionAlias); + return true; + } + } + + return false; + } + + @Override + public List getFunctionList() { + return functions.keySet().stream().collect(Collectors.toList()); + } + + @Override + public Double calculate(String expression) throws ParsingException { + return new CalculateableFunction(expression, new ArrayList<>(), functions, variables).evaluate(); + } +} \ No newline at end of file diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculateableFunction.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculateableFunction.java new file mode 100644 index 000000000..cbe06d0fd --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculateableFunction.java @@ -0,0 +1,323 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions; + +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g597.bogdanov.task4.CalculatorWithTokens.Token; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public class CalculateableFunction implements IEvaluateableFunction { + private Map functions; + private Map variables; + + private final ArrayList tokens = new ArrayList<>(); + private int tokensIndex = 0; + private String functionExpression; + + private Map parameterNumberToParameterString = new HashMap<>(); + private Map parameters = new HashMap<>(); + + public CalculateableFunction(String functionExpression, List functionParameters, + Map functions, + Map variables) throws ParsingException { + if (functionParameters != null) { + for (int i = 0; i < functionParameters.size(); ++i) { + parameterNumberToParameterString.put(i, functionParameters.get(i)); + } + } + + this.functionExpression = functionExpression; + this.functions = functions; + this.variables = variables; + + parse(functionExpression); + } + + private void parse(String expression) throws ParsingException { + if (expression == null) { + throw new ParsingException("Expression is null."); + } + + for (int expressionIndex = 0; expressionIndex < expression.length(); ++expressionIndex) { + Token currentToken; + + if (Character.isWhitespace(expression.charAt(expressionIndex)) || + Character.isSpaceChar(expression.charAt(expressionIndex))) { + continue; + } + + switch (expression.charAt(expressionIndex)) { + case '+': + currentToken = new Token(Token.TokenType.PLUS); + break; + + case '-': + currentToken = new Token(Token.TokenType.MINUS); + break; + + case '*': + currentToken = new Token(Token.TokenType.MULTIPLY); + break; + + case '/': + currentToken = new Token(Token.TokenType.DIVIDE); + break; + + case '(': + currentToken = new Token(Token.TokenType.LEFT_BRACE); + break; + + case ')': + currentToken = new Token(Token.TokenType.RIGHT_BRACE); + break; + + case ',': + currentToken = new Token(Token.TokenType.COMMA); + break; + + default: + if (Character.isDigit(expression.charAt(expressionIndex))) { + boolean readDot = false; + int numberStartIndex = expressionIndex; + for (; expressionIndex < expression.length(); ++expressionIndex) { + Character currentCharacter = expression.charAt(expressionIndex); + if (currentCharacter == '.' && !readDot) { + readDot = true; + } else if (!Character.isDigit(currentCharacter)) { + break; + } + } + + Double currentNumber = + Double.parseDouble(expression.substring(numberStartIndex, expressionIndex)); + --expressionIndex; + currentToken = new Token(currentNumber); + + } else if (Character.isAlphabetic(expression.charAt(expressionIndex))) { + int nameStartIndex = expressionIndex; + for (; expressionIndex < expression.length(); ++expressionIndex) { + Character currentCharacter = expression.charAt(expressionIndex); + if (!Character.isAlphabetic(currentCharacter) && + !Character.isDigit(currentCharacter) && + currentCharacter != '_') { + break; + } + } + + currentToken = new Token(functionExpression.substring(nameStartIndex, expressionIndex)); + --expressionIndex; + } else { + throw new ParsingException(String.format("Unexpected symbol at %d", expressionIndex)); + } + break; + } + + tokens.add(currentToken); + } + } + + private void regressTokensIndex() { + --tokensIndex; + } + + private Token progressTokens() { + if (tokensIndex >= tokens.size()) { + return null; + } + + return tokens.get(tokensIndex++); + } + + private Double expression() throws ParsingException { + Double result = multiple(); + + for (Token token = progressTokens(); token != null; token = progressTokens()) { + if (token.getType() == Token.TokenType.PLUS) { + result += multiple(); + } else if (token.getType() == Token.TokenType.MINUS) { + result -= multiple(); + } else { + regressTokensIndex(); + break; + } + } + + return result; + } + + private Double multiple() throws ParsingException { + Double result = bracedExpression(); + + for (Token token = progressTokens(); token != null; token = progressTokens()) { + if (token.getType() == Token.TokenType.MULTIPLY) { + result *= bracedExpression(); + } else if (token.getType() == Token.TokenType.DIVIDE) { + result /= bracedExpression(); + } else { + regressTokensIndex(); + break; + } + } + + return result; + } + + private Double bracedExpression() throws ParsingException { + Double result; + + Token token = progressTokens(); + if (token == null) { + throw new ParsingException("Invalid amount of numbers."); + } + + if (token.getType() == Token.TokenType.LEFT_BRACE) { + result = expression(); + token = progressTokens(); + + if (token == null || token.getType() != Token.TokenType.RIGHT_BRACE) { + throw new ParsingException("Wrong number of left/right braces"); + } + } else { + regressTokensIndex(); + result = combinedExpression(); + } + + return result; + } + + private Double combinedExpression() throws ParsingException { + Double result; + + Token token = progressTokens(); + if (token == null) { + throw new ParsingException("Invalid amount of numbers"); + } + + if (token.getType() == Token.TokenType.MINUS) { + result = -bracedExpression(); + } else if (token.getType() == Token.TokenType.NUMBER) { + result = token.getNumber(); + } else if (token.getType() == Token.TokenType.NAME) { + regressTokensIndex(); + result = namedExpression(); + } else { + throw new ParsingException("Invalid order of operations"); + } + + return result; + } + + private Double namedExpression() throws ParsingException { + Double result; + + Token token = progressTokens(); + if (token == null) { + throw new ParsingException("Invalid amount of numbers"); + } + String tokenName = token.getName(); + + Token nextToken = progressTokens(); + if (nextToken != null && nextToken.getType() == Token.TokenType.LEFT_BRACE) { + if (!functions.containsKey(tokenName)) { + throw new ParsingException("Unexpected symbol. Function call impossible."); + } + + result = functionCall(tokenName); + } else { + if (nextToken != null) { + regressTokensIndex(); + } + + if (parameters.containsKey(tokenName)) { + result = parameters.get(tokenName); + } else { + if (!variables.containsKey(tokenName)) { + throw new ParsingException("Unexpected symbol. No such variable."); + } + result = variables.get(tokenName); + } + } + + return result; + } + + private Double functionCall(String functionName) throws ParsingException { + Double result; + + IEvaluateableFunction function = functions.get(functionName); + List functionArguments = new ArrayList<>(); + + Token token; + do { + token = progressTokens(); + + if (token == null) { + throw new ParsingException("Unexpected end of function expression."); + } else { + if (token.getType() == Token.TokenType.COMMA) { + throw new ParsingException("Unexpected comma - no parameter."); + } else if (token.getType() == Token.TokenType.RIGHT_BRACE) { + break; + } else { + regressTokensIndex(); + } + } + + functionArguments.add(expression()); + + token = progressTokens(); + if (token == null || + (token.getType() != Token.TokenType.COMMA && token.getType() != Token.TokenType.RIGHT_BRACE)) { + throw new ParsingException("Unexpected end of function expression."); + } + } while (token.getType() != Token.TokenType.RIGHT_BRACE); + + function.setArguments(functionArguments); + result = function.evaluate(); + return result; + } + + @Override + public Double evaluate() throws ParsingException { + tokensIndex = 0; + Double result = expression(); + + if (tokensIndex != tokens.size()) { + throw new ParsingException("Invalid number of tokens (too many)."); + } + + return result; + } + + @Override + public void setArguments(List arguments) throws ParsingException { + if (arguments.size() != parameterNumberToParameterString.size()) { + throw new ParsingException("Number of arguments to be set is invalid."); + } + for (int argumentsIndex = 0; argumentsIndex < arguments.size(); ++argumentsIndex) { + parameters.put(parameterNumberToParameterString.get(argumentsIndex), arguments.get(argumentsIndex)); + } + } + + public String getFunctionExpression() { + return functionExpression; + } + + public List getParameterList() { + List result = new ArrayList<>(); + + List> entries = + parameterNumberToParameterString.entrySet() + .stream() + .sorted(Comparator.comparingInt(Map.Entry::getKey)) + .collect(Collectors.toList()); + + for (Map.Entry entry : entries) { + result.add(entry.getValue()); + } + return result; + } +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculatorFunctionObject.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculatorFunctionObject.java new file mode 100644 index 000000000..cab458cf9 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculatorFunctionObject.java @@ -0,0 +1,57 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public class CalculatorFunctionObject { + private String expression; + private List arguments; + + public CalculatorFunctionObject() { + expression = ""; + arguments = new ArrayList<>(); + } + + public CalculatorFunctionObject(String expression, List arguments) { + this.expression = expression; + this.arguments = arguments; + } + + public String getExpression() { + return expression; + } + + public List getArguments() { + return arguments; + } + + + public void setExpression(String expression) { + this.expression = expression; + } + + public void setArguments(List arguments) { + this.arguments = arguments; + } + + public String toString() { + return "Expression: " + expression + '\n' + + "Arguments: " + String.join(", ", arguments); + } + + public boolean equals(Object obj) { + if (!(obj instanceof CalculatorFunctionObject)) { + return false; + } + CalculatorFunctionObject comparedCalculatorFunction = (CalculatorFunctionObject) obj; + return expression.equals(comparedCalculatorFunction.expression) && + arguments.equals(comparedCalculatorFunction.arguments); + } + + public int hashCode() { + return expression.hashCode() * 31 + arguments.hashCode(); + } +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/IEvaluateableFunction.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/IEvaluateableFunction.java new file mode 100644 index 000000000..d172adad2 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/IEvaluateableFunction.java @@ -0,0 +1,18 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.List; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public interface IEvaluateableFunction { + Double evaluate() throws ParsingException; + + void setArguments(List arguments) throws ParsingException; + + default boolean isPredefined() { + return false; + } +} \ No newline at end of file diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java new file mode 100644 index 000000000..ab2536135 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java @@ -0,0 +1,131 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.*; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public class PredefinedFunction implements IEvaluateableFunction { + /** + * PUBLIC STATIC DATA + */ + public enum PredefinedFunctionType { + SIN, COS, TG, SQRT, POW, ABS, SIGN, LOG, LOG2, RND, MAX, MIN + } + + public static final Map PREDEFINED_FUNCTION_TYPE_NUM_ARGUMENTS_MAP; + + static { + Map predefinedFunctionTypeNumArguments = new HashMap<>(); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.SIN, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.COS, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.TG, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.SQRT, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.POW, 2); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.ABS, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.SIGN, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.LOG, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.LOG2, 1); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.RND, 0); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.MAX, 2); + predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.MIN, 2); + PREDEFINED_FUNCTION_TYPE_NUM_ARGUMENTS_MAP = + Collections.unmodifiableMap(predefinedFunctionTypeNumArguments); + } + + /** + * PRIVATE STATIC DATA + */ + private static final Random RANDOM_NUMBER_GENERATOR = new Random(1337); + + + /** + * DATA + */ + private final PredefinedFunctionType functionType; + private final List arguments = new ArrayList<>(); + + + /** + * CONSTRUCTOR + */ + public PredefinedFunction(PredefinedFunctionType functionType) { + this.functionType = functionType; + for (int numArgumentsCounter = 0; + numArgumentsCounter < PREDEFINED_FUNCTION_TYPE_NUM_ARGUMENTS_MAP.get(functionType); + ++numArgumentsCounter) { + arguments.add(0.0); + } + } + + + /** + * INTERFACE: IEvaluateableFunction + */ + @Override + public Double evaluate() throws ParsingException { + Double result = 0.0; + + switch (functionType) { + case SIN: + result = Math.sin(arguments.get(0)); + break; + case COS: + result = Math.cos(arguments.get(0)); + break; + case TG: + result = Math.tan(arguments.get(0)); + break; + case SQRT: + result = Math.sqrt(arguments.get(0)); + break; + case POW: + result = Math.pow(arguments.get(0), arguments.get(1)); + break; + case ABS: + result = Math.abs(arguments.get(0)); + break; + case SIGN: + result = Math.signum(arguments.get(0)); + break; + case LOG: + result = Math.log(arguments.get(0)); + break; + case LOG2: + result = Math.log(arguments.get(0)) / Math.log(2); + break; + case RND: + synchronized (RANDOM_NUMBER_GENERATOR) { + result = RANDOM_NUMBER_GENERATOR.nextDouble(); + } + break; + case MAX: + result = Math.max(arguments.get(0), arguments.get(1)); + break; + case MIN: + result = Math.min(arguments.get(0), arguments.get(1)); + break; + default: + break; + } + + return result; + } + + @Override + public void setArguments(List arguments) throws ParsingException { + if (this.arguments.size() != arguments.size()) { + throw new ParsingException("Number of arguments to be set is invalid."); + } + for (int argumentsIndex = 0; argumentsIndex < arguments.size(); ++argumentsIndex) { + this.arguments.set(argumentsIndex, arguments.get(argumentsIndex)); + } + } + + @Override + public boolean isPredefined() { + return true; + } +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorApplication.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorApplication.java new file mode 100644 index 000000000..01720f0ab --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorApplication.java @@ -0,0 +1,39 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.server; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.RESTCalculator; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.IFunctionalCalculator; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +@EnableAutoConfiguration +@Configuration +@ComponentScan(basePackageClasses = CalculatorApplication.class) +public class CalculatorApplication { + + @Bean + public IFunctionalCalculator calculator() { + return new RESTCalculator(); + } + + @Bean + public EmbeddedServletContainerCustomizer customizer( + @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.httpPort:9001}") int port) { + return container -> container.setPort(port); + } + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(CalculatorApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.run(args); + } +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java new file mode 100644 index 000000000..79f9dbfad --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java @@ -0,0 +1,134 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.RESTCalculator; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject; + +import java.util.List; +import java.util.Map; +import java.util.Arrays; + +/** + * Created by Semyo_000 on 20.12.2016. + */ + +@RestController +public class CalculatorController { + private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class); + + @Autowired + private RESTCalculator calculator; + + @Autowired + private CalculatorDao calculatorDao; + + @RequestMapping(path = "/variable/{name}", method = RequestMethod.GET, produces = "text/plain") + public String getVariable(Authentication authentication, @PathVariable String name) throws ParsingException { + String ourName = authentication.getName(); + Double result = calculatorDao.getVariable(ourName, name); + return name + " = " + result + "\n"; + } + + @RequestMapping(path = "/variable/{name}", method = RequestMethod.DELETE, produces = "text/plain") + public String deleteVariable(Authentication authentication, @PathVariable String name) throws ParsingException { + String username = authentication.getName(); + boolean success = calculatorDao.deleteVariable(username, name); + if (success) { + return name + " deleted\n"; + } else { + return name + " does not exist\n"; + } + } + + @RequestMapping(path = "/variable/{name}", method = RequestMethod.PUT, + consumes = "text/plain", produces = "text/plain") + public String addVariable(Authentication authentication, @PathVariable String name, @RequestBody String value) + throws ParsingException { + String username = authentication.getName(); + calculatorDao.addVariable(username, name, Double.parseDouble(value)); + return "Variable added\n"; + } + + @RequestMapping(path = "/variable", method = RequestMethod.GET, produces = "text/plain") + public String getVariables(Authentication authentication) throws ParsingException { + String username = authentication.getName(); + Map result = calculatorDao.getVariables(username); + return String.join(", ", result.keySet()) + "\n" + + ""; + } + + @RequestMapping(path = "/function/{name}", method = RequestMethod.GET, produces = "text/plain") + public String getFunction(Authentication authentication, @PathVariable String name) throws ParsingException { + String ourName = authentication.getName(); + CalculatorFunctionObject result = calculatorDao.getFunction(ourName, name); + return name + "(" + String.join(", ", result.getArguments()) + ")" + " = " + result.getExpression() + "\n"; + } + + @RequestMapping(path = "/function/{name}", method = RequestMethod.DELETE, produces = "text/plain") + public String deleteFunction(Authentication authentication, @PathVariable String name) throws ParsingException { + String username = authentication.getName(); + boolean success = calculatorDao.deleteFunction(username, name); + if (success) { + return name + " deleted\n"; + } else { + return name + " not exists\n"; + } + } + + @RequestMapping(path = "/function/{name}", method = RequestMethod.PUT, + consumes = "text/plain", produces = "text/plain") + public String addFunction(Authentication authentication, @PathVariable String name, + @RequestParam(value = "args") String args, + @RequestBody String expression) + throws ParsingException { + String username = authentication.getName(); + List arguments = Arrays.asList(args.split(",")); + calculatorDao.addFunction(username, name, arguments, expression); + return "Function added\n"; + } + + @RequestMapping(path = "/function", method = RequestMethod.GET, produces = "text/plain") + public String getFunctions(Authentication authentication) throws ParsingException { + String username = authentication.getName(); + Map result = calculatorDao.getFunctions(username); + return String.join(", ", result.keySet()) + "\n"; + } + + @RequestMapping(path = "/calculate", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain") + public String calculate(Authentication authentication, @RequestBody String expression) throws ParsingException { + LOG.debug("Calculation request: [" + expression + "]"); + String username = authentication.getName(); + Map variables = calculatorDao.getVariables(username); + for (Map.Entry entry : variables.entrySet()) { + calculator.putVariable(entry.getKey(), entry.getValue()); + } + Map functions = calculatorDao.getFunctions(username); + for (Map.Entry entry : functions.entrySet()) { + calculator.putFunction(entry.getKey(), entry.getValue().getExpression(), entry.getValue().getArguments()); + } + double result = calculator.calculate(expression); + calculator = new RESTCalculator(); + return result + "\n"; + } + + @RequestMapping(path = "/register/{username}", method = RequestMethod.PUT, + consumes = "text/plain", produces = "text/plain") + public String register(@PathVariable String username, @RequestBody String pswd) + throws ParsingException { + LOG.debug("New user: [" + username + ' ' + pswd + "]"); + boolean success = calculatorDao.addUserIfNotExists(username, pswd, true); + if (success) { + LOG.trace("Success"); + return "You have been successfully registered\n"; + } else { + LOG.trace("Fail"); + return "This user already exists\n"; + } + } +} \ No newline at end of file diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java new file mode 100644 index 000000000..149716202 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java @@ -0,0 +1,210 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.*; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +@Repository +public class CalculatorDao { + private static final Logger LOG = LoggerFactory.getLogger(CalculatorDao.class); + + @Autowired + private DataSource dataSource; + + private JdbcTemplate jdbcTemplate; + + @PostConstruct + public void postConstruct() { + jdbcTemplate = new JdbcTemplate(dataSource, false); + initSchema(); + } + + public void initSchema() { + LOG.trace("Initializing schema"); + jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS billing"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.users " + + "(username VARCHAR PRIMARY KEY, password VARCHAR, enabled BOOLEAN)"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.variables " + + "(username VARCHAR, name VARCHAR, value DOUBLE)"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions " + + "(username VARCHAR, name VARCHAR, arguments VARCHAR, expression VARCHAR)"); + addUserIfNotExists("username", "password", true); + } + + boolean addUserIfNotExists(String username, String password, boolean enabled) { + try { + loadUser(username); + return false; + } catch (EmptyResultDataAccessException e) { + jdbcTemplate.update("INSERT INTO billing.users VALUES (?, ?, ?)", + new Object[]{username, password, enabled}); + return true; + } + } + + public Double getVariable(String username, String variable) { + return jdbcTemplate.queryForObject( + "SELECT username, name, value FROM billing.variables WHERE username = ? AND name = ?", + new Object[]{username, variable}, + new RowMapper() { + @Override + public Double mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getDouble("value"); + } + } + ); + } + + Map getVariables(String username) { + try { + return jdbcTemplate.queryForObject( + "SELECT username, name, value FROM billing.variables WHERE username = ?", + new Object[]{username}, + new RowMapper>() { + @Override + public HashMap mapRow(ResultSet rs, int rowNum) throws SQLException { + HashMap result = new HashMap<>(); + while (true) { + result.put(rs.getString("name"), rs.getDouble("value")); + if (!rs.next()) { + break; + } + } + return result; + } + } + ); + } catch (EmptyResultDataAccessException e) { + HashMap result = new HashMap<>(); + return result; + } + } + + boolean deleteVariable(String username, String name) throws ParsingException { + try { + getVariable(username, name); + jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND name = ?", + new Object[]{username, name}); + return true; + } catch (EmptyResultDataAccessException e) { + return false; + } + } + + void addVariable(String username, String name, Double value) throws ParsingException { + try { + getVariable(username, name); + jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND name = ?", + new Object[]{username, name}); + jdbcTemplate.update("INSERT INTO billing.variables VALUES (?, ?, ?)", + new Object[]{username, name, value}); + } catch (EmptyResultDataAccessException e) { + jdbcTemplate.update("INSERT INTO billing.variables VALUES (?, ?, ?)", + new Object[]{username, name, value}); + } + } + + public CalculatorFunctionObject getFunction(String username, String function) { + return jdbcTemplate.queryForObject( + "SELECT username, name, arguments, expression FROM billing.functions WHERE username = ? AND name = ?", + new Object[]{username, function}, + new RowMapper() { + @Override + public CalculatorFunctionObject mapRow(ResultSet rs, int rowNum) throws SQLException { + List arguments = Arrays.asList(rs.getString("arguments").split(" ")); + String expression = rs.getString("expression"); + return new CalculatorFunctionObject(expression, arguments); + } + } + ); + } + + Map getFunctions(String username) { + try { + return jdbcTemplate.queryForObject( + "SELECT username, name, arguments, expression FROM billing.functions WHERE username = ?", + new Object[]{username}, + new RowMapper>() { + @Override + public HashMap mapRow(ResultSet rs, int rowNum) throws SQLException { + HashMap result = new HashMap<>(); + while (true) { + String name = rs.getString("name"); + List arguments = Arrays.asList(rs.getString("arguments").split(" ")); + String expression = rs.getString("expression"); + result.put(name, new CalculatorFunctionObject(expression, arguments)); + if (!rs.next()) { + break; + } + } + return result; + } + } + ); + } catch (EmptyResultDataAccessException e) { + HashMap result = new HashMap<>(); + return result; + } + } + + boolean deleteFunction(String username, String function) throws ParsingException { + try { + getFunction(username, function); + jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND name = ?", + new Object[]{username, function}); + return true; + } catch (EmptyResultDataAccessException e) { + return false; + } + } + + void addFunction(String username, String function, List arguments, String expression) throws ParsingException { + try { + getFunction(username, function); + jdbcTemplate.update("DELETE FROM billing.functions WHERE username = ? AND name = ?", + new Object[]{username, function}); + String stringArguments = String.join(" ", arguments); + jdbcTemplate.update("INSERT INTO billing.functions VALUES (?, ?, ?, ?)", + new Object[]{username, function, stringArguments, expression}); + } catch (EmptyResultDataAccessException e) { + String stringArguments = String.join(" ", arguments); + jdbcTemplate.update("INSERT INTO billing.functions VALUES (?, ?, ?, ?)", + new Object[]{username, function, stringArguments, expression}); + } + } + + public CalculatorUser loadUser(String username) throws EmptyResultDataAccessException { + LOG.trace("Querying for user " + username); + return jdbcTemplate.queryForObject( + "SELECT username, password, enabled FROM billing.users WHERE username = ?", + new Object[]{username}, + new RowMapper() { + @Override + public CalculatorUser mapRow(ResultSet rs, int rowNum) throws SQLException { + return new CalculatorUser( + rs.getString("username"), + rs.getString("password"), + rs.getBoolean("enabled") + ); + } + } + ); + } +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDatabaseConfiguration.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDatabaseConfiguration.java new file mode 100644 index 000000000..c84aecdfc --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDatabaseConfiguration.java @@ -0,0 +1,29 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.server; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +@Configuration +public class CalculatorDatabaseConfiguration { + @Bean + public DataSource billingDataSource( + @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.jdbcUrl}") String jdbcUrl, + @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.username:}") String username, + @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.password:}") String password + ) { + HikariConfig config = new HikariConfig(); + config.setDriverClassName(org.h2.Driver.class.getName()); + config.setJdbcUrl(jdbcUrl); + config.setUsername(username); + config.setPassword(password); + return new HikariDataSource(config); + } +} diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorUser.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorUser.java new file mode 100644 index 000000000..c4cfd3f79 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorUser.java @@ -0,0 +1,68 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.server; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +public class CalculatorUser { + private final String username; + private final String password; + private final boolean enabled; + + public CalculatorUser(String username, String password, boolean enabled) { + if (username == null) { + throw new IllegalArgumentException("Null username is not allowed"); + } + if (password == null) { + throw new IllegalArgumentException("Null password is not allowed"); + } + this.username = username; + this.password = password; + this.enabled = enabled; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public boolean isEnabled() { + return enabled; + } + + @Override + public String toString() { + return "CalculatorUser{" + + "username='" + username + '\'' + + ", password='" + password + '\'' + + ", enabled=" + enabled + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CalculatorUser that = (CalculatorUser) o; + + return enabled == that.enabled && + username.equals(that.username) && + password.equals(that.password); + + } + + @Override + public int hashCode() { + int result = username.hashCode(); + result = 31 * result + password.hashCode(); + result = 31 * result + (enabled ? 1 : 0); + return result; + } +} \ No newline at end of file diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/SecurityServiceConfiguration.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/SecurityServiceConfiguration.java new file mode 100644 index 000000000..c76647de9 --- /dev/null +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/SecurityServiceConfiguration.java @@ -0,0 +1,60 @@ +package ru.mipt.java2016.homework.g597.bogdanov.task4.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.Collections; + +/** + * Created by Semyo_000 on 20.12.2016. + */ +@Configuration +@EnableWebSecurity +public class SecurityServiceConfiguration extends WebSecurityConfigurerAdapter { + private static final Logger LOG = LoggerFactory.getLogger(SecurityServiceConfiguration.class); + + @Autowired + private CalculatorDao calculatorDao; + + @Override + protected void configure(HttpSecurity http) throws Exception { + LOG.info("Configuring security"); + http + .httpBasic().realmName("Calculator").and() + .formLogin().disable() + .logout().disable() + .csrf().disable() + .authorizeRequests() + .antMatchers("/calculate/**").authenticated() + .antMatchers("/variable/**").authenticated() + .antMatchers("/function/**").authenticated() + .anyRequest().permitAll(); + } + + @Autowired + public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception { + LOG.info("Registering global user details service"); + auth.userDetailsService(username -> { + try { + CalculatorUser user = calculatorDao.loadUser(username); + return new User( + user.getUsername(), + user.getPassword(), + Collections.singletonList(() -> "AUTH") + ); + } catch (EmptyResultDataAccessException e) { + LOG.warn("No such user: " + username); + throw new UsernameNotFoundException(username); + } + }); + } +} \ No newline at end of file From 64e852fb81149c459e385666607c8c97d4d27376 Mon Sep 17 00:00:00 2001 From: slbogdanov Date: Wed, 21 Dec 2016 06:34:33 +0300 Subject: [PATCH 2/5] Codestyle fixed --- .../homework/g597/bogdanov/task4/server/CalculatorDao.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java index 149716202..01a285433 100644 --- a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java @@ -143,7 +143,8 @@ Map getFunctions(String username) { new Object[]{username}, new RowMapper>() { @Override - public HashMap mapRow(ResultSet rs, int rowNum) throws SQLException { + public HashMap mapRow(ResultSet rs, + int rowNum) throws SQLException { HashMap result = new HashMap<>(); while (true) { String name = rs.getString("name"); @@ -175,7 +176,8 @@ boolean deleteFunction(String username, String function) throws ParsingException } } - void addFunction(String username, String function, List arguments, String expression) throws ParsingException { + void addFunction(String username, String function, List arguments, + String expression) throws ParsingException { try { getFunction(username, function); jdbcTemplate.update("DELETE FROM billing.functions WHERE username = ? AND name = ?", From 5eea30684a5317079e9763c5e4526234f0953e7a Mon Sep 17 00:00:00 2001 From: slbogdanov Date: Wed, 21 Dec 2016 07:19:01 +0300 Subject: [PATCH 3/5] okay, another useless commit --- .../java2016/homework/g597/bogdanov/task4/CURL commands.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt index 420582b24..4680d2a58 100644 --- a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt @@ -1,4 +1,4 @@ -curl http://localhost:9001/user/add/supersanic99?password=gottagofast -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" +curl http://localhost:9001/user/add/supersanic99?password=gottagofast -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "44*3+2" From 03d02e1c327c68a2492de55739d674d7b2c9e81f Mon Sep 17 00:00:00 2001 From: slbogdanov Date: Wed, 21 Dec 2016 07:25:42 +0300 Subject: [PATCH 4/5] and one more --- .../g597/bogdanov/task4/CURL commands.txt | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt index 4680d2a58..6bdc8b066 100644 --- a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt @@ -1,27 +1,23 @@ -curl http://localhost:9001/user/add/supersanic99?password=gottagofast -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "44*3+2" -curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "44*3+2" +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "44*3+2" -curl http://localhost:9001/user/add/krusher99?password=mlgpro -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" +curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "1" -curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "44*3+2" +curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "99999" -curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "1" +curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "username:password" | base64)" -curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "99999" +curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "username:password" | base64)" -curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" +curl http://localhost:9001/variable/ml -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "max(1.010, 1.009)" -curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" +curl http://localhost:9001/function/myfunc?args=x&args=y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "pow(2, x)" -curl http://localhost:9001/variable/ml -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "max(1.010, 1.009)" +curl http://localhost:9001/function/myfunc1?args=x,y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "log2(x) + y" -curl http://localhost:9001/function/myfunc?args=x&args=y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "pow(2, x)" +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "15 + myfunc1(64, 4)" -curl http://localhost:9001/function/myfunc1?args=x,y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "log2(x) + y" +curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "15 + myfunc1(64, 4)" -curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "krusher99:mlgpro" | base64)" --data-raw "15 + myfunc1(64, 4)" - -curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" --data-raw "15 + myfunc1(64, 4)" - -curl http://localhost:9001/function/myfunc1 -X GET -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "supersanic:gottagofast" | base64)" \ No newline at end of file +curl http://localhost:9001/function/myfunc1 -X GET -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" \ No newline at end of file From 58891d28182f90b320c31d13aec1af54aa376f22 Mon Sep 17 00:00:00 2001 From: slbogdanov Date: Wed, 21 Dec 2016 11:05:40 +0300 Subject: [PATCH 5/5] last one --- .../REST/functions/PredefinedFunction.java | 18 ------------------ .../task4/server/CalculatorController.java | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java index ab2536135..2b623b267 100644 --- a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java @@ -8,9 +8,6 @@ * Created by Semyo_000 on 20.12.2016. */ public class PredefinedFunction implements IEvaluateableFunction { - /** - * PUBLIC STATIC DATA - */ public enum PredefinedFunctionType { SIN, COS, TG, SQRT, POW, ABS, SIGN, LOG, LOG2, RND, MAX, MIN } @@ -35,22 +32,11 @@ public enum PredefinedFunctionType { Collections.unmodifiableMap(predefinedFunctionTypeNumArguments); } - /** - * PRIVATE STATIC DATA - */ private static final Random RANDOM_NUMBER_GENERATOR = new Random(1337); - - /** - * DATA - */ private final PredefinedFunctionType functionType; private final List arguments = new ArrayList<>(); - - /** - * CONSTRUCTOR - */ public PredefinedFunction(PredefinedFunctionType functionType) { this.functionType = functionType; for (int numArgumentsCounter = 0; @@ -60,10 +46,6 @@ public PredefinedFunction(PredefinedFunctionType functionType) { } } - - /** - * INTERFACE: IEvaluateableFunction - */ @Override public Double evaluate() throws ParsingException { Double result = 0.0; diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java index 79f9dbfad..c225cfce3 100644 --- a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java +++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java @@ -100,7 +100,7 @@ public String getFunctions(Authentication authentication) throws ParsingExceptio return String.join(", ", result.keySet()) + "\n"; } - @RequestMapping(path = "/calculate", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain") + @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain") public String calculate(Authentication authentication, @RequestBody String expression) throws ParsingException { LOG.debug("Calculation request: [" + expression + "]"); String username = authentication.getName();