diff --git a/homework-g596-gerasimov/pom.xml b/homework-g596-gerasimov/pom.xml index c7b25564f..ffe7babd7 100644 --- a/homework-g596-gerasimov/pom.xml +++ b/homework-g596-gerasimov/pom.xml @@ -11,6 +11,11 @@ 4.0.0 homework-g596-gerasimov + + + 1.4.2.RELEASE + + ru.mipt.java2016 @@ -27,6 +32,32 @@ guava 20.0 + + + 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 + diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/Application.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/Application.java new file mode 100644 index 000000000..5450382f1 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/Application.java @@ -0,0 +1,41 @@ +package ru.mipt.java2016.homework.g596.gerasimov.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; +import ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator.NewCalculator; + +/** + * 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" + */ +@EnableAutoConfiguration +@Configuration +@ComponentScan(basePackageClasses = Application.class) +public class Application { + + @Bean + public NewCalculator calculator() { + return new NewCalculator(); + } + + @Bean + public EmbeddedServletContainerCustomizer customizer( + @Value("${ru.mipt.java2016.homework.g596.gerasimov.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); + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDao.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDao.java new file mode 100644 index 000000000..16db2750d --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDao.java @@ -0,0 +1,223 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Vector; +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@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); + initSchema(); + } + + public void initSchema() { + LOG.trace("Initializing schema"); +// jdbcTemplate.execute("DROP SCHEMA IF EXISTS billing"); + 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," + + " CONSTRAINT PK_variable PRIMARY KEY (username, name))"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions " + + "(username VARCHAR, name VARCHAR, args VARCHAR, expression VARCHAR," + + " CONSTRAINT PK_function PRIMARY KEY (username, name))"); + jdbcTemplate.execute("DELETE FROM billing.users WHERE username = 'username'"); + jdbcTemplate.update("INSERT INTO billing.users VALUES ('username', 'password', 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}, new RowMapper() { + @Override + public BillingUser mapRow(ResultSet rs, int rowNum) throws SQLException { + return new BillingUser(rs.getString("username"), rs.getString("password"), + rs.getBoolean("enabled")); + } + }); + } + + public boolean addUser(String username, String password) { + LOG.trace("Adding user " + username); + try { + jdbcTemplate.update("INSERT INTO billing.users VALUES ('" + username + "', '" + password + + "', TRUE)"); + } catch (DuplicateKeyException exception) { + LOG.debug(exception.getMessage()); + return false; + } + LOG.trace("User " + username + " was successfully added"); + return true; + } + + public boolean addVariable(BillingVariable variable) { + try { + jdbcTemplate.update("Insert INTO billing.variables VALUES ('" + variable.getUsername() + + "', '" + variable.getName() + "', " + variable.getValue() + ", '" + variable + .getExpression() + "')"); + } catch (DuplicateKeyException exception) { + LOG.debug(exception.getMessage()); + return false; + } + return true; + } + + public BillingVariable getVariable(String username, String name) { + LOG.trace("Querying for variable " + username + ":" + name); + return jdbcTemplate.queryForObject( + "SELECT username, name, value, expression FROM billing.variables WHERE " + + "username = '" + username + "' AND name = '" + name + "'", + new RowMapper() { + @Override + public BillingVariable mapRow(ResultSet rs, int rowNum) throws SQLException { + return new BillingVariable(rs.getString("username"), rs.getString("name"), + rs.getDouble("value"), rs.getNString("expression")); + } + }); + } + + public List getAllVariables(String username) { + LOG.trace("Querying for all variables for " + username); + return jdbcTemplate + .query("SELECT username, name, value, expression FROM billing.variables WHERE username = '" + + username + "'", + new RowMapper() { + @Override + public BillingVariable mapRow(ResultSet rs, int rowNum) throws SQLException { + return new BillingVariable(rs.getString("username"), rs.getString("name"), + rs.getDouble("value"), rs.getNString("expression")); + } + }); + } + + public boolean deleteVariable(String username, String name) { + LOG.trace("Deleting variable " + username + ":" + name); + try { + jdbcTemplate.update("DELETE FROM billing.variables WHERE " + "username = '" + username + + "' AND name = '" + name + "'"); + } catch (Exception exception) { + LOG.debug(exception.getMessage()); + return false; + } + LOG.trace("Variable " + username + ":" + name + " was successfully deleted"); + return true; + } + + public boolean deleteAllVariables(String username) { + LOG.trace("Deleting variables of " + username); + try { + jdbcTemplate.update("DELETE FROM billing.variables WHERE " + "username = '" + username + + "'"); + } catch (Exception exception) { + LOG.debug(exception.getMessage()); + return false; + } + LOG.trace("Variables of " + username + " were successfully deleted"); + return true; + } + + public BillingFunction getFunction(String username, String name) { + LOG.trace("Querying for function " + username + ":" + name); + return jdbcTemplate.queryForObject( + "SELECT username, name, args, expression FROM billing.functions WHERE " + + "username = '" + username + "' AND name = '" + name + "'", + new RowMapper() { + @Override + public BillingFunction mapRow(ResultSet rs, int rowNum) throws SQLException { + return new BillingFunction(rs.getString("username"), rs.getString("name"), + decodeArgs(rs.getString("args")), rs.getNString("expression")); + } + }); + } + + + public boolean addFunction(BillingFunction function) { + try { + jdbcTemplate.update("Insert INTO billing.functions VALUES ('" + function.getUsername() + + "', '" + function.getName() + "', '" + encodeArgs(function.getArgsName()) + + "', '" + function.getExpression() + "')"); + } catch (DuplicateKeyException exception) { + LOG.debug(exception.getMessage()); + return false; + } + return true; + } + + public List getAllFunctions(String username) { + LOG.trace("Querying for all functions for " + username); + return jdbcTemplate + .query("SELECT username, name, args, expression FROM billing.functions WHERE " + + "username = '" + username + "'", + new RowMapper() { + @Override + public BillingFunction mapRow(ResultSet rs, int rowNum) throws SQLException { + return new BillingFunction(rs.getString("username"), rs.getString("name"), + decodeArgs(rs.getString("args")), rs.getNString("expression")); + } + }); + } + + public boolean deleteFunction(String username, String name) { + LOG.trace("Deleting variable " + username + ":" + name); + try { + jdbcTemplate.update("DELETE FROM billing.functions WHERE " + "username = '" + username + + "' AND name = '" + name + "'"); + } catch (Exception exception) { + LOG.debug(exception.getMessage()); + return false; + } + LOG.trace("Function " + username + ":" + name + " was successfully deleted"); + return true; + } + + + private Vector decodeArgs(String code) { + List argsName = new Vector<>(); + StringBuilder stringBuilder = new StringBuilder(); + + for (char currentChar : code.toCharArray()) { + if (currentChar == ',') { + argsName.add(stringBuilder.toString()); + stringBuilder.setLength(0); + } else { + stringBuilder.append(currentChar); + } + } + argsName.add(stringBuilder.toString()); + + return new Vector(argsName); + } + + private String encodeArgs(List argsName) { + StringBuilder stringBuilder = new StringBuilder(); + + for (String arg : argsName) { + stringBuilder.append(arg + ","); + } + stringBuilder.deleteCharAt(stringBuilder.length() - 1); + + return stringBuilder.toString(); + } +} \ No newline at end of file diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDatabaseConfiguration.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDatabaseConfiguration.java new file mode 100644 index 000000000..003866e10 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDatabaseConfiguration.java @@ -0,0 +1,24 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class BillingDatabaseConfiguration { + @Bean + public DataSource billingDataSource( + @Value("${ru.mipt.java2016.homework.g596.gerasimov.task4.jdbcUrl}") String jdbcUrl, + @Value("${ru.mipt.java2016.homework.g596.gerasimov.task4.username:}") String username, + @Value("${ru.mipt.java2016.homework.g596.gerasimov.task4.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-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingFunction.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingFunction.java new file mode 100644 index 000000000..35d695616 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingFunction.java @@ -0,0 +1,65 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +import java.util.List; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator.NewCalculator; + +/** + * Created by geras-artem on 20.12.16. + */ +public class BillingFunction { + private final String username; + private final String name; + private final Vector argsName; + private final String expression; + + public BillingFunction(String username, String name, Vector argsName, String expression) + throws IllegalArgumentException { + if (name == null) { + throw new IllegalArgumentException("Empty function name"); + } + this.username = username; + this.name = name; + this.argsName = argsName; + this.expression = expression; + } + + public String getUsername() { + return username; + } + + public String getName() { + return name; + } + + public List getArgsName() { + return argsName; + } + + public String getExpression() { + return expression; + } + + public double calculate(List arguments, List functions, + NewCalculator calculator) throws ParsingException { + if (arguments.size() != argsName.size()) { + throw new ParsingException("Wrong expression"); + } + + String newExpression = expression.replaceAll("\\s", ""); + newExpression = "(" + newExpression + ")"; + + for (int i = 0; i < arguments.size(); ++i) { + Pattern pattern = + Pattern.compile("([-+*/(),])" + argsName.get(i) + "([-+*/(),])"); + Matcher matcher = pattern.matcher(newExpression); + newExpression = + matcher.replaceAll("$1" + arguments.get(i).toString() + "$2"); + } + + return calculator.calculate(newExpression, functions); + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingUser.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingUser.java new file mode 100644 index 000000000..2ce04649d --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingUser.java @@ -0,0 +1,66 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +public class BillingUser { + private final String username; + private final String password; + private final boolean enabled; + + public BillingUser(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 "BillingUser{" + "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; + } + + BillingUser that = (BillingUser) o; + + if (enabled != that.enabled) { + return false; + } + if (!username.equals(that.username)) { + return false; + } + return 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; + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingVariable.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingVariable.java new file mode 100644 index 000000000..9b83d95c5 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingVariable.java @@ -0,0 +1,38 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +/** + * Created by geras-artem on 19.12.16. + */ +public class BillingVariable { + private final String username; + private final String name; + private final double value; + private final String expression; + + public BillingVariable(String username, String name, double value, String expression) + throws IllegalArgumentException { + if (name == null) { + throw new IllegalArgumentException("Empty variable name"); + } + this.username = username; + this.name = name; + this.value = value; + this.expression = expression; + } + + public String getUsername() { + return username; + } + + public String getName() { + return name; + } + + public double getValue() { + return value; + } + + public String getExpression() { + return expression; + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/CalculatorController.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/CalculatorController.java new file mode 100644 index 000000000..358d13ce1 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/CalculatorController.java @@ -0,0 +1,195 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +import java.util.Arrays; +import java.util.List; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +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.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator.NewCalculator; + +@RestController +public class CalculatorController { + private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class); + @Autowired private NewCalculator calculator; + @Autowired private BillingDao billingDao; + + @RequestMapping(path = "/ping", method = RequestMethod.GET, produces = "text/plain") + public String echo() { + return "OK\n"; + } + + @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", + produces = "text/plain") + public String eval(Authentication authentication, @RequestBody String expression) + throws ParsingException { + LOG.debug("Evaluation request: [" + expression + "]"); + double result; + try { + expression = reformatExpression(authentication.getName(), expression); + result = calculator.calculate(expression, billingDao.getAllFunctions(authentication.getName())); + } catch (ParsingException exception) { + LOG.trace(exception.getMessage()); + return exception.getMessage() + "\n"; + } + LOG.trace("Result: " + result); + return Double.toString(result) + "\n"; + } + + @RequestMapping(path = "/variable/{name}", method = RequestMethod.GET, produces = "text/plain") + public String getVariable(Authentication authentication, @PathVariable String name) { + LOG.debug("Get request for variable: " + authentication.getName() + ":" + name); + return billingDao.getVariable(authentication.getName(), name).getExpression() + "\n"; + } + + @RequestMapping(path = "/variable", method = RequestMethod.GET, produces = "text/plain") + public String getAllVariable(Authentication authentication) { + LOG.debug("Get request for ALL variables: " + authentication.getName()); + StringBuilder stringBuilder = new StringBuilder(); + List variables = billingDao.getAllVariables(authentication.getName()); + + if (variables.size() == 0) { + return "There are no variables\n"; + } + + for (BillingVariable var : variables) { + stringBuilder.append(var.getName() + ":" + var.getValue() + ":" + var.getExpression() + + "\n"); + } + + LOG.trace("Get request completed"); + + return stringBuilder.toString(); + } + + @RequestMapping(path = "/variable/{name}", method = RequestMethod.PUT, consumes = "text/plain", + produces = "text/plain") + public String addVariable(Authentication authentication, @PathVariable String name, + @RequestBody String expression) { + LOG.debug("Add request for variable: " + authentication.getName() + ":" + name); + String newExpression = reformatExpression(authentication.getName(), expression); + BillingVariable variable; + + try { + variable = new BillingVariable(authentication.getName(), name, + calculator.calculate(newExpression, billingDao + .getAllFunctions(authentication.getName())), expression); + } catch (ParsingException exception) { + LOG.error(exception.getMessage()); + return exception.getMessage() + "\n"; + } + + if (billingDao.addVariable(variable)) { + LOG.trace("Variable successfully added: " + authentication.getName() + ":" + name); + return "New variable successfully added\n"; + } + + LOG.trace("Variable already exists: " + authentication.getName() + ":" + name); + return "Variable already exists\n"; + } + + @RequestMapping(path = "/variable/{name}", method = RequestMethod.DELETE, + consumes = "text/plain", produces = "text/plain") + public String deleteVariable(Authentication authentication, @PathVariable String name) { + LOG.debug("Delete request for variable: " + authentication.getName() + ":" + name); + + if (billingDao.deleteVariable(authentication.getName(), name)) { + LOG.trace("Variable was successfully deleted: " + authentication.getName() + ":" + name); + return "Variable successfully deleted\n"; + } + + LOG.trace("Variable does't exists: " + authentication.getName() + ":" + name); + return "Variable doesn't exist\n"; + } + + + @RequestMapping(path = "/variable", method = RequestMethod.DELETE, produces = "text/plain") + public String deleteAllVariables(Authentication authentication) { + LOG.debug("Delete request for ALL variables: " + authentication.getName()); + + if (billingDao.deleteAllVariables(authentication.getName())) { + LOG.trace("Variables were successfully deleted: " + authentication.getName()); + return "Variables successfully deleted\n"; + } + + LOG.trace("No variables available: " + authentication.getName()); + return "No variables for user " + authentication.getName() + "\n"; + } + + @RequestMapping(path = "/function/{name}", method = RequestMethod.GET, produces = "text/plain") + public String getFunction(Authentication authentication, @PathVariable String name) { + LOG.debug("Get request for function: " + authentication.getName() + ":" + name); + + BillingFunction function = billingDao.getFunction(authentication.getName(), name); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(function.getName() + "("); + + for (String arg : function.getArgsName()) { + stringBuilder.append(arg + ","); + } + stringBuilder.deleteCharAt(stringBuilder.length() - 1); + stringBuilder.append(") = " + function.getExpression() + "\n"); + + return stringBuilder.toString(); + } + + @RequestMapping(path = "/function/{name}", method = RequestMethod.PUT, consumes = "text/plain", + produces = "text/plain") + public String addFunction(Authentication authentication, @PathVariable String name, + @RequestBody String expression, @RequestParam(value = "args") String args) { + LOG.debug("Add request for function: " + authentication.getName() + ":" + name); + + Vector arguments = new Vector(Arrays.asList(args.split(","))); + BillingFunction function = new BillingFunction(authentication.getName(), name, + arguments, expression); + + if (billingDao.addFunction(function)) { + LOG.trace("Function successfully added: " + authentication.getName() + ":" + name); + return "New function successfully added\n"; + } + + LOG.trace("Function already exists: " + authentication.getName() + ":" + name); + return "Function already exists\n"; + } + + @RequestMapping(path = "/function/{name}", method = RequestMethod.DELETE, + consumes = "text/plain", produces = "text/plain") + public String deleteFunction(Authentication authentication, @PathVariable String name) { + LOG.debug("Delete request for function: " + authentication.getName() + ":" + name); + + if (billingDao.deleteFunction(authentication.getName(), name)) { + LOG.trace("Function was successfully deleted: " + authentication.getName() + ":" + name); + return "Function successfully deleted\n"; + } + + LOG.trace("Function does't exists: " + authentication.getName() + ":" + name); + return "Function doesn't exist\n"; + } + + + public String reformatExpression(String username, String expression) { + expression = expression.replaceAll("\\s", ""); + expression = "(" + expression + ")"; + List variables = billingDao.getAllVariables(username); + + for (BillingVariable currentVariable : variables) { + Pattern pattern = + Pattern.compile("([-+*/(),])" + currentVariable.getName() + "([-+*/(),])"); + Matcher matcher = pattern.matcher(expression); + expression = + matcher.replaceAll("$1" + Double.toString(currentVariable.getValue()) + "$2"); + } + + return expression; + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/BracketToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/BracketToken.java new file mode 100644 index 000000000..08217dbd3 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/BracketToken.java @@ -0,0 +1,25 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator; + +/** + * Created by geras-artem on 19.12.16. + */ +public class BracketToken extends Token { + private boolean opening; + + public BracketToken(char c) { + switch (c) { + case '(': + opening = true; + break; + case ')': + opening = false; + break; + default: + throw new IllegalArgumentException(); + } + } + + public boolean isOpening() { + return opening; + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/FunctionToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/FunctionToken.java new file mode 100644 index 000000000..32396dbcc --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/FunctionToken.java @@ -0,0 +1,110 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator; + +import java.util.List; +import java.util.Random; +import java.util.Vector; +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g596.gerasimov.task4.BillingFunction; + +/** + * Created by geras-artem on 19.12.16. + */ +public class FunctionToken extends Token { + private final String functionName; + + private final Vector arguments; + + private final double value; + + public FunctionToken(String functionName, Vector arguments, List funcs, + NewCalculator calculator) throws ParsingException { + this.functionName = functionName; + this.arguments = arguments; + this.value = calculateValue(funcs, calculator); + } + + private double calculateValue(List funcs, NewCalculator calculator) throws ParsingException { + Double[] args = arguments.toArray(new Double[arguments.size()]); + + switch (functionName) { + case ("sin"): + if (arguments.size() != 1) { + throw new ParsingException("Wrong function arguments"); + } + return Math.sin(args[0]); + case ("cos"): + if (arguments.size() != 1) { + throw new ParsingException("Wrong function arguments"); + } + return Math.cos(args[0]); + case ("tg"): + if (arguments.size() != 1) { + throw new ParsingException("Wrong function arguments"); + } + return Math.tan(args[0]); + case ("sqrt"): + if (arguments.size() != 1) { + throw new ParsingException("Wrong function arguments"); + } + return Math.sqrt(args[0]); + case ("pow"): + if (arguments.size() != 2) { + throw new ParsingException("Wrong function arguments"); + } + return Math.pow(args[0], args[1]); + case ("abs"): + if (arguments.size() != 1) { + throw new ParsingException("Wrong function arguments"); + } + return Math.abs(args[0]); + case ("sign"): + if (arguments.size() != 1) { + throw new ParsingException("Wrong function arguments"); + } + return Math.signum(args[0]); + case ("log"): + if (arguments.size() != 2) { + throw new ParsingException("Wrong function arguments"); + } + return Math.log(args[0]) / Math.log(args[1]); + case ("log2"): + if (arguments.size() != 1) { + throw new ParsingException("Wrong function arguments"); + } + return Math.log(args[0]) / Math.log(2); + case ("rnd"): + if (arguments.size() != 0) { + throw new ParsingException("Wrong function arguments"); + } + return new Random().nextDouble(); + case ("max"): + if (arguments.size() != 2) { + throw new ParsingException("Wrong function arguments"); + } + return Math.max(args[0], args[1]); + case ("min"): + if (arguments.size() != 2) { + throw new ParsingException("Wrong function arguments"); + } + return Math.min(args[0], args[1]); + default: + BillingFunction function = null; + for (BillingFunction temp : funcs) { + if (temp.getName().equals(functionName)) { + function = temp; + break; + } + } + + if (function == null) { + throw new ParsingException("Wrong function name: " + functionName); + } + + return function.calculate(arguments, funcs, calculator); + } + } + + public double getValue() { + return value; + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NewCalculator.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NewCalculator.java new file mode 100644 index 000000000..7d71393f3 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NewCalculator.java @@ -0,0 +1,189 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; +import java.util.Vector; +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g596.gerasimov.task4.BillingFunction; + +/** + * Created by geras-artem on 12.10.16. + */ + +public class NewCalculator { + public double calculate(String expression, List funcs) throws ParsingException { + if (expression == null) { + throw new ParsingException("Wrong expression"); + } + expression = expression.replaceAll("[\\s]", ""); + ArrayList iExpr = tokenize(expression, funcs); + if (iExpr.size() == 0) { + throw new ParsingException("Wrong expression"); + } + return calculation(iExpr); + } + + private ArrayList tokenize(String expression, List funcs) throws ParsingException { + ArrayList res = new ArrayList<>(); + char currentChar; + boolean gotNum = false; + boolean gotUOp = false; + + for (int i = 0; i < expression.length(); ++i) { + currentChar = expression.charAt(i); + + if (Character.isDigit(currentChar)) { + StringBuilder sNum = new StringBuilder(); + boolean metDot = false; + while (i < expression.length() && (Character.isDigit(currentChar) || ( + currentChar == '.' && !metDot))) { + sNum.append(currentChar); + if (currentChar == '.') { + metDot = true; + } + ++i; + if (i < expression.length()) { + currentChar = expression.charAt(i); + } + } + --i; + gotNum = true; + gotUOp = false; + res.add(new NumToken(Double.parseDouble(sNum.toString()))); + } else if (currentChar == '(' || currentChar == ')') { + if (currentChar == '(' && res.size() > 0 && res + .get(res.size() - 1) instanceof BracketToken && ((BracketToken) res + .get(res.size() - 1)).isOpening()) { + throw new ParsingException("Wrong expression"); + } + gotNum = !(currentChar == '('); + gotUOp = false; + res.add(new BracketToken(currentChar)); + } else if (currentChar == '+' || currentChar == '-' || currentChar == '*' + || currentChar == '/') { + if (!gotNum) { + if (gotUOp) { + throw new ParsingException("Wrong expression"); + } + switch (currentChar) { + case '+': + currentChar = '#'; + gotUOp = true; + break; + case '-': + currentChar = '&'; + gotUOp = true; + break; + default: + throw new ParsingException("Wrong expression"); + } + } + gotNum = false; + res.add(new OperatorToken(currentChar)); + } else { + StringBuilder functionName = new StringBuilder(); + + ++i; + for (; i < expression.length() && currentChar != '('; ++i) { + functionName.append(currentChar); + currentChar = expression.charAt(i); + } + + if (i == expression.length()) { + throw new ParsingException("Wrong expression"); + } + + int tempBracketBalance = 1; + + Vector arguments = new Vector<>(); + StringBuilder argument = new StringBuilder(); + + for (; i < expression.length() && tempBracketBalance > 0; ++i) { + currentChar = expression.charAt(i); + if (currentChar == ',' && tempBracketBalance == 1) { + arguments.addElement(calculate(argument.toString(), funcs)); + argument.setLength(0); + continue; + } + if (currentChar == '(') { + ++tempBracketBalance; + } + if (currentChar == ')') { + --tempBracketBalance; + if (tempBracketBalance == 0) { + arguments.addElement(calculate(argument.toString(), funcs)); + break; + } + } + argument.append(currentChar); + } + + if (i == expression.length() && tempBracketBalance != 0) { + throw new ParsingException("Wrong expression"); + } + + gotNum = true; + gotUOp = false; + res.add(new FunctionToken(functionName.toString(), arguments, funcs, this)); + } + } + + return res; + } + + private double calculation(ArrayList iExpr) throws ParsingException { + Stack numbers = new Stack<>(); + Stack operators = new Stack<>(); + int bracketBalance = 0; + + for (Token tmp : iExpr) { + if (tmp instanceof NumToken) { + numbers.push(((NumToken) tmp).getValue()); + } else if (tmp instanceof FunctionToken) { + numbers.push(((FunctionToken) tmp).getValue()); + } else if (tmp instanceof OperatorToken) { + Operator op = ((OperatorToken) tmp).getOperator(); + while (operators.size() > 0 && operators.peek() instanceof OperatorToken) { + Operator prev = ((OperatorToken) operators.peek()).getOperator(); + if ((op.getPriority() <= prev.getPriority() && op.getIsLA()) || ( + op.getPriority() < prev.getPriority())) { + prev.use(numbers); + operators.pop(); + } else { + break; + } + } + + operators.push(tmp); + } else { + boolean isOpening = ((BracketToken) tmp).isOpening(); + if (isOpening) { + operators.push(tmp); + ++bracketBalance; + } else { + --bracketBalance; + if (bracketBalance < 0) { + throw new ParsingException("Wrong expression"); + } + while (operators.peek() instanceof OperatorToken) { + ((OperatorToken) operators.pop()).getOperator().use(numbers); + } + operators.pop(); + } + } + } + while (!operators.empty()) { + ((OperatorToken) operators.pop()).getOperator().use(numbers); + } + + if (bracketBalance != 0) { + throw new ParsingException("Wrong expression"); + } + if (numbers.size() != 1) { + throw new ParsingException("Wrong expression"); + } + + return numbers.pop(); + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NumToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NumToken.java new file mode 100644 index 000000000..c557e5786 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NumToken.java @@ -0,0 +1,17 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator; + +/** + * Created by geras-artem on 19.12.16. + */ +public class NumToken extends Token { + private double value; + + public NumToken(double value) { + this.value = value; + } + + public double getValue() { + return value; + } +} + diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Operator.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Operator.java new file mode 100644 index 000000000..e666c0a62 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Operator.java @@ -0,0 +1,70 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator; + +import java.util.Stack; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +/** + * Created by geras-artem on 19.12.16. + */ +public enum Operator { + + PLUS(1, 2, true), MINUS(1, 2, true), MULTIPLY(2, 2, true), DIVIDE(2, 2, true), U_PLUS(3, 1, + false), U_MINUS(3, 1, false); + + private int priority; + private int valency; + private boolean isLA; + + Operator(int priority, int valency, boolean isLA) { + this.priority = priority; + this.valency = valency; + this.isLA = isLA; + } + + public void use(Stack nums) throws ParsingException { + if (nums.size() < valency) { + throw new ParsingException("Wrong expression"); + } + + double tmp; + switch (this) { + case PLUS: + tmp = nums.pop(); + tmp += nums.pop(); + break; + case MINUS: + tmp = -nums.pop(); + tmp += nums.pop(); + break; + case MULTIPLY: + tmp = nums.pop(); + tmp *= nums.pop(); + break; + case DIVIDE: + tmp = 1 / nums.pop(); + tmp *= nums.pop(); + break; + case U_PLUS: + tmp = nums.pop(); + break; + case U_MINUS: + tmp = -nums.pop(); + break; + default: + throw new IllegalStateException(); + } + nums.push(tmp); + } + + public int getPriority() { + return priority; + } + + public int getValency() { + return valency; + } + + public boolean getIsLA() { + return isLA; + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/OperatorToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/OperatorToken.java new file mode 100644 index 000000000..bd01c957d --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/OperatorToken.java @@ -0,0 +1,38 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator; + +/** + * Created by geras-artem on 19.12.16. + */ +public class OperatorToken extends Token { + private Operator op; + + public OperatorToken(char c) { + switch (c) { + case '+': + op = Operator.PLUS; + break; + case '-': + op = Operator.MINUS; + break; + case '*': + op = Operator.MULTIPLY; + break; + case '/': + op = Operator.DIVIDE; + break; + case '#': + op = Operator.U_PLUS; + break; + case '&': + op = Operator.U_MINUS; + break; + default: + throw new IllegalArgumentException(); + } + } + + public Operator getOperator() { + return op; + } +} + diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Token.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Token.java new file mode 100644 index 000000000..1fb4781af --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Token.java @@ -0,0 +1,7 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator; + +/** + * Created by geras-artem on 19.12.16. + */ +abstract class Token { +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/RegistrationController.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/RegistrationController.java new file mode 100644 index 000000000..af788f995 --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/RegistrationController.java @@ -0,0 +1,33 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Created by geras-artem on 18.12.16. + */ + +@RestController +public class RegistrationController { + private static final Logger LOG = LoggerFactory.getLogger(RegistrationController.class); + + @Autowired private BillingDao billingDao; + + @RequestMapping(path = "/registration/{username}", method = RequestMethod.POST) + public String registration(@PathVariable String username, + @RequestParam(value = "password") String password) { + LOG.debug("Adding request for user: " + username); + if (!billingDao.addUser(username, password)) { + LOG.trace("Error"); + return "User already exists\n"; + } + LOG.trace("Success"); + return "User successfully added\nUsername: " + username + "\nPassword: " + password + "\n"; + } +} diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/SecurityServiceConfiguration.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/SecurityServiceConfiguration.java new file mode 100644 index 000000000..96e3a767b --- /dev/null +++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/SecurityServiceConfiguration.java @@ -0,0 +1,46 @@ +package ru.mipt.java2016.homework.g596.gerasimov.task4; + +import java.util.Collections; +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; + +@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/**", "/variable/**", + "/function/**").authenticated() + .anyRequest().permitAll(); + } + + @Autowired + public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception { + LOG.info("Registering global user details service"); + auth.userDetailsService(username -> { + try { + BillingUser user = billingDao.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); + } + }); + } +}