Пишем с IoC Starter. Базовый маппинг запросов, используя context, web и orm

от автора

image

Введение

С момента первого релиза прошло достаточно много времени (ссылка на предыдущую статью). Что изменилось?

  • улучшена стабильность системы в целом;
  • реализована ленивая загрузка компонентов;
  • встроена базовая система слушателей;
  • встроена поддержка аспектно-ориентированного программирования (для средней сложности решения задач, в остальном все же советую использовать — AspectJ библиотеку)
  • новый загрузчик RequestFactory

**Модули

  • модуль работы с кешем на базе EhCache, Guava
  • модуль работы с потоками (как инициализация посредством аннотации @SimpleTask, так и прямая работа с пулом)
  • модуль работы с базой (легковесный ORM с поддержкой JPA, Transactions, NO-SQL Driver — Orient, Crud methods, repository system и автогенерацией запросов из функции класса-репозитория)
  • модуль работы с веб-мордой (маппинг линков посредством аннотаций, поддержка кастомных producers/consumes, Velocity Template Rendering Page, Basic Security Requests, Sessions, Cookies, SSL) на базе Netty 4.1.30.Final

«Это конечно же все хорошо,»- скажите Вы, -«но по факту работает ли это все?».
«Да, работает. Прошу под кат».

Введение

Для реализации примера я буду использовать Maven 3 и Intelijj Idea 2018.2.

