diff --git a/homework-g596-kupriyanov/pom.xml b/homework-g596-kupriyanov/pom.xml index 32733fc74..d862e5fed 100644 --- a/homework-g596-kupriyanov/pom.xml +++ b/homework-g596-kupriyanov/pom.xml @@ -11,6 +11,10 @@ homework-g596-kupriyanov + + 1.4.2.RELEASE + + ru.mipt.java2016 @@ -24,6 +28,49 @@ 1.0.0 test + + org.apache.maven.plugins + maven-jar-plugin + 2.3.2 + + + ru.mipt.java2016 + homework-base + 1.0.0 + + + + net.sourceforge.jeval + jeval + 0.9.4 + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-jdbc + ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-security + ${spring.boot.version} + + + com.zaxxer + HikariCP + 2.5.1 + + + com.h2database + h2 + 1.4.193 + + \ No newline at end of file diff --git a/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingDao.java b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingDao.java new file mode 100644 index 000000000..3633b0aac --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingDao.java @@ -0,0 +1,202 @@ +package ru.mipt.java2016.homework.g596.kupriyanov.task4; + +/** + * Created by Artem Kupriyanov on 17/12/2016. + */ + +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.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + +@Repository +public class BillingDao { + private static final Logger LOG = LoggerFactory.getLogger(BillingDao.class); + + @Autowired + private DataSource dataSource; + + private JdbcTemplate jdbcTemplate; + + @PostConstruct + public void postConstruct() { + jdbcTemplate = new JdbcTemplate(dataSource, false); + initSchema(); + } + + public void initSchema() { + LOG.trace("Initializing schema"); + jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS billing"); + + // ЗДЕСЬ ДОЛЖНА БЫТЬ ХОРОШАЯ РЕАЛИЗАЦИЯ БАЗЫ ДАННЫХ + // Users table + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.users " + + "(username VARCHAR PRIMARY KEY, password VARCHAR, enabled BOOLEAN)"); + jdbcTemplate.update("INSERT INTO billing.users VALUES (\'username\', \'password\', TRUE)"); + + // Variable table + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.variables " + + "(username VARCHAR, variable VARCHAR, val DOUBLE)"); + + // Functions table + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions " + + "(username VARCHAR, function VARCHAR, arity INTEGER, body VARCHAR)"); + } + + private Boolean checkNull(String condition) { + LOG.trace("check " + condition); + String allSelect = "SELECT * FROM " + condition; + return jdbcTemplate.queryForObject( + allSelect, + new Object[]{}, + new RowMapper() { + @Override + public Boolean mapRow(ResultSet rs, int rowNum) throws SQLException { + Boolean flag = false; + while (true) { + if (!rs.next()) { + break; + } + flag = true; + } + return flag; + } + } + ); + } + + 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 putUser(String username, String password) { + try { + loadUser(username); + } catch (EmptyResultDataAccessException e) { + jdbcTemplate.execute("INSERT INTO billing.users VALUES (\'" + username + "\', \'" + password + "\', TRUE)"); + } + } + + public Double getVariable(String username, String variable) { + LOG.trace("Get variable " + username + " variable " + variable); + try { + return jdbcTemplate.queryForObject( + "select val 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("val").toString() + ); + } + } + ); + } catch (EmptyResultDataAccessException e) { + return 0.0; + } + } + + public void putVariable(String username, String variable, Double value) { + LOG.trace("Put variable " + variable + " for user " + username); + try { + deleteVariable(username, variable); + } catch (Exception e) { + e.printStackTrace(); + } finally { + jdbcTemplate.execute("INSERT INTO billing.variables VALUES (\'" + + username + "\', \'" + variable + "\', " + value.toString() + ")"); + } + } + + public void deleteVariable(String username, String variable) { + LOG.trace("Delete variable " + variable + "for user " + username); + jdbcTemplate.execute("DELETE FROM billing.variables WHERE username = \'" + + username + "\' AND variable = \'" + variable + "\'"); + } + + public HashMap getAllVariables(String username) throws ParsingException { + LOG.trace("List with all functions for user: " + username); + try { + return jdbcTemplate.queryForObject( + "SELECT username, variable, val FROM billing.variables WHERE username = ?", + new Object[]{username}, + new RowMapper>() { + @Override + public HashMap mapRow(ResultSet rs, int rowNum) throws SQLException { + HashMap tmp = new HashMap(); + while (true) { + tmp.put(rs.getString("variable"), Double.toString(rs.getDouble("val"))); + if (!rs.next()) { + break; + } + } + return tmp; + } + } + ); + } catch (EmptyResultDataAccessException e) { + HashMap tmp = new HashMap(); + return tmp; + } + } + + public void putFunction(String username, String function, Integer arity, String body) { + LOG.trace("Put function " + function + " for user " + username); + try { + deleteFunction(username, function); + } catch (Exception e) { + e.printStackTrace(); + } finally { + jdbcTemplate.execute("INSERT INTO billing.functions VALUES (\'" + + username + "\', \'" + function + "\', " + arity.toString() + ", \'" + + body + "\')"); + } + } + + public String getFunction(String username, String function) { + LOG.trace("Load function " + function + " for 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"); + } + } + ); + } + + public void deleteFunction(String username, String function) { + LOG.info("Delete function " + function + " for user " + username); + jdbcTemplate.execute("DELETE FROM billing.functions WHERE username = \'" + + username + "\' AND function = \'" + function + "\'"); + } +} \ No newline at end of file diff --git a/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingDatabaseConfiguration.java b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingDatabaseConfiguration.java new file mode 100644 index 000000000..f39ab15d1 --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingDatabaseConfiguration.java @@ -0,0 +1,30 @@ +package ru.mipt.java2016.homework.g596.kupriyanov.task4; + +/** + * Created by Artem Kupriyanov on 17/12/2016. + */ + +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); + } +} diff --git a/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingUser.java b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingUser.java new file mode 100644 index 000000000..75e2e37fd --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/BillingUser.java @@ -0,0 +1,73 @@ +package ru.mipt.java2016.homework.g596.kupriyanov.task4; + +/** + * Created by Artem Kupriyanov on 17/12/2016. + */ + +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-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/CalculatorController.java b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/CalculatorController.java new file mode 100644 index 000000000..52754b0c9 --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/CalculatorController.java @@ -0,0 +1,118 @@ +package ru.mipt.java2016.homework.g596.kupriyanov.task4; + +/** + * Created by Artem Kupriyanov on 17/12/2016. + */ + +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; + +@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 "" + + "FediqApp" + + "

Hello, " + name + "!

" + + ""; + } + + @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain") + public String eval(Authentication auth, @RequestBody String expression) throws ParsingException { + LOG.debug("Evaluation request: [" + expression + "]"); + String username = auth.getName(); + double result = calculator.calculate(expression); + //String result = functionalCalculate(expression, username); + LOG.trace("Result: " + result); + //return result; + return Double.toString(result) + "\n"; + } + + @RequestMapping(path = "/reg/{userName}", method = RequestMethod.PUT, + consumes = "text/plain", produces = "text/plain") + public void reg(@PathVariable String userName, @RequestBody String passwd) throws ParsingException { + LOG.debug("New user: [" + userName + ' ' + passwd + "]"); + billingDao.putUser(userName, passwd); + } + + private String functionalCalculate(String expression, String username) throws ParsingException { + String goodExpression = expression; + Parser parser = new Parser(expression, username); + for (String function : parser.getFunction()) { + if (expression.indexOf(function) != -1) { + String subExpression = parser.expressionInFunction(expression, function); + String functionAndOperand = function + subExpression; + expression.replace(functionAndOperand, functionalCalculate(subExpression, username)); + } + } + goodExpression = parser.work(); + double result = calculator.calculate(goodExpression); + return Double.toString(result) + "\n"; + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.PUT, + consumes = "text/plain", produces = "text/plain") + public void putVariable(Authentication auth, + @PathVariable String variableName, @RequestParam(value = "value") Double value) { + String username = auth.getName(); + billingDao.putVariable(username, variableName, value); + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.GET, + consumes = "text/plain", produces = "text/plain") + public String getVariable(Authentication auth, @PathVariable String variableName) { + String username = auth.getName(); + Double value = billingDao.getVariable(username, variableName); + return value.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 = "/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-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/FediqApplication.java b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/FediqApplication.java new file mode 100644 index 000000000..241551283 --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/FediqApplication.java @@ -0,0 +1,48 @@ +package ru.mipt.java2016.homework.g596.kupriyanov.task4; + +/** + * Created by Artem Kupriyanov on 17/12/2016. + */ + +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.kupriyanov.task1.MyCalculator; + + +//запуск +// 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 = FediqApplication.class) +public class FediqApplication { + + @Bean + public Calculator calculator() { + return new MyCalculator(); + } + + @Bean + public EmbeddedServletContainerCustomizer customizer( + @Value("${ru.mipt.java2016.homework.g000.lavrentyev.task4.httpPort:9001}") int port) { + return container -> container.setPort(port); + } + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(FediqApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.run(args); + } +} diff --git a/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/Parser.java b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/Parser.java new file mode 100644 index 000000000..0992809c3 --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/Parser.java @@ -0,0 +1,77 @@ +package ru.mipt.java2016.homework.g596.kupriyanov.task4; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.*; + +/** + * Created by Artem Kupriyanov on 19/12/2016. + */ + + +public class Parser extends BillingDao { + private String expression; + private String username; + private Map variables; + private List functions; + + Parser(String exp, String usern) { + expression = exp; + username = usern; + try { + variables = getAllVariables(username); + } catch (ParsingException e) { + variables = new HashMap<>(); + } + //functions = + } + + class LengthComparator implements Comparator { + @Override + public int compare(String s1, String s2) { + return -(s1.length() - s2.length()); // по убыванию + } + } + + private void substituteVariable() { + LengthComparator myComparator = new LengthComparator(); + List sortedVariables = new ArrayList<>(variables.keySet()); + Collections.sort(sortedVariables, myComparator); + for (String variable: sortedVariables) { + if (expression.contains(variable)) { + expression.replaceAll(variable, variables.get(variable)); + } + } + } + + public String expressionInFunction(String express, String function) { + int start = express.indexOf(function) + function.length(); + int bracketBalance = 0; + if (express.charAt(start) != '(') { + return null; + } + int end = start; + while (end != express.length()) { + if (express.charAt(end) == '(') { + bracketBalance++; + } + if (express.charAt(end) == ')') { + bracketBalance--; + } + if (bracketBalance == 0) { + break; + } + end++; + } + return express.substring(start, end); + } + + public String work() { + substituteVariable(); + return expression; + } + + public List getFunction() { + return functions; + } +} diff --git a/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/SecurityServiceConfiguration.java b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/SecurityServiceConfiguration.java new file mode 100644 index 000000000..2e016fd5f --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/SecurityServiceConfiguration.java @@ -0,0 +1,59 @@ +package ru.mipt.java2016.homework.g596.kupriyanov.task4; + +/** + * Created by Artem Kupriyanov on 17/12/2016. + */ + +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); + } + }); + } +} \ No newline at end of file diff --git a/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/start.txt b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/start.txt new file mode 100644 index 000000000..35fe69bb0 --- /dev/null +++ b/homework-g596-kupriyanov/src/main/java/ru/mipt/java2016/homework/g596/kupriyanov/task4/start.txt @@ -0,0 +1,28 @@ +запуск + 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" + +авторизация +curl http://localhost:9001/reg?args="artemkaa, 599" -X POST -H "Content-Type: text/plain" -H \ +"Authorization: Basic $(echo -n "username:password" | base64)" + +положить переменную +curl http://localhost:9001/variable/pi?value="3.1415926" -X PUT -H "Content-Type: text/plain" -H \ +"Authorization: Basic $(echo -n "username:password" | base64)" + +curl http://localhost:9001/variable/pi -X GET -H "Content-Type: text/plain" -H \ +"Authorization: Basic $(echo -n "username:password" | base64)" + + curl http://localhost:9001/reg/artemkaa \ + -X PUT \ + -H "Content-Type: text/plain" \ + --data-raw "599" + +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" + +curl http://localhost:9001/function/sum -X GET -H "Content-Type: text/plain" -H \ +"Authorization: Basic $(echo -n "username:password" | base64)" \ No newline at end of file