diff --git a/homework-g596-stepanova/pom.xml b/homework-g596-stepanova/pom.xml
index d60c06fd1..78c4ce843 100644
--- a/homework-g596-stepanova/pom.xml
+++ b/homework-g596-stepanova/pom.xml
@@ -9,6 +9,10 @@
4.0.0
+
+ 1.4.2.RELEASE
+
+
homework-g596-stepanova
@@ -23,6 +27,36 @@
1.0.0
test
+
+
+ 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-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingDatabaseConfiguration.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingDatabaseConfiguration.java
new file mode 100644
index 000000000..1bcd7243f
--- /dev/null
+++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingDatabaseConfiguration.java
@@ -0,0 +1,26 @@
+package ru.mipt.java2016.homework.g596.stepanova.task4;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class BillingDatabaseConfiguration {
+ @Bean
+ public DataSource billingDataSource(
+ @Value("${ru.mipt.java2016.homework.g000.lavrentyev.task4.jdbcUrl}") String jdbcUrl,
+ @Value("${ru.mipt.java2016.homework.g000.lavrentyev.task4.username:}") String username,
+ @Value("${ru.mipt.java2016.homework.g000.lavrentyev.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);
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingUser.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingUser.java
new file mode 100644
index 000000000..2b5c2db10
--- /dev/null
+++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingUser.java
@@ -0,0 +1,66 @@
+package ru.mipt.java2016.homework.g596.stepanova.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;
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/CalculatorController.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/CalculatorController.java
new file mode 100644
index 000000000..4b37d4b8b
--- /dev/null
+++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/CalculatorController.java
@@ -0,0 +1,129 @@
+package ru.mipt.java2016.homework.g596.stepanova.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;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.TreeSet;
+
+@RestController
+public class CalculatorController {
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class);
+ @Autowired private Calculator calculator;
+ @Autowired private Database database;
+ @Autowired private Database clients;
+
+ private static final Set SYMBOLS = new TreeSet<>(
+ Arrays.asList('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'));
+
+ @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 "" + "MyApp" + "Hello, " + name
+ + "!
" + "";
+ }
+
+ @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain")
+ public String eval(Authentication authentication, @RequestBody String expression)
+ throws ParsingException {
+ String author = authentication.getName();
+ if (clients.checkUser(author)) {
+ LOG.debug("Evaluation request: [" + expression + "]");
+ StringBuilder expressionNew = new StringBuilder();
+ StringBuilder word = new StringBuilder();
+ boolean letter = false;
+ for (int i = 0; i < expression.length(); ++i) {
+ if (SYMBOLS.contains(expression.charAt(i))) {
+ letter = true;
+ word.append(expression.charAt(i));
+ } else {
+ if (letter) {
+ letter = false;
+ try {
+ expressionNew.append(database.loadMeaning(word.toString()));
+ } catch (Exception e) {
+ throw new ParsingException(e.getMessage(), e.getCause());
+ }
+ expressionNew.append(expression.charAt(i));
+ word = new StringBuilder();
+ } else {
+ expressionNew.append(expression.charAt(i));
+ }
+ }
+ }
+ System.out.println(expressionNew.toString());
+ double result = calculator.calculate(expressionNew.toString());
+ LOG.trace("Result: " + result);
+ return Double.toString(result) + "\n";
+ } else {
+ return "Has no permission!\n";
+ }
+ }
+
+ @RequestMapping(path = "/add", method = RequestMethod.POST,
+ consumes = "text/plain", produces = "text/plain")
+ public String add(@RequestBody String argument) throws ParsingException {
+ LOG.debug("Add request: [" + argument + "]");
+ String delim = "[;]";
+ String[] tokens = argument.split(delim);
+ database.register(tokens);
+ return "OK\n";
+ }
+
+ @RequestMapping(path = "/delete", method = RequestMethod.DELETE,
+ consumes = "text/plain", produces = "text/plain")
+ public String delete(@RequestBody String argument) throws IOException {
+ LOG.debug("Delete argument: [" + argument + "]");
+ database.deleteArgument(argument);
+ return "OK\n";
+ }
+
+ @RequestMapping(path = "/check", method = RequestMethod.GET,
+ consumes = "text/plain", produces = "text/plain")
+ public String check(@RequestBody String argument) throws IOException {
+ LOG.debug("Check for existence of [" + argument + "] element");
+ Double answer = database.loadMeaning(argument);
+ return answer.toString();
+ }
+
+ @RequestMapping(path = "/addUser/{variableName}", method = RequestMethod.POST,
+ consumes = "text/plain", produces = "text/plain")
+ public String addUser(Authentication authentication, @RequestBody String data) {
+ LOG.debug("Add new user [" + data + "]");
+ String author = authentication.getName();
+ if (author.equals("admin")) {
+ clients.addUser(data);
+ } else {
+ return "Has no permission to add user!\n";
+ }
+ return "OK\n";
+ }
+
+ @RequestMapping(path = "/deleteUser/{variableName}", method = RequestMethod.DELETE,
+ consumes = "text/plain", produces = "text/plain")
+ public String deleteUser(Authentication authentication, @RequestBody String data) {
+ LOG.debug("Delete user [" + data + "]");
+ String author = authentication.getName();
+ if (author.equals("admin")) {
+ clients.deleteUser(data);
+ } else {
+ return "Has no permission to delete user!\n";
+ }
+ return "OK\n";
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/Database.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/Database.java
new file mode 100644
index 000000000..feda0668c
--- /dev/null
+++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/Database.java
@@ -0,0 +1,118 @@
+package ru.mipt.java2016.homework.g596.stepanova.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 ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Repository
+public class Database {
+ private static final Logger LOG = LoggerFactory.getLogger(Database.class);
+
+ @Autowired private DataSource dataSource;
+
+ private JdbcTemplate jdbcTemplate;
+
+ @PostConstruct
+ public void postConstruct() {
+ jdbcTemplate = new JdbcTemplate(dataSource, false);
+ initSchema();
+ }
+
+ public void initSchema() {
+ LOG.trace("Initializing schema");
+ jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS billing");
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.users "
+ + "(username VARCHAR PRIMARY KEY, password VARCHAR, enabled BOOLEAN)");
+ jdbcTemplate.update("DELETE FROM billing.users WHERE username = 'admin'");
+ jdbcTemplate.update("INSERT INTO billing.users VALUES ('admin', 'admin', TRUE)");
+
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.arguments "
+ + "(argument VARCHAR PRIMARY KEY, meaning VARCHAR)");
+ }
+
+
+ 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 void register(String[] tokens) throws ParsingException {
+ jdbcTemplate.update("DELETE FROM billing.arguments WHERE argument = '" + tokens[0] + "'");
+ jdbcTemplate
+ .update("INSERT INTO billing.arguments VALUES('" + tokens[0] + "', '" + tokens[1]
+ + "')");
+ }
+
+ public double loadMeaning(String argument) throws IOException {
+ return jdbcTemplate.queryForObject(
+ "SELECT argument, meaning FROM billing.arguments WHERE argument = ?",
+ new Object[] {argument}, new RowMapper() {
+ @Override
+ public Double mapRow(ResultSet resultSet, int i) throws SQLException {
+ return resultSet.getDouble("meaning");
+ }
+ });
+ }
+
+ public void deleteArgument(String argument) throws IOException {
+ jdbcTemplate.update("DELETE FROM billing.arguments WHERE argument = '" + argument + "'");
+ }
+
+ public double check(String argument) throws IOException {
+ int i = jdbcTemplate
+ .queryForObject("SELECT 1 COUNT * FROM billing.arguments WHERE argument = ?",
+ new Object[] {argument}, new RowMapper() {
+ @Override
+ public Integer mapRow(ResultSet resultSet, int i) throws SQLException {
+ return resultSet.getInt(1);
+ }
+ });
+ if (i > 0) {
+ return jdbcTemplate.queryForObject(
+ "SELECT argument, meaning FROM billing.arguments WHERE argument = ?",
+ new Object[] {argument}, new RowMapper() {
+ @Override
+ public Double mapRow(ResultSet resultSet, int i) throws SQLException {
+ return resultSet.getDouble("meaning");
+ }
+ });
+ }
+ throw new IOException("Smth wen't wrong");
+ }
+
+ public void addUser(String data) {
+ String delim = "[;]";
+ String[] tokens = data.split(delim);
+ jdbcTemplate.update("DELETE FROM billing.users WHERE username = '" + tokens[0] + "'");
+ jdbcTemplate.update("INSERT INTO billing.users VALUES('" + tokens[0] + "', '" + tokens[1]
+ + "', TRUE)");
+ }
+
+ public void deleteUser(String data) {
+ jdbcTemplate.update("DELETE FROM billing.users WHERE username = '" + data + "'");
+ }
+
+ public boolean checkUser(String data) {
+ BillingUser user = loadUser(data);
+ return user.getUsername().length() > 0;
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/MyApplication.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/MyApplication.java
new file mode 100644
index 000000000..a4658fd94
--- /dev/null
+++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/MyApplication.java
@@ -0,0 +1,34 @@
+package ru.mipt.java2016.homework.g596.stepanova.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;
+import ru.mipt.java2016.homework.g596.stepanova.task1.MyCalc;
+
+@EnableAutoConfiguration
+@Configuration
+@ComponentScan(basePackageClasses = MyApplication.class)
+public class MyApplication {
+ @Bean
+ public Calculator calculator() {
+ return new MyCalc();
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer customizer(
+ @Value("${ru.mipt.java2016.homework.g596.stepanova.task4.httpPort:9001}") int port) {
+ return container -> container.setPort(port);
+ }
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(MyApplication.class);
+ application.setBannerMode(Banner.Mode.OFF);
+ application.run(args);
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/SecurityServiceConfiguration.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/SecurityServiceConfiguration.java
new file mode 100644
index 000000000..a540ec11a
--- /dev/null
+++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/SecurityServiceConfiguration.java
@@ -0,0 +1,46 @@
+package ru.mipt.java2016.homework.g596.stepanova.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 Database clients;
+
+ @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 = clients.loadUser(username);
+ return new User(user.getUsername(), user.getPassword(),
+ Collections.singletonList(() -> "AUTH"));
+ } catch (EmptyResultDataAccessException e) {
+ LOG.warn("No such user: " + username);
+ throw new UsernameNotFoundException(username);
+ }
+ });
+ }
+}
\ No newline at end of file