📜 ⬆️ ⬇️

Web App with IoC Starter. Basic request mapping using ioc context, ioc web and ioc orm

image

Introduction


A lot of time has passed since the first release ( link to the previous article ). What changed?



** Modules



Framework structure
struct


“This is of course all is well,” you say, “but in fact does it all work?”
"Yes, it works. I ask under the cat."


Sample implementation process


To implement the example, I will use Maven 3 and Intelijj Idea 2018.2.


1) Connect dependencies:


<?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> 

** I haven't moved to the maven central yet, alas.


Project structure:
structure
Standard MVC pattern, isn't it?


Create an entry point to the application:


 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); } } 

** Explanations:
Annotation @ScanPackages - defines the context of packages for identifying components (in the common people - "bins").
The @WebModule annotation is used to connect and initialize a web factory.
The @CacheModule annotation is used to connect and initialize the cache factory; it is used for ORM to work correctly (in future versions, the summary will not be required).
Annotation @ThreadingModule - serves to connect and initialize the thread factory, used for the web factory to work correctly (in future versions, the summary will not be required).
The @DatabaseModule annotation is used to connect and initialize the ORM factory.
All factories have default configurators that can be changed to their own with overriding the functions of the settings used by the factories (in each module annotation the class configurator is redefined - Class <?> AutoConfigurationClass () default WebAutoConfiguration.class ) or disable any configuration by annotation @Exclude to main classroom.
The IoCStarter utility is the main context initializer class.


Well, everything seems to be ready, the context is initialized, the web works on the default port 8081, but there is no linking and we don’t really give anything away when going to the site. Well, we go further.


Create a configuration file for our modules. By default, all configs are loaded from {working_dir} /configs/default_settings.properties - and create it in the appropriate path.


 # 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 

Next, we need the user's identity and its managing repository:
TblAccount entity implementation:


 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"); } } 

** Explanations:
Everything is standard as in all JPA-supported frameworks. Mapping an entity using @ .Entity , creating a Primary Key using the annotation @ .Id , mapping the column using the annotation @Column. and inherit from UserDetails to identify the entity in the Security module.


Implementing the TblAccountRepository entity repository:


 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); } 

** Explanations:
The @IoCRepository annotation is used to define the context that a class is a repository and it needs to be processed "differently . "
Supports standard CRUD functions:



Supports autogeneration of requests by redefining functions with keywords, as in the implementation above ( TblAccount findByUsernameEq (String username) ) and calling registered requests ( NamedQuery )


Function findByUsernameEq (String username) - searches for an entity by its username field. Generated request:


  select * from tbl_account where username = 'username' 

Next, we need a level to manage business logic.
AccountService implementations:


 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!"); } } 

** Explanations:
The @IoCComponent annotation is used to initialize the class as a component.
The @IoCDependency annotation is used to inject dependencies into a class instance.
The BCryptEncoder utility is an implementation of the BCrypt codec for encrypting a password (so far, the only codec).
System instance SecurityConfigureAdapter - used to work with the mapping of requests and user sessions.
UserDetails loadUserByUsername function - the inherited UserDetailsProcessor function, used to load the user into the session and set the authentication flag (in the future for standard authorization mapping in Security )
The iMessage function tryCreateUser is a user creation function.
The iMessage function tryAuthenticateUser is a user authentication function.
The iMessage logout function is a session clearing function from an authorized user.
The IMessage class is a utility class for displaying the information we need in the browser (json response).


 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 } } 

Now we need to implement the link itself (query mapping):


 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; } } 

** Explanations:
Annotation @IoCController - used to identify a class in context as a controller (browser requests mapper)
The @UrlMapping annotation indicates that the function / class should be analyzed for the presence of requests handled by channel handlers.
Options:



The @RequestParam annotation is used to determine the name of the received parameter from the request. Since the default means of reflection cannot get the current name of the parameter of the method, I was too lazy to connect the extra dependency of javaassist, shamanizing with asm. Therefore, such a method of determining the name of the parameter to implement this parameter values ​​obtained from the query. There is an analogue for GET type - @PathVariable - the same principle of operation (not compatible with POST ).
The @Credentials annotation is used to insert the current data of the authorized user, otherwise it may be null if the information of the authorized user is not in the session.
System class Request - the current information about the incoming request, which contains coca, headers and user channel, which later can be sent Push Message's ... who has any imagination about this.
The ModelAndView utility class is a model of a page with the name of a resource without an extension, and attributes to be inserted into a resource.


It seems to be all, but no - it is necessary to configure the available request mapping for users.


 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(); } } 

** Explanations:
The @Property annotation tells the context that this is a configuration file and needs to be initialized.
Annotation @PropertyFunction - informs the configuration analyzer that this function returns some type and should initialize it as a component (bin).
The SecurityConfigureProcessor interface is a utility used to configure request mapping.
The class model HttpContainer is a utility that stores in itself a mapping of requests specified by the user.
The class CsrfFilter is a filter of not valid queries (implementations of the CSRF mechanics).
The CorsFilter class is a Cross-Origin Resource Sharing filter.


The anonymousRequests function - accepts an unlimited array of requests, does not require authorized users and role checking (ROLE_ANONYMOUS).
The resourceRequests function - takes an unlimited array of requests, specifically serves to determine which path will be the resource file that does not require complex processing (css, js, images, etc.).
The function authorizeRequests - takes an unlimited array of requests, requires an authorized user and a specific role inherent in the user.
The expiredPath function — when clearing an elapsed session, the user throws over this mapping (redirect link).


Well, there were pages, scripts and site styles (I will not go deep).


Spoiler header

index.vm - home page


 <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 - to display an authorized user


 <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 - controller, for sending and receiving information request to the server


 $(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 ) } 

Compile, run all that we have.
If everything is correct, we will see something similar at the end of the download:


Log

[10.21.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 ()]
[10.21.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)]
[10.21.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)]
[10.21.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)]
[10.21.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)]
[10.21.18 22: 29: 51: 997] INFO ioc.web.factory.HttpInitializerFactory: Http server started on port (s): 8081 (http)


Result:
1) Homepage
index
2) Registration
signup
3) Authentication
auth
4) The page with the result of authorization (redirect after entering the correct username and password)
result
5) Clearing authorization information from the session and redirecting the user to the start page
image
6) Attempt by an unauthorized user to access the session authentication information page
image


the end


The project is developing, “contributors” and original ideas are welcome, since it is hard for one to do this project.
Project repository .
Context
ORM factory
Web factory
Examples
The current example from the article
Also on the repository there are examples of using all the functionality in the 'examples' module, and as they say, "Good luck, have fun", thank you all for your attention.


')

Source: https://habr.com/ru/post/427247/


All Articles