Введение
С момента первого релиза прошло достаточно много времени (ссылка на предыдущую статью). Что изменилось?
- улучшена стабильность системы в целом;
- реализована ленивая загрузка компонентов;
- встроена базовая система слушателей;
- встроена поддержка аспектно-ориентированного программирования (для средней сложности решения задач, в остальном все же советую использовать — 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.
<?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>
<groupId>org.ioc</groupId>
<packaging>jar</packaging>
<version>0.0.1</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<.source>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) Главная страница
2) Регистрация
3) Аутентфикация
4) Страница с результатом авторизации(редирект после правильного ввода логина и пароля)
5) Очистка информации авторизации из сесии и перенаправления пользователя на стартовую страницу
6) Попытка не авторизированного пользователя попасть на страницу с информацией аутентификации сессии
Конец
Проект развивающийся, приветствуются "контрибьюторы" и оригинальные идеи, поскольку одному делать этот проект тяжеловато.
Репозиторий проекта.
Контекст
ОРМ фабрика
Веб фабрика
Примеры
Текущий пример из статьи
Так же на репозитории имеются примеры использования всего функционала в модуле 'examples', и как говориться, "Good luck, have fun", всем спасибо за внимание.