diff --git a/homework-g597-spirin/pom.xml b/homework-g597-spirin/pom.xml
index 8e3251357..a8217888b 100644
--- a/homework-g597-spirin/pom.xml
+++ b/homework-g597-spirin/pom.xml
@@ -11,6 +11,10 @@
homework-g597-spirin
+
+ 1.4.2.RELEASE
+
+
ru.mipt.java2016
@@ -30,6 +34,40 @@
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
+
+
+
+ ru.mipt.java2016
+ homework-tests
+ 1.0.0
+ test
+
+
\ No newline at end of file
diff --git a/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/Application.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/Application.java
new file mode 100644
index 000000000..75dc2ed38
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/Application.java
@@ -0,0 +1,39 @@
+package ru.mipt.java2016.homework.g597.spirin.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;
+
+/**
+ * Created by whoami on 12/13/16.
+ */
+
+@EnableAutoConfiguration
+@Configuration
+@ComponentScan(basePackageClasses = Application.class)
+public class Application {
+
+ @Bean
+ public Calculator calculator() {
+ return new Evaluator();
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer customizer(
+ @Value("${ru.mipt.java2016.homework.g597.spirin.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-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingDao.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingDao.java
new file mode 100644
index 000000000..c0dcac9f8
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingDao.java
@@ -0,0 +1,163 @@
+package ru.mipt.java2016.homework.g597.spirin.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.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * Created by whoami on 12/13/16.
+ */
+
+@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.info("Initializing schema");
+ jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS billing");
+
+ // User table
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.users " +
+ "(username VARCHAR PRIMARY KEY, password VARCHAR)");
+
+ // Update initial (username, password)
+ jdbcTemplate.execute("DELETE FROM billing.users WHERE username = 'username'");
+ jdbcTemplate.execute("INSERT INTO billing.users VALUES ('username', 'password')");
+
+ // Variable table
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.variables " +
+ "(username VARCHAR, variable VARCHAR, value FLOAT)");
+
+ // Function table
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions " +
+ "(username VARCHAR, function VARCHAR, arity INTEGER, body VARCHAR)");
+ }
+
+ // Load user from user table
+ public BillingUser loadUser(String username) throws EmptyResultDataAccessException {
+ LOG.info("Querying for user " + username);
+ return jdbcTemplate.queryForObject(
+ "SELECT username, password 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")
+ );
+ }
+ }
+ );
+ }
+
+ // Put user into user table
+ public void putUser(String username, String password) {
+ LOG.info("Putting user (username, password) into table");
+ jdbcTemplate.execute("INSERT INTO billing.users VALUES ('"
+ + username + "', '" + password + "')");
+ }
+
+ // Get value of variable of particular user
+ public Double getVariable(String username, String variable) throws EmptyResultDataAccessException {
+ LOG.info("Querying for variable " + variable + " of user " + username);
+ return jdbcTemplate.queryForObject(
+ "SELECT value FROM billing.variables WHERE username = '" + username +
+ "' AND variable = '" + variable + "'",
+ new Double[]{},
+ new RowMapper() {
+ @Override
+ public Double mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new Double(
+ rs.getString("value").toString()
+ );
+ }
+ }
+ );
+ }
+
+ // Put variable of particular user
+ public void putVariable(String username, String variable, Double value) {
+ LOG.info("Putting variable " + variable + " of user " + username);
+ deleteVariable(username, variable);
+ jdbcTemplate.execute("INSERT INTO billing.variables VALUES ('"
+ + username + "', '" + variable + "', " + value.toString() + ")");
+ }
+
+ // Delete variable of particular user
+ public void deleteVariable(String username, String variable) {
+ LOG.info("Deleting variable " + variable + " of user " + username);
+ jdbcTemplate.execute("DELETE FROM billing.variables WHERE username = '"
+ + username + "' AND variable = '" + variable + "'");
+ }
+
+ // Get all variables from the service of particular user
+ public String[] getAllVariables(String username) {
+ LOG.info("Querying for all variables of user " + username);
+
+ List queryResult = jdbcTemplate.queryForList(
+ "SELECT variable FROM billing.variables WHERE username = '" +
+ username + "'");
+
+ String[] variables = new String[queryResult.size()];
+
+ for (int i = 0; i < queryResult.size(); ++i) {
+ variables[i] = queryResult.get(i).toString();
+ }
+
+ return variables;
+ }
+
+ // Get function of particular user
+ public String getFunction(String username, String function) {
+ LOG.info("Querying for function " + function + " of user " + username);
+ return jdbcTemplate.queryForObject(
+ "SELECT body FROM billing.functions WHERE username = '" + username +
+ "' AND function = '" + function + "'",
+ new String[]{},
+ new RowMapper() {
+ @Override
+ public String mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return rs.getString("body");
+ }
+ }
+ );
+ }
+
+ // Put function of particular user
+ public void putFunction(String username, String function, Integer arity, String body) {
+ LOG.info("Putting function " + function + " of user " + username);
+ deleteFunction(username, function);
+ jdbcTemplate.execute("INSERT INTO billing.functions VALUES ('"
+ + username + "', '" + function + "', " + arity.toString() + ", '"
+ + body + "')");
+ }
+
+ // Delete function of particular user
+ public void deleteFunction(String username, String function) {
+ LOG.info("Deleting function " + function + " of user " + username);
+ jdbcTemplate.execute("DELETE FROM billing.functions WHERE username = '" +
+ username + "' AND function = '" + function + "'");
+ }
+}
diff --git a/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingDatabaseConfiguration.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingDatabaseConfiguration.java
new file mode 100644
index 000000000..8528d1052
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingDatabaseConfiguration.java
@@ -0,0 +1,30 @@
+package ru.mipt.java2016.homework.g597.spirin.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;
+
+/**
+ * Created by whoami on 12/13/16.
+ */
+
+@Configuration
+public class BillingDatabaseConfiguration {
+ @Bean
+ public DataSource billingDataSource(
+ @Value("jdbc:h2:mem:testdb") String jdbcUrl,
+ @Value("${ru.mipt.java2016.homework.g597.spirin.task4.username:}") String username,
+ @Value("${ru.mipt.java2016.homework.g597.spirin.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-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingUser.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingUser.java
new file mode 100644
index 000000000..4de2d352a
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/BillingUser.java
@@ -0,0 +1,63 @@
+package ru.mipt.java2016.homework.g597.spirin.task4;
+
+/**
+ * Created by whoami on 12/13/16.
+ */
+
+public class BillingUser {
+ private final String username;
+ private final String password;
+
+ public BillingUser(String username, String password) {
+ 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;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String toString() {
+ return "BillingUser{" +
+ "username='" + username + '\'' +
+ ", password='" + password + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ BillingUser that = (BillingUser) o;
+
+ 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();
+ return result;
+ }
+}
+
diff --git a/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/CalculatorController.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/CalculatorController.java
new file mode 100644
index 000000000..db55ba517
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/CalculatorController.java
@@ -0,0 +1,115 @@
+package ru.mipt.java2016.homework.g597.spirin.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.Calculator;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+/**
+ * Created by whoami on 12/13/16.
+ */
+
+@RestController
+public class CalculatorController {
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class);
+ @Autowired
+ private Calculator calculator;
+ @Autowired
+ private BillingDao billingDao;
+
+ @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 "" +
+ "mountain-viewer app" +
+ "Hello, " + name + "!
" +
+ "";
+ }
+
+ @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain")
+ public String eval(Authentication authentication, @RequestBody String expression) throws ParsingException {
+ LOG.info("Evaluation request: [" + expression + "]");
+
+ String username = authentication.getName();
+ double result = calculator.calculate(expression);
+
+ LOG.info("Result: " + result);
+
+ return Double.toString(result) + "\n";
+ }
+
+ @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.info(username);
+ LOG.info(password);
+
+ billingDao.putUser(username, password);
+ }
+
+ @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.PUT,
+ consumes = "text/plain", produces = "text/plain")
+ public void putVariable(Authentication authentication,
+ @PathVariable String variableName, @RequestParam(value = "value") Double value) {
+ String username = authentication.getName();
+ billingDao.putVariable(username, variableName, value);
+ }
+
+ @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.GET,
+ consumes = "text/plain", produces = "text/plain")
+ public String getVariable(Authentication authentication, @PathVariable String variableName) {
+ String username = authentication.getName();
+ Double res = billingDao.getVariable(username, variableName);
+ 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) {
+ String username = authentication.getName();
+ billingDao.deleteVariable(username, variableName);
+ }
+
+ @RequestMapping(path = "/variable", method = RequestMethod.GET, consumes = "text/plain", produces = "text/plain")
+ public String getAllVariables(Authentication authentication) {
+ String username = authentication.getName();
+ String[] res = billingDao.getAllVariables(username);
+ return res.toString() + "\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, @RequestBody String body) {
+ String username = authentication.getName();
+ LOG.trace(username);
+ billingDao.putFunction(username, functionName, arity, body);
+ }
+
+ @RequestMapping(path = "/function/{functionName}", method = RequestMethod.DELETE,
+ consumes = "text/plain", produces = "text/plain")
+ public void deleteFunction(Authentication authentication, @PathVariable String functionName) {
+ String username = authentication.getName();
+ billingDao.deleteFunction(username, functionName);
+ }
+
+ @RequestMapping(path = "/function/{functionName}", method = RequestMethod.GET,
+ consumes = "text/plain", produces = "text/plain")
+ public String getFunction(Authentication authentication, @PathVariable String functionName) {
+ String username = authentication.getName();
+ String res = billingDao.getFunction(username, functionName);
+ return res + "\n";
+ }
+}
\ No newline at end of file
diff --git a/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/Evaluator.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/Evaluator.java
new file mode 100644
index 000000000..b3f328ed6
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/Evaluator.java
@@ -0,0 +1,43 @@
+package ru.mipt.java2016.homework.g597.spirin.task4;
+
+import ru.mipt.java2016.homework.base.task1.Calculator;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+/**
+ * Created by whoami on 12/13/16.
+ */
+
+public class Evaluator implements Calculator {
+ @Override
+ public double calculate(String expression) throws ParsingException {
+ if (expression == null) {
+ throw new ParsingException("Null can't be interpreted as an expression");
+ }
+
+ if (!checkIfBalanceCorrect(expression)) {
+ throw new ParsingException("Incorrect bracket sequence");
+ }
+
+ return new EvaluatorHelper(expression).evaluate();
+ }
+
+ // Check correctness of bracket sequence
+ private boolean checkIfBalanceCorrect(String expression) {
+ int balance = 0;
+
+ for (Character ch : expression.toCharArray()) {
+ if (balance < 0) {
+ return false;
+ }
+ if (ch == '(') {
+ balance++;
+ } else if (ch == ')') {
+ balance--;
+ }
+
+ }
+
+ return balance == 0;
+ }
+}
+
diff --git a/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/EvaluatorHelper.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/EvaluatorHelper.java
new file mode 100644
index 000000000..97a9317f3
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/EvaluatorHelper.java
@@ -0,0 +1,197 @@
+package ru.mipt.java2016.homework.g597.spirin.task4;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import java.util.Stack;
+
+/**
+ * Created by whoami on 12/13/16.
+ */
+
+// Algorithm based on grammar expressions
+
+// Grammar:
+// expression = term | expression `+` term | expression `-` term | expression, expression
+// term = factor | term `*` factor | term `/` factor
+// factor = `+` factor | `-` factor | `(` expression `)`
+// | number | functionName factor
+
+class EvaluatorHelper {
+
+ // Class data members
+ private int pos;
+ private char ch;
+ private final String expression;
+
+ private final Stack functions = new Stack<>();
+
+ // Constructor
+ EvaluatorHelper(String expression) {
+ this.expression = expression;
+ pos = -1;
+ }
+
+ // Access to next character
+ private void getNextChar() throws ParsingException {
+ if (++pos < expression.length()) {
+ ch = expression.charAt(pos);
+ } else {
+ // Just to stop execution
+ ch = '&';
+ }
+ }
+
+ // Wait for expected character
+ private boolean tryCaptureChar(char charToBeCaptured) throws ParsingException {
+ // Skip all whitespace characters
+ while (Character.isWhitespace(ch)) {
+ getNextChar();
+ }
+
+ if (ch == charToBeCaptured) {
+ getNextChar();
+ return true;
+ }
+ return false;
+ }
+
+ // Main logic function
+ double evaluate() throws ParsingException {
+ getNextChar();
+
+ double result = processExpression();
+ if (pos < expression.length()) {
+ throw new ParsingException("Unexpected appearance of: " + ch);
+ }
+
+ return result;
+ }
+
+ // Calculate the value of call function(a, b)
+ private double performBinaryFunction(String function, double a, double b) throws ParsingException {
+ if (function.equals("max")) {
+ return Math.max(a, b);
+ } else if (function.equals("min")) {
+ return Math.min(a, b);
+ } else if (function.equals("pow")) {
+ return Math.pow(a, b);
+ } else if (function.equals("log")) {
+ return Math.log(a) / Math.log(b);
+ } else {
+ throw new ParsingException("Unknown function call: " + function);
+ }
+ }
+
+ // Evaluation the whole expression
+ private double processExpression() throws ParsingException {
+ double result = processTerm();
+
+ while (true) {
+ if (tryCaptureChar('+')) {
+ result += processTerm();
+ } else if (tryCaptureChar('-')) {
+ result -= processTerm();
+ } else if (tryCaptureChar(',')) {
+ double pendingArgument = processExpression();
+ result = performBinaryFunction(functions.pop(), result, pendingArgument);
+ } else {
+ break;
+ }
+ }
+ return result;
+ }
+
+ // Evaluation subterm of the expression
+ private double processTerm() throws ParsingException {
+ double result = processFactor();
+
+ while (true) {
+ if (tryCaptureChar('*')) {
+ result *= processFactor();
+ } else if (tryCaptureChar('/')) {
+ result /= processFactor();
+ } else {
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ // Check if function is binary
+ private boolean isBinaryFunction(String func) {
+ return func.equals("pow") || func.equals("log") || func.equals("min") || func.equals("max");
+ }
+
+ // Evaluation factors of the expression
+ private double processFactor() throws ParsingException {
+ if (tryCaptureChar('-')) {
+ return -processFactor();
+ }
+
+ double result = 0;
+ int startPos = this.pos;
+
+ if (tryCaptureChar('(')) {
+ result = processExpression();
+ tryCaptureChar(')');
+ } else if (Character.isDigit(ch) || ch == '.') {
+
+ while (Character.isDigit(ch) || ch == '.') {
+ getNextChar();
+ }
+
+ // Number of points in a string
+ // Without a hack :(
+ int countPoints = 0;
+ for (int i = startPos; i < this.pos; ++i) {
+ countPoints += expression.charAt(i) == '.' ? 1 : 0;
+ }
+
+ if (countPoints > 1) {
+ throw new ParsingException("Number with many points found.");
+ }
+
+ result = Double.parseDouble(expression.substring(startPos, this.pos));
+ } else if (Character.isAlphabetic(ch)) {
+ while (Character.isAlphabetic(ch)) {
+ getNextChar();
+ }
+
+ String func = expression.substring(startPos, this.pos);
+
+ if (isBinaryFunction(func)) {
+ functions.push(func);
+ }
+
+ if (func.equals("rnd")) {
+ result = Math.random();
+ getNextChar();
+ getNextChar();
+ } else {
+ result = processFactor();
+ if (func.equals("sqrt")) {
+ result = Math.sqrt(result);
+ } else if (func.equals("sin")) {
+ result = Math.sin(Math.toRadians(result));
+ } else if (func.equals("cos")) {
+ result = Math.cos(Math.toRadians(result));
+ } else if (func.equals("tg")) {
+ result = Math.tan(Math.toRadians(result));
+ } else if (func.equals("abs")) {
+ result = Math.abs(result);
+ } else if (func.equals("sign")) {
+ result = Math.signum(result);
+ } else if (func.equals("log2")) {
+ result = Math.log(result) / Math.log(2);
+ } else if (!isBinaryFunction(func)) {
+ throw new ParsingException("Unknown function call: " + func);
+ }
+ }
+ } else {
+ throw new ParsingException("Unexpected appearance of: " + ch);
+ }
+
+ return result;
+ }
+
+}
diff --git a/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/README.md b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/README.md
new file mode 100644
index 000000000..eaab7410c
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/README.md
@@ -0,0 +1,29 @@
+# Java REST Calculator (in progress)
+
+# Usage
+
+First run application, then follow commands below:
+
+To authorize run in shell:
+
+`curl http://localhost:9001/signup?args="mountain-viewer,1234567" -X POST -H "Content-Type: text/plain"`
+
+To put variable/function run in shell:
+
+`curl http://localhost:9001/variable/x?value="19" -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)"`
+
+`curl http://localhost:9001/function/sum?arity="2" -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data "x+y"`
+
+
+To get value of a variable/body of function run in shell:
+
+`curl http://localhost:9001/variable/x -X GET -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)"`
+
+`curl http://localhost:9001/function/sum -X GET -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)"`
+
+
+To evaluate expressions run in shell:
+
+`curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data "pow(sin(3 + pow(2, 4)), tg(32)) + 44*3+2"`
+
+`curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "mountain-viewer:1234567" | base64)" --data "sum(sum(3, 4), 4)"`
diff --git a/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/SecurityServiceConfiguration.java b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/SecurityServiceConfiguration.java
new file mode 100644
index 000000000..49a476f27
--- /dev/null
+++ b/homework-g597-spirin/src/main/java/ru/mipt/java2016/homework/g597/spirin/task4/SecurityServiceConfiguration.java
@@ -0,0 +1,58 @@
+package ru.mipt.java2016.homework.g597.spirin.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()
+ .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);
+ }
+ });
+ }
+}
+