1) Подключаем зависимости:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xmlns="http://maven.apache.org/POM/4.0.0"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>     <artifactId>example-webapp</artifactId>     <packaging>jar</packaging>      <build>         <plugins>             <plugin>                 <groupId>org.apache.maven.plugins</groupId>                 <artifactId>maven-compiler-plugin</artifactId>                 <version>3.8.0</version>                 <configuration> ```1.8</source>                     <target>1.8</target>                 </configuration>                  <executions>                     <execution>                         <id>default-testCompile</id>                         <phase>test-compile</phase>                         <goals>                             <goal>testCompile</goal>                         </goals>                     </execution>                 </executions>             </plugin>         </plugins>     </build>      <dependencies>         <dependency>             <groupId>org.ioc</groupId>             <artifactId>context-factory</artifactId>             <version>2.2.4.STABLE</version>         </dependency>          <dependency>             <groupId>org.ioc</groupId>             <artifactId>orm-factory</artifactId>             <version>2.2.4.STABLE</version>         </dependency>          <dependency>             <groupId>org.ioc</groupId>             <artifactId>web-factory</artifactId>             <version>2.2.4.STABLE</version>         </dependency>     </dependencies>      <repositories>         <repository>             <id>context</id>             <url>https://raw.github.com/GenCloud/ioc_container/context</url>         </repository>         <repository>             <id>cache</id>             <url>https://raw.github.com/GenCloud/ioc_container/cache</url>         </repository>         <repository>             <id>threading</id>             <url>https://raw.github.com/GenCloud/ioc_container/threading</url>         </repository>         <repository>             <id>orm</id>             <url>https://raw.github.com/GenCloud/ioc_container/orm</url>         </repository>         <repository>             <id>web</id>             <url>https://raw.github.com/GenCloud/ioc_container/web</url>         </repository>     </repositories> </project>

**На maven central пока не переехал, увы.

Структура проекта:
структура
Стандартный MVC паттерн, не правда ли?

Создадим точку входа в приложение:

package org.examples.webapp;  import org.ioc.annotations.context.ScanPackage; import org.ioc.annotations.modules.CacheModule; import org.ioc.annotations.modules.DatabaseModule; import org.ioc.annotations.modules.ThreadingModule; import org.ioc.annotations.modules.WebModule; import org.ioc.context.starter.IoCStarter;  @WebModule @CacheModule @ThreadingModule @DatabaseModule @ScanPackage(packages = {"org.examples.webapp"}) public class AppMain {     public static void main(String[] args) {         IoCStarter.start(AppMain.class);     } }

**Пояснения:
Аннотация @ScanPackages — определяет контексту пакеты для выявления компонентов (в простонародии — «бинов»).
Аннотация @WebModule — служит для подключения и инициализации web фабрики.
Аннотация @CacheModule — служит для подключения и инициализации фабрики кеша, используется для корректной работы ORM (в будущих версиях аннотация не будет требоваться).
Аннотация @ThreadingModule — служит для подключения и инициализации фабрики потоков, используется для корректной работы web фабрики (в будущих версиях аннотация не будет требоваться).
Аннотация @DatabaseModule — служит для подключения и инициализации фабрики ORM.
Все фабрики имеют дефолтные конфигураторы, которые можно изменить на свои с переопределением функций использующихся настроек фабриками (в каждой аннотации модуля переопределен класс конфигуратор — Class<?> autoConfigurationClass() default WebAutoConfiguration.class), либо же отключить любую конфигурацию посредством аннотации @Exclude в main классе.
Утилита IoCStarter — главный класс-инициализатор контекста.

Ну вроде бы все и готово, контекст инициализируется, веб работает на дефолтном порту 8081, но линковки нет и при переходе на сайт нам ничего толком не выдает. Что ж, идем дальше.

Создадим конфигурационный файл для наших модулей. По дефолту все конфиги грузятся из {working_dir}/configs/default_settings.properties — его и создадим по соответствующему пути.

# Threading ioc.threads.poolName=shared ioc.threads.availableProcessors=4 ioc.threads.threadTimeout=0 ioc.threads.threadAllowCoreTimeOut=true ioc.threads.threadPoolPriority=NORMAL # Event dispather # кол-во дескрипторов (процессоров) для обработки слушателей (асинхронное выполнение) ioc.dispatcher.availableDescriptors=4 # Cache # фабрика кеша (EhFactory|GuavaFactory) cache.factory=org.ioc.cache.impl.EhFactory # Datasource # тип базы (локальная-собственная, локальная-серверная или удаленная) #LOCAL, LOCAL_SERVER, REMOTE datasource.orient.database-type=LOCAL # местонахождение базы datasource.orient.url=./database # имя базы данных (для локальной не обязательно) datasource.orient.database=orient # пользователь базы datasource.orient.username=admin # пароль пользователя datasource.orient.password=admin # конфигурация для маппинга сущностей в базу (create, dropCreate, refresh, none) datasource.orient.ddl-auto=dropCreate # конфигурация сообщающая менеджеру, показывать сгенерированные запросы или нет datasource.orient.showSql=true # Web server # порт работы веб сервера web.server.port=8081 # нужен ли SSL обработчик web.server.ssl-enabled=false # in seconds # таймаут сессий (дефолтный 7200 сек. = 2 часа) web.server.security.session.timeout=300 # кодировки веб-морды web.server.velocity.input.encoding=UTF-8 web.server.velocity.output.encoding=UTF-8 # загрузчик веб-морды web.server.velocity.resource.loader=file # класс загрузчика web.server.velocity.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader # путь к находящейся веб-морде web.server.velocity.resource.loading.path=./public

Далее, нам нужны сущность пользователя и ее управляющий репозиторий:
Реализация сущности TblAccount:

package org.examples.webapp.domain.entity;  import org.ioc.web.security.user.UserDetails;  import javax.persistence.*; import java.util.Collections; import java.util.List;  @Entity public class TblAccount implements UserDetails {     @Id     @GeneratedValue(strategy = GenerationType.SEQUENCE)     private long id;      @Column(name = "username")     private String username;      @Column(name = "password")     private String password;      @Transient     private String repeatedPassword;      public String getRepeatedPassword() {         return repeatedPassword;     }      public void setRepeatedPassword(String repeatedPassword) {         this.repeatedPassword = repeatedPassword;     }      @Override     public String getUsername() {         return username;     }      public void setUsername(String username) {         this.username = username;     }      @Override     public String getPassword() {         return password;     }      public void setPassword(String password) {         this.password = password;     }      @Override     public List<String> getRoles() {         return Collections.singletonList("ROLE_USER");     } }

**Пояснения:
Все стандартно как и у всех JPA-поддерживающих фреймворков. Маппим сущность с помощью @.Entity, создаем Primary Key с помощью аннотации @.Id, маппим колонки с помощью аннотации @Column. и унаследуемся от UserDetails для идентификации сущности в Security модуле.

Реализациия репозитория сущности TblAccountRepository:

package org.examples.webapp.domain.repository;  import org.examples.webapp.domain.entity.TblAccount; import org.ioc.annotations.context.IoCRepository; import org.ioc.orm.repositories.CrudRepository;  import javax.transaction.Transactional;  @IoCRepository public interface TblAccountRepository extends CrudRepository<TblAccount, Long> {     @Transactional     TblAccount findByUsernameEq(String username); }

**Пояснения:
Аннотация @IoCRepository — служит для определения контекстом, что класс является репозиториеми его нужно обработать «по-другому».
Поддерживает стандартные CRUD функции:

  • Entity fetch(ID id) — достает сущность типа Entity из базы, либо из кеша по Primary Key;
  • List fetchAll() — достает все сущности типа Entity, предварительно загружая их в кеш;
  • void save(Entity entity) — создает/обновляет сущность типа Entity как в базе так и в кеше;
  • void delete(Entity entity) — удаляет сущность типа Entity как из базы так и из кеша;
  • boolean exists(ID id) — проверяет наличие сущности в базе по Primary Key.
    Все CRUD-запросы происходят в транзакции.

Поддерживает автогенерацию запросов посредством переопределения функций с ключевыми словами, как в реализации выше (TblAccount findByUsernameEq(String username)) и вызов зарегистрированных запросов (NamedQuery)

Функция findByUsernameEq(String username) — осуществляет поиск сущности по ее полю username. Сгенерированный запрос:

     select * from tbl_account where username = 'username'

Далее нам понадобиться, уровень для управления бизнес-логикой.
Реализации AccountService:

package org.examples.webapp.service;  import org.examples.webapp.domain.entity.TblAccount; import org.examples.webapp.domain.repository.TblAccountRepository; import org.examples.webapp.responces.IMessage; import org.ioc.annotations.context.IoCComponent; import org.ioc.annotations.context.IoCDependency; import org.ioc.web.model.http.Request; import org.ioc.web.security.configuration.SecurityConfigureAdapter; import org.ioc.web.security.encoder.bcrypt.BCryptEncoder; import org.ioc.web.security.user.UserDetails; import org.ioc.web.security.user.UserDetailsProcessor;  import java.util.Objects;  import static org.examples.webapp.responces.IMessage.Type.ERROR; import static org.examples.webapp.responces.IMessage.Type.OK;  @IoCComponent public class AccountService implements UserDetailsProcessor {     @IoCDependency     private TblAccountRepository tblAccountRepository;      @IoCDependency     private BCryptEncoder bCryptEncoder;      @IoCDependency     private SecurityConfigureAdapter securityConfigureAdapter;      @Override     public UserDetails loadUserByUsername(String username) {         return tblAccountRepository.findByUsernameEq(username);     }      public void save(TblAccount tblAccount) {         tblAccountRepository.save(tblAccount);     }      public void delete(TblAccount tblAccount) {         tblAccountRepository.delete(tblAccount);     }      public IMessage tryCreateUser(String username, String password, String repeatedPassword) {         if (username == null || username.isEmpty() || password == null || password.isEmpty()                 || repeatedPassword == null || repeatedPassword.isEmpty()) {             return new IMessage(ERROR, "Invalid request parameters!");         }          if (!Objects.equals(password, repeatedPassword)) {             return new IMessage(ERROR, "Repeated password doesn't match!");         }          final UserDetails userDetails = loadUserByUsername(username);         if (userDetails != null) {             return new IMessage(ERROR, "Account already exists!");         }          final TblAccount account = new TblAccount();         account.setUsername(username);         account.setPassword(bCryptEncoder.encode(password));          save(account);         return new IMessage(OK, "Successfully created!");     }      public IMessage tryAuthenticateUser(Request request, String username, String password) {         if (username == null || username.isEmpty() || password == null || password.isEmpty()) {             return new IMessage(ERROR, "Invalid request parameters!");         }          final UserDetails userDetails = loadUserByUsername(username);         if (userDetails == null) {             return new IMessage(ERROR, "Account not found!");         }          if (!bCryptEncoder.match(password, userDetails.getPassword())) {             return new IMessage(ERROR, "Password does not match!");         }          securityConfigureAdapter.getContext().authenticate(request, userDetails);         return new IMessage(OK, "Successfully authenticated");     }      public IMessage logout(Request request) {         if (securityConfigureAdapter.getContext().removeAuthInformation(request)) {             return new IMessage(OK, "/");         }          return new IMessage(ERROR, "Credentials not found or not authenticated!");     } }

**Пояснения:
Аннотация @IoCComponent — служит для инициализации класса как компонента.
Аннотация @IoCDependency — служит для внедрения зависимостей в инстанс класса.
Утилита BCryptEncoder — реализация кодека BCrypt для шифрования пароля (пока что единственные кодек).
Системный инстанс SecurityConfigureAdapter — служит для работы с маппингом запросов и сессиий пользователей.
Функция UserDetails loadUserByUsername — наследуемая функция UserDetailsProcessor, служит для загрузки пользователя в сессию и выставлении флага аутентификации (в будущем для стандартного маппинга авторизации в Security)
Функция IMessage tryCreateUser — функция создания пользователя.
Функция IMessage tryAuthenticateUser — функция аутентификации пользователя.
Функция IMessage logout — функция очистки сессии от авторизированного пользователя.
Класс IMessage — класс-утилита для выведении нужной нам информации в браузере (json-ответ).

package org.examples.webapp.responces;  public class IMessage {     private final String message;     private final Type type;      public IMessage(String message) {         this.message = message;         type = Type.OK;     }      public IMessage(Type type, String message) {         this.message = message;         this.type = type;     }      public String getMessage() {         return message;     }      public Type getType() {         return type;     }      public enum Type {         OK,         ERROR     } }

Теперь понадобиться реализация самой линковки (маппинга запросов):

package org.examples.webapp.mapping;  import org.examples.webapp.domain.entity.TblAccount; import org.examples.webapp.responces.IMessage; import org.examples.webapp.service.AccountService; import org.ioc.annotations.context.IoCDependency; import org.ioc.annotations.web.IoCController; import org.ioc.web.annotations.Credentials; import org.ioc.web.annotations.MappingMethod; import org.ioc.web.annotations.RequestParam; import org.ioc.web.annotations.UrlMapping; import org.ioc.web.model.ModelAndView; import org.ioc.web.model.http.Request;  @IoCController @UrlMapping("/") public class MainMapping {     @IoCDependency     private AccountService accountService;      @UrlMapping     public ModelAndView index() {         final ModelAndView modelAndView = new ModelAndView();         modelAndView.setView("index");         return modelAndView;     }      @UrlMapping(value = "signup", method = MappingMethod.POST)     public IMessage createUser(@RequestParam("username") String username,                                @RequestParam("password") String password,                                @RequestParam("repeatedPassword") String repeatedPassword) {         return accountService.tryCreateUser(username, password, repeatedPassword);     }      @UrlMapping(value = "signin", method = MappingMethod.POST)     public IMessage auth(Request request,                          @RequestParam("username") String username,                          @RequestParam("password") String password) {         return accountService.tryAuthenticateUser(request, username, password);     }      @UrlMapping("signout")     public IMessage signout(Request request) {         return accountService.logout(request);     }      @UrlMapping("loginPage")     public ModelAndView authenticated(@Credentials TblAccount account) {         final ModelAndView modelAndView = new ModelAndView();         modelAndView.setView("auth");          modelAndView.addAttribute("account", account);         return modelAndView;     } }

**Пояснения:
Аннотация @IoCController — служит для идентификации класса в контексте, как контроллера (маппера запросов браузера)
Аннотация @UrlMapping — указывает, что нужно проанализировать функцию/класс на наличие запросов, обрабатываемых хендлерами канала.
Параметры:

  • value — нужный нам запрос;
  • method — http метод для обработки (GET, POST, PUT, etc.);
  • consumes — http mime type для проверки наличия запроса конкретного типа (опционально);
  • produces — http content-type для отдачи в ответе конкретного типа контента (Content-Type: text/html; charset=utf-8, Content-Type: multipart/form-data; boundary=something, etc. опционально;

Аннотация @RequestParam — служит для определении имени получаемого параметра из запроса. Поскольку постольку дефолтными средствами рефлексии нельзя получить текущее имя параметра метода, мне было лень подключать лишнюю зависимость javaassist, шаманить с асмом. Поэтому такой себе метод определения имени параметра для внедрения этому параметру значения, полученного из запроса. Существует аналог для GET типа — @PathVariable — тот же самый принцип работы (не совместим с POST).
Аннотация @Credentials — служит для вставки текущих данных авторизированного пользователя, в противном случаи может быть null, если информации авторизированного пользователя нет в сесии.
Системный класс Request — текущая информация о поступившем запросе, содержащая в себе коки, хидеры и канал пользователя, который в последствии можно будет отправлять Push Message’s… у кого какая фантазия уже на этот счет.
Класс-утилита ModelAndView — модель страницы с именем ресурса без расширения, и атрибутами для внедрения в ресурс.

Вроде бы все, но нет — нужно обязательно сконфигурировать доступный маппинг запросов для пользователей.

package org.examples.webapp.config;  import org.ioc.annotations.configuration.Property; import org.ioc.annotations.configuration.PropertyFunction; import org.ioc.web.security.configuration.HttpContainer; import org.ioc.web.security.configuration.SecurityConfigureProcessor; import org.ioc.web.security.encoder.Encoder; import org.ioc.web.security.encoder.bcrypt.BCryptEncoder; import org.ioc.web.security.filter.CorsFilter; import org.ioc.web.security.filter.CsrfFilter;  @Property public class SecurityConfig implements SecurityConfigureProcessor {     @Override     public void configure(HttpContainer httpContainer) {         httpContainer.                 configureRequests().                 anonymousRequests("/", "/signup", "/signin").                 resourceRequests("/static/**").                 authorizeRequests("/loginPage", "ROLE_USER").                 authorizeRequests("/signout", "ROLE_USER").                 and().                 configureSession().                 expiredPath("/");     }      @PropertyFunction     public CsrfFilter csrfFilter() {         return new CsrfFilter();     }      @PropertyFunction     public CorsFilter corsFilter() {         return new CorsFilter();     }      @PropertyFunction     public Encoder encoder() {         return new BCryptEncoder();     } }

**Пояснения:
Аннотация @Property — сообщает контексту, что это конфигурационный файл и его нужно инициализировать.
Аннотация @PropertyFunction — сообщает анализатору конфигураций, что эта функция возвращает какой-то тип и должен его инициализировать как компонент (бин).
Интерфейс SecurityConfigureProcessor — утилита служащая для конфигурации маппинга запросов.
Класс-модель HttpContainer — утилита, хранящая в себе маппинг запросов, указанных пользователем.
Класс CsrfFilter — фильтр не валидных запросов (реализации механики CSRF).
Класс CorsFilter — фильтр Cross-Origin Resource Sharing.

Функция anonymousRequests — принимает в себя неограниченный массив запросов, не требует авторизированных пользователей и проверки ролей (ROLE_ANONYMOUS).
Функция resourceRequests — принимает в себя неограниченный массив запросов, конкретно служит для определения, по какому пути будет лежать ресурсный файл, не требующей сложной обработки (css, js, images, etc.).
Функция authorizeRequests — принимает в себя неограниченный массив запросов, требует авторизированного пользователя и конкретную роль, присущую пользователю.
Функция expiredPath — при очистке истекшей по времени сессии, пользователя перебрасывает по этому маппингу (redirect link).

Что ж, остались страницы, скрипты и стили сайта (глубоко углубляться не буду).

Заголовок спойлера

index.vm — главная страница

<html> <head>     <meta charset="utf-8"/>     <title>IoC Test</title>     <link rel="stylesheet" href="/static/css/bootstrap.min.css">      <link rel="stylesheet" href="/static/css/style.css"/>     <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/>     <link rel="stylesheet" href="/static/css/pnotify.css"/>     <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/> </head> <body> <div class="container">     <h1>IoC Starter Test</h1>     <br>     <h4>Create user</h4>     <br>     <form id="creation">         <label for="username">Username: </label>         <input type="text" id="username" name="username" class="color-input-field"/>         <label for="password">Password: </label>         <input type="password" id="password" name="password" class="color-input-field"/>         <label for="repeatedPassword">Repeate: </label>         <input type="password" id="repeatedPassword" name="repeatedPassword" class="color-input-field"/>         <button type="button" class="btn btn-success btn-create">Sing up!</button>     </form>      <h4>Authenticate</h4>     <br>     <form id="auth">         <label for="username">Username: </label>         <input type="text" id="username" name="username" class="color-input-field"/>         <label for="password">Password: </label>         <input type="password" id="password" name="password" class="color-input-field"/>         <button type="button" class="btn btn-danger btn-auth">Sing in!</button>     </form> </div>  <script type="text/javascript" src="/static/js/jquery.js"></script> <script type="text/javascript" src="/static/js/bootstrap.min.js"></script> <script type="text/javascript" src="/static/js/scripts.js"></script> <script type="text/javascript" src="/static/js/pnotify.js"></script> <script type="text/javascript" src="/static/js/pnotify.buttons.js"></script>  </body> </html>

auth.vm — для отображения авторизированного пользователя

<html> <head>     <meta charset="utf-8"/>     <title>IoC Test</title>     <link rel="stylesheet" href="/static/css/bootstrap.min.css">      <link rel="stylesheet" href="/static/css/style.css"/>     <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/>     <link rel="stylesheet" href="/static/css/pnotify.css"/>     <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/> </head> <body> <div class="container">     <h1>Authorized page</h1>     <br>      <h4>Test auth data</h4>     <div id="auth_data">         #if($!account)             <h4>Hello @$!account.username, You successfully authenticated!</h4>             <br>             <button type="button" class="btn btn-success btn-logout">Logout!</button>         #end     </div> </div>  <script type="text/javascript" src="/static/js/jquery.js"></script> <script type="text/javascript" src="/static/js/bootstrap.min.js"></script> <script type="text/javascript" src="/static/js/scripts.js"></script> <script type="text/javascript" src="/static/js/pnotify.js"></script> <script type="text/javascript" src="/static/js/pnotify.buttons.js"></script>  </body> </html>

scripts.js — контроллер, для отправки и получения информации запроса на сервер

$(function () {     $(".btn-create").click(function () {         var cooki = cookie();         document.cookie = 'CSRF-TOKEN=' + cooki;          $.ajax({             url: "/signup",             data: $('#creation').serialize(),             headers: {'X-CSRF-TOKEN': cooki},             crossDomain: true,             xhrFields: {                 withCredentials: true             },             type: "POST"         }).done(function (data) {             switch (data.type) {                 case 'OK':                     new PNotify({                         title: 'Success',                         text: data.message,                         type: 'success',                         hide: false                     });                     break;                 case 'ERROR':                     new PNotify({                         title: 'Error',                         text: data.message,                         type: 'error',                         hide: false                     });                     break;             }         });     });      $(".btn-auth").click(function () {         var cooki = cookie();         document.cookie = 'CSRF-TOKEN=' + cooki;          $.ajax({             url: "/signin",             data: $('#auth').serialize(),             headers: {'X-CSRF-TOKEN': cooki},             crossDomain: true,             xhrFields: {                 withCredentials: true             },             type: "POST"          }).done(function (data) {             switch (data.type) {                 case 'OK':                     new PNotify({                         title: 'Success',                         text: data.message,                         type: 'success',                         hide: false                     });                      setTimeout(function () {                         window.location = "/loginPage";                     }, 5000);                     break;                 case 'ERROR':                     new PNotify({                         title: 'Error',                         text: data.message,                         type: 'error',                         hide: false                     });                     break;             }         });     });      $(".btn-logout").click(function () {         $.ajax({             url: "/signout",             crossDomain: true,             xhrFields: {                 withCredentials: true             },             type: "GET"         }).done(function (data) {             switch (data.type) {                 case 'OK':                     new PNotify({                         title: 'Success',                         text: 'Logouting...',                         type: 'success',                         hide: false                     });                      setTimeout(function () {                         window.location = data.message;                     }, 5000);                     break;                 case 'ERROR':                     new PNotify({                         title: 'Error',                         text: data.message,                         type: 'error',                         hide: false                     });                     break;             }         });     }); });  function cookie(a) {     return a           // if the placeholder was passed, return         ? (              // a random number from 0 to 15             a ^            // unless b is 8,             Math.random()  // in which case             * 16           // a random number from             >> a / 4         // 8 to 11         ).toString(16) // in hexadecimal         : (              // or otherwise a concatenated string:             [1e7] +        // 10000000 +             -1e3 +         // -1000 +             -4e3 +         // -4000 +             -8e3 +         // -80000000 +             -1e11          // -100000000000,         ).replace(     // replacing             /[018]/g,    // zeroes, ones, and eights with             cookie           // random hex digits         ) }

Компилим, запускаем все что у нас получилось.
Если все правильно, увидим что-то подобное в конце загрузки:

Лог

[21.10.18 22:29:51:990] INFO web.model.mapping.MappingContainer: Mapped method [/], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.index()]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signup], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.createUser(java.lang.String,java.lang.String,java.lang.String)]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signin], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.auth(org.ioc.web.model.http.Request,java.lang.String,java.lang.String)]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signout], method=[GET], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.signout(org.ioc.web.model.http.Request)]
[21.10.18 22:29:51:995] INFO web.model.mapping.MappingContainer: Mapped method [/loginPage], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.authenticated(org.examples.webapp.domain.entity.TblAccount)]
[21.10.18 22:29:51:997] INFO ioc.web.factory.HttpInitializerFactory: Http server started on port(s): 8081 (http)

Результат:
1) Главная страница
index
2) Регистрация
signup
3) Аутентфикация
auth
4) Страница с результатом авторизации
result
5) Очистка информации авторизации из сесии и перенаправления пользователя на стартовую страницу
image
6) Попытка не авторизированного пользователя попасть на страницу с информацией аутентификации сессии
image

Конец

Проект развивающийся, приветствуются «контрибьюторы» и оригинальные идеи, поскольку одному делать этот проект тяжеловато.
Репозиторий проекта, так же на репозитории имеются примеры использования всего функционала в модуле ‘examples’, и как говориться, «Good luck, have fun», всем спасибо за внимание.


ссылка на оригинал статьи https://habr.com/post/427247/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *