Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Michael Breu
SkeletonTests
Commits
7d77ed98
Commit
7d77ed98
authored
Feb 16, 2021
by
Blaas Alexander
Browse files
first draft with websockets introduced
parent
11404799
Changes
38
Hide whitespace changes
Inline
Side-by-side
.gitignore
0 → 100644
View file @
7d77ed98
/bin/
/target/
/.classpath
/.settings/
/.project
/src/main/webapp/WEB-INF/.faces-config.xml.jsfdia
\ No newline at end of file
README
View file @
7d77ed98
...
...
@@ -26,3 +26,4 @@ Christian Sillaber
Michael Brunner
Clemens Sauerwein
Andrea Mussmann
Alexander Blaas
pom.xml
View file @
7d77ed98
...
...
@@ -69,19 +69,6 @@
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-security
</artifactId>
</dependency>
<dependency>
<groupId>
com.sun.faces
</groupId>
<artifactId>
jsf-api
</artifactId>
<version>
2.2.20
</version>
<scope>
compile
</scope>
</dependency>
<dependency>
<groupId>
com.sun.faces
</groupId>
<artifactId>
jsf-impl
</artifactId>
<version>
2.2.20
</version>
<scope>
compile
</scope>
<optional>
true
</optional>
</dependency>
<dependency>
<groupId>
org.apache.tomcat.embed
</groupId>
<artifactId>
tomcat-embed-core
</artifactId>
...
...
@@ -99,6 +86,18 @@
<scope>
compile
</scope>
</dependency>
<!-- special websockets dependencies -->
<dependency>
<groupId>
org.joinfaces
</groupId>
<artifactId>
omnifaces3-spring-boot-starter
</artifactId>
<version>
4.4.2
</version>
</dependency>
<dependency>
<groupId>
org.joinfaces
</groupId>
<artifactId>
weld-spring-boot-starter
</artifactId>
<version>
4.4.2
</version>
</dependency>
<!-- special test dependencies -->
<dependency>
<groupId>
org.springframework.boot
</groupId>
...
...
@@ -162,4 +161,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
\ No newline at end of file
src/main/java/at/qe/skeleton/Main.java
View file @
7d77ed98
package
at.qe.skeleton
;
import
at.qe.skeleton.configs.CustomServletContextInitializer
;
import
at.qe.skeleton.configs.WebSecurityConfig
;
import
at.qe.skeleton.utils.ViewScope
;
import
java.util.HashMap
;
import
javax.faces.webapp.FacesServlet
;
import
org.apache.catalina.startup.ContextConfig
;
import
org.springframework.beans.factory.config.CustomScopeConfigurer
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
...
...
@@ -12,8 +12,15 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
import
org.springframework.boot.web.servlet.ServletRegistrationBean
;
import
org.springframework.boot.web.servlet.support.SpringBootServletInitializer
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.ComponentScan
;
import
org.springframework.context.annotation.ComponentScan.Filter
;
import
org.springframework.context.annotation.FilterType
;
import
org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
;
import
at.qe.skeleton.configs.CustomServletContextInitializer
;
import
at.qe.skeleton.configs.WebSecurityConfig
;
import
at.qe.skeleton.utils.ViewScope
;
/**
* Spring boot application. Execute maven with <code>mvn spring-boot:run</code>
* to start this web application.
...
...
@@ -24,34 +31,45 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
*/
@SpringBootApplication
@EnableGlobalMethodSecurity
(
prePostEnabled
=
true
)
/*
* Prevent spring from trying to autowire the websocket-infrastructure: Exclude
* the at.qe.skeleton.ui.websockets package from component scan.
*
* NOTE: Do not add any components to this package which should be managed by
* spring. It is reserved for the CDI-injection-mechanisms (Weld). Only add
* CDI-managed components.
*/
@ComponentScan
(
basePackages
=
"at.qe.skeleton"
,
excludeFilters
=
@Filter
(
type
=
FilterType
.
REGEX
,
pattern
=
"at.qe.skeleton.ui.websockets.*"
))
public
class
Main
extends
SpringBootServletInitializer
{
public
static
void
main
(
String
[]
args
)
{
SpringApplication
.
run
(
Main
.
class
,
args
);
}
@Override
protected
SpringApplicationBuilder
configure
(
SpringApplicationBuilder
application
)
{
return
application
.
sources
(
new
Class
[]{
Main
.
class
,
CustomServletContextInitializer
.
class
,
WebSecurityConfig
.
class
});
}
@Bean
public
ServletRegistrationBean
servletRegistrationBean
()
{
FacesServlet
servlet
=
new
FacesServlet
();
ServletRegistrationBean
servletRegistrationBean
=
new
ServletRegistrationBean
(
servlet
,
"*.xhtml"
);
servletRegistrationBean
.
setName
(
"Faces Servlet"
);
servletRegistrationBean
.
setAsyncSupported
(
true
);
servletRegistrationBean
.
setLoadOnStartup
(
1
);
return
servletRegistrationBean
;
}
@Bean
public
CustomScopeConfigurer
customScopeConfigurer
()
{
CustomScopeConfigurer
customScopeConfigurer
=
new
CustomScopeConfigurer
();
HashMap
<
String
,
Object
>
customScopes
=
new
HashMap
<>();
customScopes
.
put
(
"view"
,
new
ViewScope
());
customScopeConfigurer
.
setScopes
(
customScopes
);
return
customScopeConfigurer
;
}
public
static
void
main
(
String
[]
args
)
{
SpringApplication
.
run
(
Main
.
class
,
args
);
}
@Override
protected
SpringApplicationBuilder
configure
(
SpringApplicationBuilder
application
)
{
return
application
.
sources
(
Main
.
class
,
CustomServletContextInitializer
.
class
,
WebSecurityConfig
.
class
,
ContextConfig
.
class
);
}
@Bean
public
ServletRegistrationBean
<
FacesServlet
>
servletRegistrationBean
()
{
FacesServlet
servlet
=
new
FacesServlet
();
ServletRegistrationBean
<
FacesServlet
>
servletRegistrationBean
=
new
ServletRegistrationBean
<>(
servlet
,
"*.xhtml"
);
servletRegistrationBean
.
setName
(
"Faces Servlet"
);
servletRegistrationBean
.
setAsyncSupported
(
true
);
servletRegistrationBean
.
setLoadOnStartup
(
1
);
return
servletRegistrationBean
;
}
@Bean
public
CustomScopeConfigurer
customScopeConfigurer
()
{
CustomScopeConfigurer
customScopeConfigurer
=
new
CustomScopeConfigurer
();
HashMap
<
String
,
Object
>
customScopes
=
new
HashMap
<>();
customScopes
.
put
(
"view"
,
new
ViewScope
());
customScopeConfigurer
.
setScopes
(
customScopes
);
return
customScopeConfigurer
;
}
}
src/main/java/at/qe/skeleton/configs/CustomServletContextInitializer.java
View file @
7d77ed98
...
...
@@ -2,6 +2,7 @@ package at.qe.skeleton.configs;
import
javax.servlet.ServletContext
;
import
javax.servlet.ServletException
;
import
org.springframework.boot.web.servlet.ServletContextInitializer
;
import
org.springframework.context.annotation.Configuration
;
...
...
@@ -15,10 +16,12 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public
class
CustomServletContextInitializer
implements
ServletContextInitializer
{
@Override
public
void
onStartup
(
ServletContext
sc
)
throws
ServletException
{
sc
.
setInitParameter
(
"javax.faces.DEFAULT_SUFFIX"
,
".xhtml"
);
sc
.
setInitParameter
(
"javax.faces.PROJECT_STAGE"
,
"Development"
);
}
@Override
public
void
onStartup
(
ServletContext
sc
)
throws
ServletException
{
sc
.
setInitParameter
(
"javax.faces.DEFAULT_SUFFIX"
,
".xhtml"
);
sc
.
setInitParameter
(
"javax.faces.PROJECT_STAGE"
,
"Development"
);
// websockets configuration
sc
.
setInitParameter
(
"javax.faces.ENABLE_CDI_RESOLVER_CHAIN"
,
"true"
);
sc
.
setInitParameter
(
"org.omnifaces.SOCKET_ENDPOINT_ENABLED"
,
"true"
);
}
}
src/main/java/at/qe/skeleton/configs/WebSecurityConfig.java
View file @
7d77ed98
...
...
@@ -10,8 +10,11 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
;
import
org.springframework.security.crypto.password.NoOpPasswordEncoder
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.security.web.authentication.logout.LogoutSuccessHandler
;
import
org.springframework.security.web.util.matcher.AntPathRequestMatcher
;
import
at.qe.skeleton.spring.CustomizedLogoutSuccessHandler
;
/**
* Spring configuration for web security.
*
...
...
@@ -26,6 +29,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
DataSource
dataSource
;
@Bean
protected
LogoutSuccessHandler
logoutSuccessHandler
()
{
return
new
CustomizedLogoutSuccessHandler
();
}
@Override
protected
void
configure
(
HttpSecurity
http
)
throws
Exception
{
...
...
@@ -37,7 +45,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.
logoutRequestMatcher
(
new
AntPathRequestMatcher
(
"/logout"
))
.
invalidateHttpSession
(
true
)
.
deleteCookies
(
"JSESSIONID"
)
.
logoutSuccessUrl
(
"/login.xhtml"
);
.
logoutSuccessUrl
(
"/login.xhtml"
)
.
logoutSuccessHandler
(
this
.
logoutSuccessHandler
());
http
.
authorizeRequests
()
//Permit access to the H2 console
...
...
@@ -51,6 +60,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//Permit access only for some roles
.
antMatchers
(
"/secured/**"
)
.
hasAnyAuthority
(
"ADMIN"
,
"MANAGER"
,
"EMPLOYEE"
)
// Allow only certain roles to use websockets (only logged in users)
.
antMatchers
(
"/omnifaces.push/**"
)
.
hasAnyAuthority
(
"ADMIN"
,
"MANAGER"
,
"EMPLOYEE"
)
//If user doesn't have permission, forward him to login page
.
and
()
.
formLogin
()
...
...
src/main/java/at/qe/skeleton/model/User.java
View file @
7d77ed98
...
...
@@ -25,7 +25,7 @@ import org.springframework.data.domain.Persistable;
* University of Innsbruck.
*/
@Entity
public
class
User
implements
Persistable
<
String
>,
Serializable
{
public
class
User
implements
Persistable
<
String
>,
Serializable
,
Comparable
<
User
>
{
private
static
final
long
serialVersionUID
=
1L
;
...
...
@@ -194,4 +194,9 @@ public class User implements Persistable<String>, Serializable {
return
(
null
==
createDate
);
}
@Override
public
int
compareTo
(
User
o
)
{
return
this
.
username
.
compareTo
(
o
.
getUsername
());
}
}
src/main/java/at/qe/skeleton/spring/CDIAwareBeanPostProcessor.java
0 → 100644
View file @
7d77ed98
package
at.qe.skeleton.spring
;
import
java.lang.reflect.Field
;
import
javax.enterprise.inject.spi.CDI
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.BeansException
;
import
org.springframework.beans.factory.config.BeanPostProcessor
;
import
org.springframework.stereotype.Component
;
import
at.qe.skeleton.ui.websockets.WebSocketManager
;
import
at.qe.skeleton.utils.CDIAutowired
;
import
at.qe.skeleton.utils.CDIContextRelated
;
/**
* This beanPostProcessor is used to manually "autowire" CDI-managed beans (see
* {@link WebSocketManager}) within the spring-context. This happens after a
* beans' initialization is finished.
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*
*/
@Component
public
class
CDIAwareBeanPostProcessor
implements
BeanPostProcessor
{
private
static
final
Logger
LOGGER
=
LoggerFactory
.
getLogger
(
CDIAwareBeanPostProcessor
.
class
);
@Override
public
Object
postProcessAfterInitialization
(
Object
bean
,
String
beanName
)
throws
BeansException
{
// only if controller has a webSocketManager
Class
<?
extends
Object
>
beanClass
=
bean
.
getClass
();
/*
* proceed only if bean uses websockets (i.e. in our case cdi-managed
* webSocket-infrastructure)
*/
if
(
beanClass
.
isAnnotationPresent
(
CDIContextRelated
.
class
))
{
// check for @CDIAutowired on fields to find the websocket-managing field
for
(
Field
field
:
beanClass
.
getDeclaredFields
())
{
field
.
setAccessible
(
true
);
// when annotation is present, perform a manual "autowiring"
if
(
field
.
isAnnotationPresent
(
CDIAutowired
.
class
))
{
Class
<?>
fieldType
=
field
.
getType
();
Object
cdiManagedBean
=
CDI
.
current
().
select
(
fieldType
).
get
();
LOGGER
.
info
(
"Field '{}' of '{}' successfully autowired"
,
field
.
getName
(),
bean
.
getClass
());
try
{
field
.
set
(
bean
,
cdiManagedBean
);
}
catch
(
IllegalArgumentException
|
IllegalAccessException
e
)
{
LOGGER
.
error
(
"Manual CDI-injection failed"
,
e
);
}
}
}
}
// simply returns bean
return
BeanPostProcessor
.
super
.
postProcessAfterInitialization
(
bean
,
beanName
);
}
}
src/main/java/at/qe/skeleton/spring/CustomizedLogoutSuccessHandler.java
0 → 100644
View file @
7d77ed98
package
at.qe.skeleton.spring
;
import
java.io.IOException
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.security.core.Authentication
;
import
org.springframework.security.web.authentication.logout.LogoutSuccessHandler
;
import
org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
;
import
org.springframework.stereotype.Component
;
import
at.qe.skeleton.ui.controllers.demo.ChatManagerController
;
import
at.qe.skeleton.ui.controllers.demo.UserStatusController
;
/**
* This handler is triggered after a logout is performed.
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*
*/
@Component
public
class
CustomizedLogoutSuccessHandler
extends
SimpleUrlLogoutSuccessHandler
implements
LogoutSuccessHandler
{
@Autowired
private
UserStatusController
userStatusController
;
@Autowired
private
ChatManagerController
chatManagerController
;
@Override
public
void
onLogoutSuccess
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Authentication
authentication
)
throws
IOException
,
ServletException
{
String
username
=
authentication
.
getName
();
// update chat-manager
this
.
chatManagerController
.
onLogout
(
username
);
// update online-status
this
.
userStatusController
.
afterLogout
(
username
);
// continue as expected
super
.
onLogoutSuccess
(
request
,
response
,
authentication
);
}
}
src/main/java/at/qe/skeleton/spring/LoginSuccessHandler.java
0 → 100644
View file @
7d77ed98
package
at.qe.skeleton.spring
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.context.ApplicationListener
;
import
org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
;
import
org.springframework.stereotype.Component
;
import
at.qe.skeleton.ui.controllers.demo.ChatManagerController
;
import
at.qe.skeleton.ui.controllers.demo.UserStatusController
;
/**
* This handler is triggered after a login is performed.
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*
*/
@Component
public
class
LoginSuccessHandler
implements
ApplicationListener
<
InteractiveAuthenticationSuccessEvent
>
{
@Autowired
private
UserStatusController
userStatusController
;
@Autowired
private
ChatManagerController
chatManagerController
;
@Override
public
void
onApplicationEvent
(
InteractiveAuthenticationSuccessEvent
event
)
{
String
username
=
event
.
getAuthentication
().
getName
();
// update chat-manager
this
.
chatManagerController
.
onLogin
(
username
);
// update online-status
this
.
userStatusController
.
afterLogin
(
username
);
}
}
src/main/java/at/qe/skeleton/spring/UserStatusInitializationHandler.java
0 → 100644
View file @
7d77ed98
package
at.qe.skeleton.spring
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.context.ApplicationListener
;
import
org.springframework.context.event.ContextRefreshedEvent
;
import
org.springframework.stereotype.Component
;
import
at.qe.skeleton.ui.controllers.demo.UserStatusController
;
/**
* This handler is triggered after the application-context is refreshed, i.e.
* configurations are setup.
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*
*/
@Component
public
class
UserStatusInitializationHandler
implements
ApplicationListener
<
ContextRefreshedEvent
>
{
@Autowired
private
UserStatusController
userStatusController
;
@Override
public
void
onApplicationEvent
(
ContextRefreshedEvent
event
)
{
// init
this
.
userStatusController
.
setupUserStatus
();
}
}
src/main/java/at/qe/skeleton/ui/controllers/UserDetailController.java
View file @
7d77ed98
...
...
@@ -18,7 +18,12 @@ import org.springframework.stereotype.Component;
@Scope
(
"view"
)
public
class
UserDetailController
implements
Serializable
{
@Autowired
/**
*
*/
private
static
final
long
serialVersionUID
=
-
8724249000495756469L
;
@Autowired
private
UserService
userService
;
/**
...
...
src/main/java/at/qe/skeleton/ui/controllers/UserListController.java
View file @
7d77ed98
...
...
@@ -19,7 +19,11 @@ import org.springframework.stereotype.Component;
@Scope
(
"view"
)
public
class
UserListController
implements
Serializable
{
@Autowired
/**
*
*/
private
static
final
long
serialVersionUID
=
-
174521650843617720L
;
@Autowired
private
UserService
userService
;
/**
...
...
src/main/java/at/qe/skeleton/ui/controllers/demo/ChatManagerController.java
0 → 100644
View file @
7d77ed98
package
at.qe.skeleton.ui.controllers.demo
;
import
java.util.Collections
;
import
java.util.LinkedList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.ConcurrentSkipListSet
;
import
java.util.stream.Collectors
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.context.annotation.Scope
;
import
org.springframework.stereotype.Controller
;
import
at.qe.skeleton.model.User
;
import
at.qe.skeleton.repositories.UserRepository
;
import
at.qe.skeleton.spring.CustomizedLogoutSuccessHandler
;
import
at.qe.skeleton.spring.LoginSuccessHandler
;
import
at.qe.skeleton.ui.websockets.WebSocketManager
;
import
at.qe.skeleton.utils.CDIAutowired
;
import
at.qe.skeleton.utils.CDIContextRelated
;
/**
* The chatManagerController is used to manage all conversations between users
* (message-lists, i.e. chats).
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*/
@Controller
@Scope
(
"application"
)
@CDIContextRelated
public
class
ChatManagerController
{
@Autowired
private
UserRepository
userRepository
;
@CDIAutowired
private
WebSocketManager
websocketManager
;
private
Set
<
User
>
possibleRecipients
=
new
ConcurrentSkipListSet
<>();
private
Map
<
String
,
List
<
Message
>>
chats
=
new
ConcurrentHashMap
<>();
/**
* Called when a user logs in (see {@link LoginSuccessHandler}). Simply
* initializes the chat-infrastructure for the logged in user and adds it to the
* list of possible recipients
*
* @param username
*/
public
void
onLogin
(
String
username
)
{
User
user
=
this
.
userRepository
.
findFirstByUsername
(
username
);
this
.
possibleRecipients
.
add
(
user
);
this
.
chats
.
put
(
username
,
new
LinkedList
<>());
}
/**
* Called when a user logs out (see {@link CustomizedLogoutSuccessHandler}).
* Simply destroys, respectively, clears the previously initialized
* chat-infrastructure and removes the user from the list of possible recipients
*
* @param username
*/
public
void
onLogout
(
String
username
)
{
User
user
=
this
.
userRepository
.
findFirstByUsername
(
username
);
this
.
possibleRecipients
.
remove
(
user
);
this
.
chats
.
remove
(
user
.
getUsername
());
}
/**
* Sends a message to specified recipients (to-property of message) using
* websockets. Only one delivery at a time is allowed.
*
* @param message
*/
public
synchronized
void
deliver
(
Message
message
)
{
User
sender
=
message
.
getFrom
();
List
<
User
>
recipients
=
message
.
getTo
();
List
<
String
>
sendTo
=
recipients
.
stream
().
map
(
User:
:
getUsername
).
collect
(
Collectors
.
toList
());
// don't forget the sender
sendTo
.
add
(
sender
.
getUsername
());
// add to chat-content
this
.
addToChatContent
(
message
,
recipients
);
// also display at sender
this
.
addToChatContent
(
message
,
sender
);
// notify sender and recpipient to update their chat-window
this
.
websocketManager
.
getMessageChannel
().
send
(
"msgRecieved"
,
sendTo
);
}
/**
* Adds a message to the chat-content of the specified users.
*
* @param message The message to add
* @param to The recipient
*/
private
void
addToChatContent
(
Message
message
,
User
to
)
{
this
.
chats
.
get
(
to
.
getUsername
()).
add
(
message
);
}
/**
* Convenience-method. See {@link #addToChatContent(Message, User)}
*
* @param message
* @param to
*/
private
void
addToChatContent
(
Message
message
,
List
<
User
>
to
)
{
to
.
forEach
(
toUser
->
this
.
addToChatContent
(
message
,
toUser
));
}
public
List
<
Message
>
getChatContentRef
(
User
user1
)
{
return
Collections
.
unmodifiableList
(
this
.
chats
.
get
(
user1
.
getUsername
()));
}
public
Set
<
User
>
getPossibleRecipients
()
{
return
Collections
.
unmodifiableSet
(
possibleRecipients
);
}
}
src/main/java/at/qe/skeleton/ui/controllers/demo/LogEntry.java
0 → 100644
View file @
7d77ed98
package
at.qe.skeleton.ui.controllers.demo
;
import
java.util.Date
;
import
at.qe.skeleton.model.User
;
/**
* A class which represents a logEntry
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*/
public
class
LogEntry
{
private
User
user
;
private
Date
timestamp
=
new
Date
();
private
LogEntryType
logType
;