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

Структура фреймворка
struct


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


Процесс реализации примера


Для реализации примера я буду использовать 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>
    <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) Главная страница
index
2) Регистрация
signup
3) Аутентфикация
auth
4) Страница с результатом авторизации(редирект после правильного ввода логина и пароля)
result
5) Очистка информации авторизации из сесии и перенаправления пользователя на стартовую страницу
image
6) Попытка не авторизированного пользователя попасть на страницу с информацией аутентификации сессии
image


Конец


Проект развивающийся, приветствуются "контрибьюторы" и оригинальные идеи, поскольку одному делать этот проект тяжеловато.
Репозиторий проекта.
Контекст
ОРМ фабрика
Веб фабрика
Примеры
Текущий пример из статьи
Так же на репозитории имеются примеры использования всего функционала в модуле 'examples', и как говориться, "Good luck, have fun", всем спасибо за внимание.

Комментарии (0)