diff --git a/homework-g596-kozlova/pom.xml b/homework-g596-kozlova/pom.xml index f8aae5c72..c5af0bc0b 100644 --- a/homework-g596-kozlova/pom.xml +++ b/homework-g596-kozlova/pom.xml @@ -10,6 +10,12 @@ 4.0.0 homework-g596-kozlova + pom + 1.0.0 + + + 1.4.2.RELEASE + @@ -27,6 +33,41 @@ homework-tests 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.5.1 + + + com.h2database + h2 + 1.4.193 + + + ru.mipt.java2016 + homework-tests + 1.0.0 + \ No newline at end of file diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Application.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Application.java new file mode 100644 index 000000000..4e2b7aa69 --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Application.java @@ -0,0 +1,33 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +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; + +@EnableAutoConfiguration +@Configuration +@ComponentScan(basePackageClasses = Application.class) +public class Application { + + @Bean + public Calculator calculator() { + return Calculator.INSTANCE; + } + + @Bean + public EmbeddedServletContainerCustomizer customizer( + @Value("${ru.mipt.java2016.homework.g596.kozlova.task4.httpPort:9001}") int port) { + return container -> container.setPort(port); + } + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(Application.class); + application.setBannerMode(Banner.Mode.OFF); + application.run(args); + } +} \ No newline at end of file diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDao.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDao.java new file mode 100644 index 000000000..314edfaa3 --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDao.java @@ -0,0 +1,159 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +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.stereotype.Repository; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.util.List; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; + +@Repository +public class BillingDao { + + private static final Logger LOG = LoggerFactory.getLogger(BillingDao.class); + + @Autowired + private DataSource dataSource; + private JdbcTemplate jdbcTemplate; + + @PostConstruct + public void postConstruct() { + jdbcTemplate = new JdbcTemplate(dataSource, false); + LOG.trace("Initializing"); + 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, expression VARCHAR)"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions " + + "(username VARCHAR, name VARCHAR, arguments VARCHAR, expression VARCHAR)"); + createNewUser("userName", "password", true); + } + + public boolean createNewUser(String userName, String password, boolean enabled) { + try { + loadUser(userName); + return false; + } catch (EmptyResultDataAccessException e) { + jdbcTemplate.update("INSERT INTO billing.users VALUES (?, ?, ?)", userName, password, enabled); + return true; + } + } + + public BillingUser 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}, + (ResultSet resultSet, int numberOfRow) -> { + return new BillingUser(resultSet.getString("userName"), resultSet.getString("password"), + resultSet.getBoolean("enabled")); + } + ); + } + + public Variable getVariable(String userName, String variable) { + return jdbcTemplate.queryForObject( + "SELECT userName, name, value, expression FROM billing.variables WHERE userName = ? AND name = ?", + new Object[]{userName, variable}, + (ResultSet resultSet, int numberOfRow) -> { + return new Variable( + resultSet.getString("userName"), + resultSet.getString("name"), + resultSet.getDouble("value"), + resultSet.getString("expression")); + } + ); + } + + public Map getVariables(String userName) { + try { + return jdbcTemplate.queryForObject( + "SELECT userName, name, value, expression FROM billing.variables WHERE userName = ?", + new Object[]{userName}, + (ResultSet resultSet, int numberOfRow) -> { + Map map = new HashMap<>(); + while (!resultSet.next()) { + map.put(resultSet.getString("name"), Double.toString(resultSet.getDouble("value"))); + } + return map; + } + ); + } catch (EmptyResultDataAccessException e) { + return new HashMap<>(); + } + } + + public void deleteVariable(String userName, String name) throws ParsingException { + try { + getVariable(userName, name); + jdbcTemplate.update("DELETE FROM billing.variables WHERE userName = ? AND name = ?", userName, name); + } catch (EmptyResultDataAccessException e) { + throw new ParsingException("Can't delete"); + } + } + + public void addVariable(String userName, String name, Double value, String expression) throws ParsingException { + jdbcTemplate.update("MERGE INTO billing.variables VALUES (?, ?, ?, ?)", userName, name, value, expression); + } + + public Function getFunction(String userName, String function) { + return jdbcTemplate.queryForObject( + "SELECT userName, name, arguments, expression FROM billing.functions WHERE userName = ? AND name = ?", + new Object[]{userName, function}, + (ResultSet resultSet, int numberOfRow) -> { + String name = resultSet.getString("name"); + List arguments = Arrays.asList(resultSet.getString("arguments").split(" ")); + String expression = resultSet.getString("expression"); + return new Function(userName, name, arguments, expression); + } + ); + } + + public Map getFunctions(String userName) { + try { + return jdbcTemplate.queryForObject( + "SELECT userName, name, arguments, expression FROM billing.functions WHERE userName = ?", + new Object[]{userName}, + (ResultSet resultSet, int numberOfRow) -> { + Map map = new HashMap<>(); + while (true) { + String name = resultSet.getString("name"); + List arguments = Arrays.asList(resultSet.getString("arguments").split(" ")); + String expression = resultSet.getString("expression"); + map.put(name, new Function(userName, name, arguments, expression)); + if (!resultSet.next()) { + break; + } + } + return map; + } + ); + } catch (EmptyResultDataAccessException e) { + return new HashMap<>(); + } + } + + public void deleteFunction(String userName, String name) throws ParsingException { + try { + getFunction(userName, name); + jdbcTemplate.update("DELETE FROM billing.functions WHERE userName = ? AND name = ?", userName, name); + } catch (EmptyResultDataAccessException e) { + throw new ParsingException("Can't delete"); + } + } + + public void addFunction(String userName, String name, List arguments, String expression) + throws ParsingException { + jdbcTemplate.update("MERGE INTO billing.functions VALUES (?, ?, ?, ?)", userName, name, arguments, expression); + } +} \ No newline at end of file diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDatabaseConfiguration.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDatabaseConfiguration.java new file mode 100644 index 000000000..8f25a70af --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDatabaseConfiguration.java @@ -0,0 +1,26 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +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; + +@Configuration +public class BillingDatabaseConfiguration { + @Bean + public DataSource billingDataSource( + @Value("${ru.mipt.java2016.homework.g596.kozlova.task4.jdbcUrl}") String jdbcUrlAdress, + @Value("${ru.mipt.java2016.homework.g596.kozlova.task4.userName:}") String userName, + @Value("${ru.mipt.java2016.homework.g596.kozlova.task4.password:}") String password + ) { + HikariConfig config = new HikariConfig(); + config.setDriverClassName(org.h2.Driver.class.getName()); + config.setJdbcUrl(jdbcUrlAdress); + config.setUsername(userName); + config.setPassword(password); + return new HikariDataSource(config); + } +} \ No newline at end of file diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingUser.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingUser.java new file mode 100644 index 000000000..1b1fb42f0 --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingUser.java @@ -0,0 +1,53 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +public class BillingUser { + + private final String userName; + private final String password; + private final boolean enabled; + + public BillingUser(String u, String p, boolean e) { + if (u.equals(null) || p.equals(null)) { + throw new IllegalArgumentException("Incorrect data"); + } + userName = u; + password = p; + enabled = e; + } + + public String getUserName() { + return userName; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "BillingUser{ userName='" + userName + "\', password='" + password + '\'' + ", enabled=" + enabled + "}"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BillingUser that = (BillingUser) obj; + if (!userName.equals(that.userName) || !password.equals(that.password) || enabled != that.enabled) { + return false; + } + return true; + } + + @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-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Calculator.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Calculator.java new file mode 100644 index 000000000..252fb19bc --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Calculator.java @@ -0,0 +1,363 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +import ru.mipt.java2016.homework.base.task1.ParsingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.LinkedList; + +import static java.lang.Character.digit; +import static java.lang.Character.isDigit; +import static java.lang.Character.isLetter; +import static java.lang.Character.isWhitespace; + +public class Calculator { + + private static final Logger LOG = LoggerFactory.getLogger(Calculator.class); + + public static final Calculator INSTANCE = new Calculator(); + + public double calculate(Map variables, String expression) throws ParsingException { + if (expression == null || isSplitedNumbers(expression) || expression.equals("")) { + throw new ParsingException("Bad expressions"); + } + return calc(variables, expression); + } + + private double calc(Map variables, String expression) + throws ParsingException { + LinkedList operations = new LinkedList<>(); + LinkedList numbers = new LinkedList<>(); + expression = expression.replaceAll("\\s", ""); + expression = "(" + expression + ")"; + double lastNumber = 0; + boolean readNumber = false; + boolean readVariable = false; + String variable = ""; + boolean isPoint = false; + boolean isSomeAfterPoint = false; + double afterPoint = 1; + boolean canBeUnary = true; + boolean needJump = false; + int jump = -1; + for (int i = 0; i < expression.length(); ++i) { + if (needJump) { + if (i < jump) { + continue; + } + needJump = false; + } + afterPoint /= 10; + char currentSymbol = expression.charAt(i); + if (isDigit(currentSymbol) || currentSymbol == '.' || isLetter(currentSymbol)) { + canBeUnary = false; + if (isDigit(currentSymbol)) { + readNumber = true; + if (isPoint) { + isSomeAfterPoint = true; + lastNumber += afterPoint * digit(currentSymbol, 10); + continue; + } + lastNumber = lastNumber * 10 + digit(currentSymbol, 10); + continue; + } + if (isLetter(currentSymbol)) { + readVariable = true; + variable += currentSymbol; + continue; + } + if (isPoint || !readNumber) { + throw new ParsingException("Bad expression"); + } + isPoint = true; + afterPoint = 1; + continue; + } + if (readNumber || !isPossibleSymbol(currentSymbol)) { + if ((isPoint && !isSomeAfterPoint) || !isPossibleSymbol(currentSymbol)) { + throw new ParsingException("Bad expression"); + } + numbers.push(lastNumber); + lastNumber = 0; + isPoint = false; + readNumber = false; + } + if (readVariable) { + if (!variables.containsKey(variable)) { + if (variable.equals("rnd")) { + int position = i; + if (expression.charAt(position) != '(' || position + 1 >= expression.length() + || expression.charAt(position + 1) != ')') { + throw new ParsingException("Bad expression"); + } + numbers.push(Math.random()); + variable = ""; + readVariable = false; + needJump = true; + jump = position + 2; + continue; + } + if (isFunctionWithOneParameter(variable)) { + StringBuilder parameter = new StringBuilder(); + int position = actionsIfOneParameter(expression, i, parameter); + numbers.push(calcFunctionWithOneParameter(variable, + calc(variables, parameter.toString()))); + variable = ""; + readVariable = false; + needJump = true; + jump = position; + continue; + } + if (isFunctionWithTwoParameters(variable)) { + StringBuilder firstParameter = new StringBuilder(); + StringBuilder secondParameter = new StringBuilder(); + int position = actionsIfTwoParameters(expression, i, firstParameter, secondParameter); + LOG.debug(firstParameter + " " + secondParameter); + numbers.push(calcFunctionWithTwoParameters(variable, + calc(variables, firstParameter.toString()), + calc(variables, secondParameter.toString()))); + variable = ""; + readVariable = false; + needJump = true; + jump = position; + continue; + } + throw new ParsingException("Bad expression"); + } + numbers.push(Double.parseDouble(variables.get(variable))); + variable = ""; + readVariable = false; + } + if (canBeUnary && (currentSymbol == '+' || currentSymbol == '-')) { + if (currentSymbol == '+') { + currentSymbol = '&'; + } else { + currentSymbol = '@'; + } + } + if (currentSymbol == '(') { + operations.push(currentSymbol); + canBeUnary = true; + continue; + } + if (currentSymbol == ')') { + char last = '^'; + while (operations.size() != 0) { + last = operations.pop(); + if (last == '(') { + break; + } + calcSimpleOperation(last, numbers); + } + canBeUnary = false; + if (last != '(') { + LOG.debug(Integer.toString(i)); + throw new ParsingException("Bad expression"); + } + continue; + } + while (operations.size() != 0) { + char back = operations.pop(); + if (getPriorityOperation(back) >= getPriorityOperation(currentSymbol)) { + calcSimpleOperation(back, numbers); + } else { + operations.push(back); + break; + } + } + operations.push(currentSymbol); + canBeUnary = true; + } + if (numbers.size() != 1 || operations.size() != 0) { + throw new ParsingException("Bad expression"); + } + double answer = numbers.pop(); + LOG.debug(Double.toString(answer) + " " + expression); + return answer; + } + + private boolean isFunctionWithOneParameter(String f) { + return f.equals("sin") || f.equals("cos") || f.equals("tg") || f.equals("sqrt") || + f.equals("abs") || f.equals("sign") || f.equals("log2"); + } + + private boolean isFunctionWithTwoParameters(String f) { + return (f.equals("pow") || f.equals("log") || f.equals("max") || f.equals("min")); + } + + private void calcSimpleOperation(char symbol, LinkedList numbers) throws ParsingException { + if (isUnary(symbol)) { + if (numbers.size() < 1) { + throw new ParsingException("Not enough operands for unary operation"); + } + if (symbol == '@') { + double x = numbers.pop(); + numbers.push(-x); + } + return; + } + if (numbers.size() < 2) { + throw new ParsingException("Not enough operands for operation"); + } + double x = numbers.pop(); + double y = numbers.pop(); + if (symbol == '+') { + numbers.push(y + x); + } + if (symbol == '-') { + numbers.push(y - x); + } + if (symbol == '*') { + numbers.push(y * x); + } + if (symbol == '/') { + numbers.push(y / x); + } + } + + private Double calcFunctionWithOneParameter(String f, Double x) { + if (f.equals("sin")) { + return Math.sin(x); + } + if (f.equals("cos")) { + return Math.cos(x); + } + if (f.equals("tg")) { + return Math.tan(x); + } + if (f.equals("sqrt")) { + return Math.sqrt(x); + } + if (f.equals("abs")) { + return Math.abs(x); + } + if (f.equals("sign")) { + return Math.signum(x); + } + if (f.equals("log2")) { + return Math.log(x) / Math.log(2); + } + return 0.0; + } + + private double calcFunctionWithTwoParameters(String f, Double x, Double y) { + if (f.equals("pow")) { + return Math.pow(x, y); + } + if (f.equals("log")) { + return Math.log(x) / Math.log(y); + } + if (f.equals("max")) { + return Math.max(x, y); + } + if (f.equals("min")) { + return Math.min(x, y); + } + return 0.0; + } + + private int getPriorityOperation(char symbol) { + if (isUnary(symbol)) { + return 2; + } + if (symbol == '+' || symbol == '-') { + return 0; + } + if (symbol == '*' || symbol == '/') { + return 1; + } + return -1; + } + + private boolean isUnary(char symbol) { + return (symbol == '&' || symbol == '@'); // & := +, @ := - + } + + private boolean isPossibleSymbol(char symbol) { + return (symbol == '+' || symbol == '-' || symbol == '*' || symbol == '/' || symbol == '(' || symbol == ')'); + } + + int actionsIfOneParameter(String expression, int i, StringBuilder parameter) + throws ParsingException { + int position = i; + if (expression.charAt(position) != '(') { + throw new ParsingException("Bad expression"); + } + int newBalance = 1; + position += 1; + while (newBalance != 0 && position < expression.length()) { + if (expression.charAt(position) == '(') { + newBalance += 1; + } + if (expression.charAt(position) == ')') { + newBalance -= 1; + } + if (newBalance != 0) { + parameter.append(expression.charAt(position)); + } + position += 1; + } + if (position == expression.length() && newBalance != 0) { + throw new ParsingException("Bad expression"); + } + return position; + } + + int actionsIfTwoParameters(String expression, int i, StringBuilder firstParameter, StringBuilder secondParameter) + throws ParsingException { + int position = i; + boolean secondAlready = false; + if (expression.charAt(position) != '(') { + throw new ParsingException("Bad expression"); + } + int newBalance = 1; + position += 1; + while (newBalance != 0 && position < expression.length()) { + if (expression.charAt(position) == '(') { + newBalance += 1; + } + if (expression.charAt(position) == ')') { + newBalance -= 1; + } + if (newBalance != 0) { + if (newBalance == 1 && expression.charAt(position) == ',') { + secondAlready = true; + } else { + if (!secondAlready) { + firstParameter.append(expression.charAt(position)); + } else { + secondParameter.append(expression.charAt(position)); + } + } + } + position += 1; + } + if (position == expression.length() && newBalance != 0) { + throw new ParsingException("Bad expression"); + } + return position; + } + + private boolean isSplitedNumbers(String expression) { + Character lastSymbol = '^'; + for (int i = 0; i < expression.length(); ++i) { + if ((isDigit(lastSymbol) || lastSymbol == '.' || isLetter(lastSymbol)) && + (isDigit(expression.charAt(i)) || expression.charAt(i) == '.' || + isLetter(expression.charAt(i))) && + !(isDigit(expression.charAt(i - 1)) || expression.charAt(i - 1) == '.' || + isLetter(expression.charAt(i - 1)))) { + return true; + } + if (!isWhitespace(expression.charAt(i))) { + lastSymbol = expression.charAt(i); + } + } + return false; + } + + public boolean isPredefinedFunction(String f) { + return isFunctionWithOneParameter(f) || isFunctionWithTwoParameters(f) || f.equals("rnd"); + } +} + diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/CalculatorController.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/CalculatorController.java new file mode 100644 index 000000000..d85a5b68f --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/CalculatorController.java @@ -0,0 +1,126 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +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.RestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestBody; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.List; +import java.util.Map; + +import static java.lang.Character.isDigit; +import static java.lang.Character.isLetter; + +@RestController +@RequestMapping(produces = "text/plain") +public class CalculatorController { + + private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class); + + @Autowired + private Calculator calculator; + @Autowired + private BillingDao billingDao; + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.GET) + public String getVariable(Authentication authentication, @PathVariable String variableName) + throws ParsingException { + return billingDao.getVariable(authentication.getName(), variableName).getExpression(); + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.PUT, consumes = "text/plain") + public void putVariable(Authentication authentication, @PathVariable String variableName, + @RequestBody String expression) throws ParsingException { + billingDao.addVariable(authentication.getName(), variableName, + calculator.calculate(billingDao.getVariables(authentication.getName()), expression), expression); + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.DELETE) + public void deleteVariable(Authentication authentication, @PathVariable String variableName) + throws ParsingException { + billingDao.deleteVariable(authentication.getName(), variableName); + } + + @RequestMapping(path = "/variable", method = RequestMethod.GET, produces = "text/plain") + public String getVariables(Authentication authentication) throws ParsingException { + StringBuilder result = new StringBuilder(); + for (Map.Entry entry : billingDao.getVariables(authentication.getName()).entrySet()) { + result.append(entry.getKey()); + result.append("\n"); + } + return result.toString(); + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.GET) + public String getFunction(Authentication authentication, @PathVariable String functionName) + throws ParsingException { + if (calculator.isPredefinedFunction(functionName)) { + throw new ParsingException("This function is already defined"); + } + return billingDao.getFunction(authentication.getName(), functionName).getExpression(); + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.PUT, consumes = "text/plain") + public void putFunction(Authentication authentication, @PathVariable String functionName, + @RequestParam(value = "arguments", defaultValue = "") List arguments, + @RequestBody String expression) throws ParsingException { + if (!checkName(functionName)) { + throw new ParsingException("Invalid name"); + } + if (calculator.isPredefinedFunction(functionName)) { + throw new ParsingException("This function is already defined"); + } + billingDao.addFunction(authentication.getName(), functionName, arguments, expression); + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.DELETE) + public void deleteFunction(Authentication authentication, @PathVariable String functionName) + throws ParsingException { + billingDao.deleteFunction(authentication.getName(), functionName); + } + + @RequestMapping(path = "/function/", method = RequestMethod.GET) + public String getFunctions(Authentication authentication) throws ParsingException { + StringBuilder result = new StringBuilder(); + for (Map.Entry entry : billingDao.getFunctions(authentication.getName()).entrySet()) { + result.append(entry.getKey()); + result.append("\n"); + } + return result.toString(); + } + + @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain") + public String eval(Authentication authentication, @RequestBody String expression) throws ParsingException { + LOG.debug("Evaluation request: [" + expression + "]"); + double result = calculator.calculate(billingDao.getVariables(authentication.getName()), expression); + LOG.trace("Result: " + Double.toString(result)); + return Double.toString(result); + } + + @RequestMapping(path = "/newUser/{userName}", method = RequestMethod.PUT, consumes = "text/plain") + public void newUser(@PathVariable String userName, @RequestBody String password) throws ParsingException { + LOG.debug("New user: [" + userName + ' ' + password + "]"); + if (billingDao.createNewUser(userName, password, true)) { + LOG.trace("Success"); + } else { + LOG.trace("Fail"); + } + } + + private static boolean checkName(String name) { + for (int i = 0; i < name.length(); ++i) { + char ch = name.charAt(i); + if (!(isLetter(ch) || ch != '_' || isDigit(ch))) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Function.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Function.java new file mode 100644 index 000000000..29fbc4b53 --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Function.java @@ -0,0 +1,40 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class Function { + + private static final Logger LOG = LoggerFactory.getLogger(Variable.class); + + private String userName; + private String name; + private List arguments = new ArrayList<>(); + private String expression; + + public Function(String u, String n, List a, String e) { + userName = u; + name = n; + arguments = a; + expression = e; + } + + public String getUserName() { + return userName; + } + + public String getName() { + return name; + } + + public List getArguments() { + return arguments; + } + + public String getExpression() { + return expression; + } +} \ No newline at end of file diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/SecurityServiceConfiguration.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/SecurityServiceConfiguration.java new file mode 100644 index 000000000..70ac97207 --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/SecurityServiceConfiguration.java @@ -0,0 +1,58 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +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; + +@Configuration +@EnableWebSecurity +public class SecurityServiceConfiguration extends WebSecurityConfigurerAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(SecurityServiceConfiguration.class); + + @Autowired + private BillingDao billingDao; + + @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("/eval/**").authenticated() + .antMatchers("/variable/**").authenticated() + .antMatchers("/function/**").authenticated() + .anyRequest().permitAll(); + } + + @Autowired + public void registerGlobalAuthentication(AuthenticationManagerBuilder authentication) throws Exception { + LOG.info("Registering global user details service"); + authentication.userDetailsService(userName -> { + try { + BillingUser user = billingDao.loadUser(userName); + return new User( + user.getUserName(), + user.getPassword(), + Collections.singletonList(() -> "AUTHENTICATION") + ); + } catch (EmptyResultDataAccessException e) { + LOG.warn("No such user: " + userName); + throw new UsernameNotFoundException(userName); + } + }); + } +} \ No newline at end of file diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Variable.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Variable.java new file mode 100644 index 000000000..bcfd85310 --- /dev/null +++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Variable.java @@ -0,0 +1,37 @@ +package ru.mipt.java2016.homework.g596.kozlova.task4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Variable { + + private static final Logger LOG = LoggerFactory.getLogger(Variable.class); + + private String userName; + private String name; + private double value; + private String expression; + + public Variable(String u, String n, double v, String e) { + userName = u; + name = n; + value = v; + expression = e; + } + + public String getUserName() { + return userName; + } + + public String getName() { + return name; + } + + public Double getValue() { + return value; + } + + public String getExpression() { + return expression; + } +} \ No newline at end of file