diff --git a/homework-g597-kochukov/pom.xml b/homework-g597-kochukov/pom.xml index 36920dce2..fe79b9bb0 100644 --- a/homework-g597-kochukov/pom.xml +++ b/homework-g597-kochukov/pom.xml @@ -10,6 +10,9 @@ 4.0.0 homework-g597-kochukov + + 1.4.2.RELEASE + @@ -17,6 +20,36 @@ homework-base 1.0.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} + + + org.xerial + sqlite-jdbc + 3.8.11.1 + + + com.zaxxer + HikariCP + 2.5.1 + + + com.h2database + h2 + 1.4.193 + ru.mipt.java2016 diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/BillingUser.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/BillingUser.java new file mode 100644 index 000000000..d92f47412 --- /dev/null +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/BillingUser.java @@ -0,0 +1,79 @@ +package ru.mipt.java2016.homework.g597.kochukov.task4; + +public class BillingUser { + private final String username; + private final String password; + private final Integer userid; + private final boolean enabled; + + public BillingUser(String username, String password, boolean enabled, Integer userid) { + 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; + this.userid = userid; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public Integer getUserId() { + return userid; + } + + 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; + } + if (!userid.equals(that.userid)) { + 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-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/CalculatorController.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/CalculatorController.java new file mode 100644 index 000000000..e2ce63b29 --- /dev/null +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/CalculatorController.java @@ -0,0 +1,152 @@ +package ru.mipt.java2016.homework.g597.kochukov.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.*; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; + +@RestController +public class CalculatorController { + private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class); + @Autowired + private MegaCalculator calculator; + + private DBWorker dbWorker = DBWorker.getInstance(); + + @RequestMapping(path = "/ping", method = RequestMethod.GET, produces = "text/plain") + public String echo() { + return "OK\n"; + } + + @RequestMapping(path = "/", method = RequestMethod.GET, produces = "text/html") + public String main(@RequestParam(required = false) String name) { + if (name == null) { + name = "world"; + } + return "" + + "tna0y_App" + + "

Hello, " + name + "!

" + + ""; + } + + @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain") + public String eval(Authentication authentication, @RequestBody String expr) throws ParsingException { + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + LOG.debug("Evaluation request: [" + expr + "]"); + LinkedHashMap scope = dbWorker.getUserScope(userId).getResult(); + Expression expression = new Expression(expr, scope); + double result; + try { + calculator.setUserid(userId); + result = calculator.calculate(expression); + LOG.trace("Result: " + result); + return Double.toString(result) + "\n"; + } catch (SQLException sqle) { + LOG.trace("Failed due to unknown functions"); + return "Using undeclared functions: " + sqle.getLocalizedMessage(); + } + } + + @RequestMapping(path = "/signup", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain") + public void signUp(@RequestParam(value = "args") String[] arguments) throws ParsingException { + String username = arguments[0]; + String password = arguments[1]; + + LOG.debug("user added:" + username); + System.out.println("USER ADDED: " + username); + LOG.trace(username); + LOG.trace(password); + + dbWorker.register(username, password); + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.PUT, + consumes = "text/plain", produces = "text/plain") + public void putVariable(Authentication authentication, + @PathVariable String variableName, + @RequestBody String expr) { + LOG.trace("Creating variable"); + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + try { + calculator.setUserid(userId); + double d = calculator.calculate(new Expression(expr, dbWorker.getUserScope(0).getResult())); + + dbWorker.setVariable(variableName, d, userId); + } catch (SQLException e) { + LOG.trace("Failed due to unknown functions"); + } catch (ParsingException e) { + LOG.trace("Failed due to incorrect syntax"); + } + } + + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.GET, + consumes = "text/plain", produces = "text/plain") + public String getVariable(Authentication authentication, @PathVariable String variableName) { + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + Double res = dbWorker.getVariable(variableName, userId).getResult(); + return res.toString() + "\n"; + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.DELETE, + consumes = "text/plain", produces = "text/plain") + public void deleteVariable(Authentication authentication, @PathVariable String variableName) { + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + dbWorker.deleteVariable(variableName, userId); + } + + @RequestMapping(path = "/variable", method = RequestMethod.GET, consumes = "text/plain", produces = "text/plain") + public String getAllVariables(Authentication authentication) { + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + ArrayList strings = dbWorker.listVariables(userId).getResult(); + String res = ""; + for (String i : strings) { + res += i; + res += ";"; + } + return res + "\n"; + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.PUT, + consumes = "text/plain", produces = "text/plain") + public void putFunction(Authentication authentication, @PathVariable String functionName, + @RequestParam(value = "arity") Integer arity, + @RequestParam(value = "vars") String vars, + @RequestBody String body) { + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + + try { + dbWorker.setFunction(functionName, body, arity, vars, userId); + } catch (SQLException e) { + LOG.trace("Failed to add function: " + e.getMessage()); + } + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.DELETE, + consumes = "text/plain", produces = "text/plain") + public void deleteFunction(Authentication authentication, + @RequestParam(value = "arity") Integer argc, + @PathVariable String functionName) { + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + dbWorker.deleteFunction(functionName, argc, userId); + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.GET, + consumes = "text/plain", produces = "text/plain") + public String getFunction(Authentication authentication, + @RequestParam(value = "arity") Integer argc, + @PathVariable String functionName) { + Integer userId = dbWorker.getUser(authentication.getName()).getUserId(); + Expression res = dbWorker.getFunction(functionName, argc, userId).getResult(); + + return res.getName() + " " + res.getExpression() + "\n"; + } + + +} diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/DBWorker.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/DBWorker.java new file mode 100644 index 000000000..563b63d26 --- /dev/null +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/DBWorker.java @@ -0,0 +1,368 @@ +package ru.mipt.java2016.homework.g597.kochukov.task4; + + +import java.sql.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; + + +/** + * Created by tna0y on 20/12/16. + */ + +public class DBWorker { + + + public static class DBQuerryResult { + private T result; + private int responseCode; + + public DBQuerryResult(T res, int code) { + result = res; + responseCode = code; + } + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + } + + private static DBWorker instance = new DBWorker(); + + private Connection conn; + + private DBWorker() { + try { + Class.forName("org.sqlite.JDBC"); + conn = DriverManager.getConnection("jdbc:sqlite:calculator_meta.db"); + initializeTable(); + } catch (SQLException | ClassNotFoundException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + System.exit(0); + } + } + + public static DBWorker getInstance() { + return instance; + } + + private void initializeTable() { + + try { + Statement stmt = conn.createStatement(); + String sql = "DROP TABLE IF EXISTS USERS; DROP TABLE IF EXISTS VARIABLES; DROP TABLE IF EXISTS FUNCTIONS;" + + " CREATE TABLE USERS " + + "(ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + + " USERNAME TEXT NOT NULL, " + + " PASSWORD TEXT NOT NULL);" + + "CREATE TABLE VARIABLES " + + "(ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + + " OWNER INTEGER NOT NULL," + + " NAME TEXT NOT NULL," + + " VALUE DOUBLE NOT NULL);" + + "CREATE TABLE FUNCTIONS " + + "(ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + + " OWNER INTEGER NOT NULL," + + " NAME TEXT NOT NULL," + + " EXPRESSION TEXT NOT NULL," + + " ARGC INT NOT NULL," + + " ARGV TEXT NOT NULL);"; + + stmt.executeUpdate(sql); + stmt.close(); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + System.exit(0); + } + } + + public DBQuerryResult getVariable(String name, Integer userid) { + + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery( + "SELECT VALUE FROM VARIABLES WHERE OWNER=" + + userid + " AND NAME='" + name + "' LIMIT 1; "); + Double value; + if (rs.next()) { + value = rs.getDouble("VALUE"); + } else { + return new DBQuerryResult(0.0, 404); + } + rs.close(); + stmt.close(); + return new DBQuerryResult(value, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult(0.0, 500); + } + } + + public DBQuerryResult setVariable(String name, Double value, Integer userid) { + + try { + Statement stmt = conn.createStatement(); + String sql = "INSERT INTO VARIABLES (OWNER,NAME,VALUE) " + + "VALUES (" + userid + ", '" + name + "', " + value + ");"; + stmt.executeUpdate(sql); + stmt.close(); + return new DBQuerryResult(value, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult(0.0, 500); + } + + } + + public DBQuerryResult deleteVariable(String name, Integer userid) { + try { + Statement stmt = conn.createStatement(); + String sql = "DELETE FROM VARIABLES WHERE NAME='" + name + "' AND OWNER='" + userid + "';"; + stmt.executeUpdate(sql); + stmt.close(); + return new DBQuerryResult(0.0, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult(0.0, 500); + } + + } + + public DBQuerryResult> getUserScope(Integer userid) { + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT NAME, VALUE FROM VARIABLES WHERE OWNER=" + userid + ";"); + LinkedHashMap res = new LinkedHashMap<>(); + while (rs.next()) { + try { + res.put(rs.getString("NAME"), rs.getDouble("VALUE")); + } catch (SQLException e) { + System.err.println("Error fetching:" + e.getClass().getName() + ": " + e.getMessage()); + } + } + rs.close(); + stmt.close(); + return new DBQuerryResult<>(res, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult<>(null, 500); + } + + } + + public DBQuerryResult> listVariables(Integer userid) { + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT NAME FROM VARIABLES WHERE OWNER=" + userid + ";"); + ArrayList names = new ArrayList<>(); + while (rs.next()) { + names.add(rs.getString("NAME")); + } + rs.close(); + stmt.close(); + return new DBQuerryResult>(names, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult>(null, 500); + } + + } + + public DBQuerryResult getFunction(String name, Integer argc, Integer userid) { + + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT EXPRESSION, ARGV FROM FUNCTIONS WHERE OWNER=" + + userid + " AND NAME='" + name + "' LIMIT 1;"); + String expr; + LinkedHashMap hm = new LinkedHashMap<>(); + if (rs.next()) { + expr = rs.getString("EXPRESSION"); + for (String key : rs.getString("ARGV").split(";")) { + hm.put(key, 0.0); + } + } else { + return new DBQuerryResult<>(null, 404); + } + rs.close(); + stmt.close(); + Expression res = new Expression(expr, hm); + res.setName(name); + return new DBQuerryResult<>(res, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult<>(null, 500); + } + } + + public DBQuerryResult getFunctionWithArguments(String name, Integer argc, + ArrayList argv, Integer userid) { + DBQuerryResult result = getFunction(name, argc, userid); + int i = 0; + Iterator it = result.result.getScopeVars().keySet().iterator(); + LinkedHashMap tmp = new LinkedHashMap<>(); + while (it.hasNext()) { + String key = it.next(); + tmp.put(key, argv.get(i++)); + } + result.result.setScopeVars(tmp); + + return result; + } + + public DBQuerryResult setFunction(String name, String expression, + Integer argc, String argv, Integer userid) throws SQLException { + + if (Arrays.asList(DefaultCalculator.DEFAULTS).contains(name)) { + throw new SQLException("Must not redefine existing functions."); + } + + try { + + + Statement stmt = conn.createStatement(); + String sql = "INSERT INTO FUNCTIONS (OWNER,NAME,EXPRESSION,ARGC,ARGV) " + + "VALUES (" + userid + ", '" + name + "', '" + expression + "', " + argc + ",'" + argv + "');"; + stmt.executeUpdate(sql); + stmt.close(); + LinkedHashMap hm = new LinkedHashMap<>(); + for (String key : argv.split(";")) { + hm.put(key, 0.0); + } + return new DBQuerryResult<>(new Expression(expression, hm), 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult<>(null, 500); + } + + } + + public DBQuerryResult deleteFunction(String name, Integer argc, Integer userid) { + try { + Statement stmt = conn.createStatement(); + String sql = "DELETE FROM FUNCTIONS WHERE NAME='" + name + "' AND OWNER='" + userid + "';"; + stmt.executeUpdate(sql); + stmt.close(); + return new DBQuerryResult<>(null, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult<>(null, 500); + } + } + + + public DBQuerryResult> listFunctions(Integer userid) { + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT NAME, EXPRESSION, ARGV VALUE FROM FUNCTIONS WHERE OWNER=" + + userid + ";"); + ArrayList res = new ArrayList<>(); + while (rs.next()) { + String name = rs.getString("NAME"); + String expr = rs.getString("EXPRESSION"); + String argv = rs.getString("ARGV"); + + LinkedHashMap hm = new LinkedHashMap<>(); + for (String key : argv.split(";")) { + hm.put(key, 0.0); + } + Expression e = new Expression(expr, hm); + e.setName(name); + res.add(e); + } + rs.close(); + stmt.close(); + return new DBQuerryResult>(res, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult>(null, 500); + } + } + + public DBQuerryResult authenticate(String login, String password) { + // System.out.println("Auth: "+login+" "+password); + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT ID FROM USERS WHERE USERNAME='" + + login + "' AND PASSWORD='" + password + "';"); + Integer uid; + if (rs.next()) { + uid = rs.getInt("ID"); + } else { + return new DBQuerryResult(-1, 401); // 401 - unauthorized + } + rs.close(); + stmt.close(); + return new DBQuerryResult(uid, 200); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult(-1, 500); + } + } + + + public DBQuerryResult register(String login, String password) { + + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * FROM USERS WHERE USERNAME='" + login + "';"); + if (rs.next()) { + return new DBQuerryResult(-1, 401); // 401 - unauthorized + } + rs.close(); + stmt.close(); + + stmt = conn.createStatement(); + String sql = "INSERT INTO USERS (USERNAME,PASSWORD) VALUES ('" + login + "', '" + password + "');"; + stmt.executeUpdate(sql); + stmt.close(); + return authenticate(login, password); + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return new DBQuerryResult(-1, 500); + } + + } + + public BillingUser getUser(String username) { + // System.out.println("Getting user: " + username); + try { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT ID, USERNAME, PASSWORD FROM USERS WHERE USERNAME='" + + username + "';"); + Integer uid; + String u; + String p; + if (rs.next()) { + uid = rs.getInt("ID"); + u = rs.getString("USERNAME"); + p = rs.getString("PASSWORD"); + rs.close(); + stmt.close(); + return new BillingUser(u, p, true, uid); + } else { + return null; + } + + } catch (SQLException e) { + System.err.println(e.getClass().getName() + ": " + e.getMessage()); + return null; + } + } + +} \ No newline at end of file diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/DefaultCalculator.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/DefaultCalculator.java new file mode 100644 index 000000000..8981adef0 --- /dev/null +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/DefaultCalculator.java @@ -0,0 +1,49 @@ +package ru.mipt.java2016.homework.g597.kochukov.task4; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Created by tna0y on 21/12/16. + */ +public class DefaultCalculator { + + static final String[] DEFAULTS = {"sin", "cos", "tg", "sqrt", "pow", "abs", "sign", "log", "log2", "rnd", "max", + "min"}; + + static double calculate(String signature, ArrayList argv) throws ParsingException { + // System.err.println(signature + " " + argv); + if (argv.size() == 0) { + if (signature == "rnd") { + return Math.random(); + } + } else if (argv.size() == 1) { + if (Objects.equals(signature, "sin")) { + return Math.sin(argv.get(0)); + } else if (Objects.equals(signature, "cos")) { + return Math.cos(argv.get(0)); + } else if (Objects.equals(signature, "tg")) { + return Math.tan(argv.get(0)); + } else if (Objects.equals(signature, "sqrt")) { + return Math.sqrt(argv.get(0)); + } else if (Objects.equals(signature, "abs")) { + return Math.abs(argv.get(0)); + } else if (Objects.equals(signature, "log2")) { + return Math.log(argv.get(0)) / Math.log(2.0); + } + } else if (argv.size() == 2) { + if (Objects.equals(signature, "pow")) { + return Math.pow(argv.get(0), argv.get(1)); + } else if (Objects.equals(signature, "log")) { + return Math.log(argv.get(1)) / Math.log(argv.get(0)); + } else if (Objects.equals(signature, "max")) { + return Math.max(argv.get(0), argv.get(1)); + } else if (Objects.equals(signature, "min")) { + return Math.min(argv.get(0), argv.get(1)); + } + } + throw new ParsingException("Incorrect arguments to predefined functions"); + } +} diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/Expression.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/Expression.java new file mode 100644 index 000000000..55bbb6a85 --- /dev/null +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/Expression.java @@ -0,0 +1,42 @@ +package ru.mipt.java2016.homework.g597.kochukov.task4; + +import java.util.LinkedHashMap; + +/** + * Created by tna0y on 20/12/16. + */ +public class Expression { + private String name; + private String expression; + private LinkedHashMap scopeVars; + + public Expression(String s, LinkedHashMap vars) { + expression = s; + scopeVars = vars; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getExpression() { + return expression; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public LinkedHashMap getScopeVars() { + return scopeVars; + } + + public void setScopeVars(LinkedHashMap scopeVars) { + this.scopeVars = scopeVars; + } + +} diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/MegaApplication.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/MegaApplication.java new file mode 100644 index 000000000..938328c65 --- /dev/null +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/MegaApplication.java @@ -0,0 +1,46 @@ +package ru.mipt.java2016.homework.g597.kochukov.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.base.task1.Calculator; + +/** + * 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 = MegaApplication.class) +public class MegaApplication { + + @Bean + public MegaCalculator calculator() { + return new MegaCalculator(0); + } + + @Bean + public EmbeddedServletContainerCustomizer customizer( + @Value("${ru.mipt.java2016.homework.g597.kochukov.task4.httpPort:9001}") int port) { + return container -> container.setPort(port); + } + + @Bean + public DBWorker worker() { + return DBWorker.getInstance(); + } + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(MegaApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.run(args); + } +} diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task1/MegaCalculator.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/MegaCalculator.java similarity index 68% rename from homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task1/MegaCalculator.java rename to homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/MegaCalculator.java index 301d9275c..7d1f9ce91 100644 --- a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task1/MegaCalculator.java +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/MegaCalculator.java @@ -1,31 +1,36 @@ -package ru.mipt.java2016.homework.g597.kochukov.task1; +package ru.mipt.java2016.homework.g597.kochukov.task4; -import ru.mipt.java2016.homework.base.task1.Calculator; import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g597.kochukov.task4.TokenStream.Number; - -import ru.mipt.java2016.homework.g597.kochukov.task1.TokenStream.Operator; -import ru.mipt.java2016.homework.g597.kochukov.task1.TokenStream.Brace; -import ru.mipt.java2016.homework.g597.kochukov.task1.TokenStream.Number; - +import java.sql.SQLException; import java.util.*; -import java.util.regex.Pattern; import java.util.regex.Matcher; - +import java.util.regex.Pattern; /** * Created by Maxim Kochukov on 13/10/16. */ -public class MegaCalculator implements Calculator { +public class MegaCalculator { + private Integer userid; + public MegaCalculator(Integer userid) { + this.userid = userid; + } + + public void setUserid(Integer userid) { + this.userid = userid; + } + public final double calculate(Expression function) throws ParsingException, SQLException { - @Override + String expression = function.getExpression(); + // System.err.println("Calculator started with expression: "+expression); + LinkedHashMap variables = function.getScopeVars(); - public final double calculate(String expression) throws ParsingException { if (expression == null) { throw new ParsingException("Expression cannot be null"); } @@ -39,14 +44,28 @@ public final double calculate(String expression) throws ParsingException { TokenStream ts = new TokenStream(expression); List tokenList = new ArrayList<>(); TokenStream.Token token = ts.getToken(); - while (token != null) { + while (token != null) { tokenList.add(token); token = ts.getToken(); + } + tokenList = resolveRecursive(tokenList, variables, userid); + return calculateTokenizedRPN(convertToRPN(tokenList)); + } + private static List resolveRecursive(List tokenList, + LinkedHashMap variables, + Integer userid) throws SQLException, ParsingException { + for (int i = 0; i < tokenList.size(); i++) { + TokenStream.Token token = tokenList.get(i); + if (token instanceof TokenStream.Variable) { + tokenList.set(i, ((TokenStream.Variable) token).resolve(variables)); + } else if (token instanceof TokenStream.FunctionRef) { + tokenList.set(i, ((TokenStream.FunctionRef) token).resolve(variables, userid)); + } } - return calculateTokenizedRPN(convertToRPN(tokenList)); + return tokenList; } private static ArrayList convertToRPN(List input) { @@ -55,22 +74,24 @@ private static ArrayList convertToRPN(List Deque stack = new LinkedList<>(); for (TokenStream.Token token : input) { - if (token instanceof Operator) { // If operator - Operator operator = (Operator) token; + if (token instanceof TokenStream.Operator) { // If operator + TokenStream.Operator operator = (TokenStream.Operator) token; while (!stack.isEmpty() - && stack.peek() instanceof Operator - && (((Operator) stack.peek()).getType().getPriority() >= operator.getType().getPriority())) { + && stack.peek() instanceof TokenStream.Operator + && (((TokenStream.Operator) stack.peek()).getType().getPriority() + >= operator.getType().getPriority())) { + output.add(stack.pop()); } stack.push(operator); - } else if (token instanceof Brace) { // if brace - Brace brace = (Brace) token; + } else if (token instanceof TokenStream.Brace) { // if brace + TokenStream.Brace brace = (TokenStream.Brace) token; if (!brace.getType()) { // opening stack.push(token); } else { // closing - while (!stack.isEmpty() && !(stack.peek() instanceof Brace)) { // while not '(' + while (!stack.isEmpty() && !(stack.peek() instanceof TokenStream.Brace)) { // while not '(' output.add(stack.pop()); } @@ -134,7 +155,6 @@ private String prepare(String expression) throws ParsingException { expression = '~' + expression.substring(1); } - Pattern unacceptablePairs = Pattern.compile("([+\\-*/]{2})|([(][+\\-*/])|\\(\\)"); int balance = 0; char last = '!'; char cur; @@ -145,7 +165,7 @@ private String prepare(String expression) throws ParsingException { String finish = (i == expression.length() - 1) ? "" : expression.substring(i + 1); expression = expression.substring(0, i) + '~' + finish; cur = '~'; - } else if ("+-;/".indexOf(last) >= 0 && cur == '-') { + } else if ("+-*/".indexOf(last) >= 0 && cur == '-') { String finish = (i == expression.length() - 1) ? "" : expression.substring(i + 1); expression = expression.substring(0, i) + '~' + finish; cur = '~'; @@ -155,21 +175,12 @@ private String prepare(String expression) throws ParsingException { } else if (cur == ')') { balance--; } - - if (unacceptablePairs.matcher(Character.toString(last) + cur).matches()) { - throw new ParsingException("Invalid characters position"); - } - last = cur; } if (balance != 0) { throw new ParsingException("Unbalanced parentheses"); } - Pattern invalidCharCheck = Pattern.compile("[~\\d\\(\\)\\+\\-\\*\\/\\.]+"); - if (!invalidCharCheck.matcher(expression).matches()) { - throw new ParsingException("Invalid characters"); - } return expression; } } diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/SecurityServiceConfiguration.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/SecurityServiceConfiguration.java new file mode 100644 index 000000000..2e561f13f --- /dev/null +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/SecurityServiceConfiguration.java @@ -0,0 +1,56 @@ +package ru.mipt.java2016.homework.g597.kochukov.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 DBWorker dbWorker; + + @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() + .anyRequest().permitAll(); + } + + @Autowired + public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception { + LOG.info("Registering global user details service"); + System.out.println("LOGGING IN"); + auth.userDetailsService(username -> { + try { + BillingUser user = dbWorker.getUser(username); + return new User( + user.getUsername(), + user.getPassword(), + Collections.singletonList(() -> "AUTH") + ); + } catch (EmptyResultDataAccessException e) { + LOG.warn("No such user: " + username); + throw new UsernameNotFoundException(username); + } + }); + } +} diff --git a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task1/TokenStream.java b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/TokenStream.java similarity index 52% rename from homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task1/TokenStream.java rename to homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/TokenStream.java index 45462ee2d..58589336c 100644 --- a/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task1/TokenStream.java +++ b/homework-g597-kochukov/src/main/java/ru/mipt/java2016/homework/g597/kochukov/task4/TokenStream.java @@ -1,7 +1,14 @@ -package ru.mipt.java2016.homework.g597.kochukov.task1; +package ru.mipt.java2016.homework.g597.kochukov.task4; import ru.mipt.java2016.homework.base.task1.ParsingException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.regex.Pattern; + /** * Created by tna0y on 18/10/16. */ @@ -45,6 +52,7 @@ public Token getToken() throws ParsingException { char c = getChar(stringPosition); stringPosition++; + Pattern functionPattern = Pattern.compile("[a-z]+\\(.*"); Token token; if ("()".indexOf(c) >= 0) { token = new Brace(c); @@ -52,6 +60,10 @@ public Token getToken() throws ParsingException { token = new Operator(c); } else if ("0123456789~".indexOf(c) >= 0) { token = new Number(getNumber(c)); + } else if (functionPattern.matcher(expression.substring(stringPosition - 1, expression.length())).matches()) { + token = new FunctionRef(getFunctionRef(c)); + } else if ("abcdefghijklmnopqrstuvwxyz".indexOf(c) >= 0) { + token = new Variable(getVariable(c)); } else { throw new ParsingException("Unexpected symbol " + c); } @@ -102,6 +114,34 @@ private double getNumber(char c) throws ParsingException { return Double.parseDouble(numberString); } + private String getVariable(char c) throws ParsingException { + String ret = Character.toString(c); + char cur = getChar(stringPosition); + while ("abcdefghijklmnopqrstuvwxyz".indexOf(cur) >= 0) { + ret += Character.toString(cur); + stringPosition++; + cur = getChar(stringPosition); + } + return ret; + } + + private String getFunctionRef(char c) throws ParsingException { + String ret = Character.toString(c); + int balance = 0; + int newbalance = 0; + while (!(newbalance == 0 && balance > 0)) { + char cur = getChar(stringPosition); + ret += Character.toString(cur); + stringPosition++; + balance = newbalance; + if (cur == '(') { + newbalance++; + } else if (cur == ')') { + newbalance--; + } + } + return ret; + } abstract static class Token { public abstract String getVisualRepresentation(); @@ -197,6 +237,10 @@ static class Brace extends Token { } } + Brace(final byte[] rep) throws ParsingException { + this((char) rep[0]); + } + public boolean getType() { return type; } @@ -211,5 +255,100 @@ public String getVisualRepresentation() { } } + + } + + static class Variable extends Token { + + private String name; + + Variable(final String name) { + this.name = name; + } + + public Number resolve(LinkedHashMap scope) throws SQLException { + return new Number(scope.get(name)); + } + + @Override + public String getVisualRepresentation() { + return name; + } + } + + static class FunctionRef extends Token { + + private String signature; + private Integer argc; + private ArrayList argv; + + FunctionRef(String fullString) { + System.out.println(fullString); + ArrayList internals = new ArrayList<>(); + int opening = fullString.indexOf("(") + 1; + String filling = ""; + int balance = 0; + for (int pos = opening; pos < fullString.length() - 1; pos++) { + if (fullString.charAt(pos) != ',' || balance > 0) { + filling += Character.toString(fullString.charAt(pos)); + } + + if (fullString.charAt(pos) == '(') { + balance++; + } else if (fullString.charAt(pos) == ')') { + balance--; + } else if (balance == 0 && fullString.charAt(pos) == ',') { + internals.add(filling); + filling = ""; + } + } + if (filling != "") { + internals.add(filling); + } + argc = internals.size(); + argv = internals; + signature = fullString.substring(0, opening - 1); + } + + public Number resolve(LinkedHashMap vars, Integer userid) + throws ParsingException, SQLException { + + MegaCalculator calculator = new MegaCalculator(userid); + ArrayList arguments = new ArrayList<>(); + for (String arg : argv) { + arguments.add(calculator.calculate(new Expression(arg, vars))); + } + if (Arrays.asList(DefaultCalculator.DEFAULTS).contains(signature)) { + return new Number(DefaultCalculator.calculate(signature, arguments)); + } + + + DBWorker db = DBWorker.getInstance(); + DBWorker.DBQuerryResult functionRes; + + functionRes = db.getFunctionWithArguments(signature, argc, arguments, userid); + + if (functionRes.getResponseCode() != 200) { + throw new ParsingException("Unknown function called!"); + } + Expression function = functionRes.getResult(); + + Iterator it = vars.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + if (!function.getScopeVars().containsKey(key)) { + function.getScopeVars().put(key, vars.get(key)); + } + } + + return new Number(calculator.calculate(function)); + } + + @Override + public String getVisualRepresentation() { + return signature; + } } + + } \ No newline at end of file diff --git a/homework-g597-kochukov/src/test/java/ru/mipt/java2016/homework/g597/kochukov/task1/MegaCalculatorTest.java b/homework-g597-kochukov/src/test/java/ru/mipt/java2016/homework/g597/kochukov/task1/MegaCalculatorTest.java index 955d5e073..a97d405e6 100644 --- a/homework-g597-kochukov/src/test/java/ru/mipt/java2016/homework/g597/kochukov/task1/MegaCalculatorTest.java +++ b/homework-g597-kochukov/src/test/java/ru/mipt/java2016/homework/g597/kochukov/task1/MegaCalculatorTest.java @@ -1,4 +1,4 @@ -package ru.mipt.java2016.homework.g597.kochukov.task1; +/*package ru.mipt.java2016.homework.g597.kochukov.task1; import ru.mipt.java2016.homework.base.task1.Calculator; import ru.mipt.java2016.homework.g597.kochukov.task1.MegaCalculator; @@ -9,4 +9,4 @@ protected Calculator calc() { return new MegaCalculator(); } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/homework-g597-kochukov/src/test/java/ru/mipt/java2016/homework/g597/kochukov/task4/AbstractServerTest.java b/homework-g597-kochukov/src/test/java/ru/mipt/java2016/homework/g597/kochukov/task4/AbstractServerTest.java new file mode 100644 index 000000000..9ecffacb2 --- /dev/null +++ b/homework-g597-kochukov/src/test/java/ru/mipt/java2016/homework/g597/kochukov/task4/AbstractServerTest.java @@ -0,0 +1,128 @@ +package ru.mipt.java2016.homework.g597.kochukov.task4; + +import org.junit.Assert; +import org.junit.Test; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.sql.SQLException; +import java.util.LinkedHashMap; + +/** + * Created by tna0y on 21/12/16. + */ + +public class AbstractServerTest { + + + private MegaCalculator calc; + + + protected void test(Expression expression, double expected) throws ParsingException, SQLException { + String errorMessage = String.format("Bad result for expression '%s', %.2f expected", expression, expected); + calc = new MegaCalculator(0); + double actual = calc.calculate(expression); + Assert.assertEquals(errorMessage, expected, actual, 1e-6); + } + + @Test + public void testBasicOps() throws ParsingException, SQLException { + Expression testExpression = new Expression("2 * 2", new LinkedHashMap()); + test(testExpression, 4.0); + testExpression = new Expression("2 * (2 + 2)", new LinkedHashMap()); + test(testExpression, 8.0); + testExpression = new Expression("2 * 2 + 2", new LinkedHashMap()); + test(testExpression, 6.0); + testExpression = new Expression("-(-1)", new LinkedHashMap()); + test(testExpression, 1.0); + testExpression = new Expression("- 1 * 2", new LinkedHashMap()); + test(testExpression, -2.0); + testExpression = new Expression("2 + - 2", new LinkedHashMap()); + test(testExpression, 0.0); + } + @Test + public void testGlobalVariables() throws ParsingException, SQLException { + + LinkedHashMap hm = new LinkedHashMap<>(); + hm.put("x",1.0); + Expression testExpression = new Expression("x", hm); + test(testExpression, 1.0); + + hm = new LinkedHashMap<>(); + hm.put("x",1.0); + hm.put("y",1.0); + + testExpression = new Expression("x + y", hm); + test(testExpression, 2.0); + + hm = new LinkedHashMap<>(); + hm.put("x",1.0); + hm.put("y",1.0); + + testExpression = new Expression("x + y + 7", hm); + test(testExpression, 9.0); + + hm = new LinkedHashMap<>(); + hm.put("x",1.0); + hm.put("y",2.0); + hm.put("z",3.0); + testExpression = new Expression("y * - y + 2 * - (z)", hm); + test(testExpression, -10.0); + + } + + @Test + public void testBasicFunction() throws ParsingException, SQLException { + + DBWorker.getInstance().setFunction("f", "x+1", 1, "x", 0); + LinkedHashMap hm = new LinkedHashMap<>(); + + Expression testExpression = new Expression("f(1)", hm); + test(testExpression, 2.0); + } + + @Test + public void testFunctionScopeOverwrite() throws ParsingException, SQLException { + DBWorker.getInstance().setFunction("f", "x+1", 1, "x", 0); + LinkedHashMap hm = new LinkedHashMap<>(); + hm.put("x",2.0); + Expression testExpression = new Expression("f(1)", hm); + test(testExpression, 2.0); + } + + @Test + public void testFunctionComposition() throws ParsingException, SQLException { + + DBWorker.getInstance().setFunction("f", "x+1", 1, "x", 0); + LinkedHashMap hm = new LinkedHashMap<>(); + Expression testExpression = new Expression("f(f(1))", hm); + test(testExpression, 3.0); + } + + @Test + public void testFunctionsWithVariables() throws ParsingException, SQLException { + + DBWorker.getInstance().setFunction("f", "x+1", 1, "x", 0); + DBWorker.getInstance().setFunction("g", "x+y", 2, "x;y", 0); + LinkedHashMap hm = new LinkedHashMap<>(); + hm.put("x",1.0); + Expression testExpression = new Expression("f(f(x))", hm); + test(testExpression, 3.0); + + hm = new LinkedHashMap<>(); + hm.put("first",1.0); + hm.put("second",2.0); + testExpression = new Expression("f(f(first))*3+g(first,second)", hm); + test(testExpression, 12.0); + + hm = new LinkedHashMap<>(); + hm.put("x",1.0); + hm.put("y",2.0); + testExpression = new Expression("g(g(x,f(y)),10)", hm); + test(testExpression, 14.0); + } + + /*@Test + public void testDBInitialization() throws SQLException { + testDB(); + }*/ +} \ No newline at end of file