I have a simple Spring Boot application with Vaadin for UI and Spring Boot Security.
What I'm trying to achive is simple navigation between components from login page to main view.
this is my security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
// Not using Spring CSRF here to be able to use plain HTML for the login page
http.csrf().disable()
.authorizeRequests()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.and().formLogin().loginPage(LOGIN_URL).permitAll().loginProcessingUrl(LOGIN_PROCESSING_URL)
.failureUrl(LOGIN_FAILURE_URL)
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
}
this is my LoginView:
#Route("login")
#UIScope
#SpringComponent
public class LoginView extends VerticalLayout {
/**
* AuthenticationManager is already exposed in WebSecurityConfig
*/
#Autowired
private AuthenticationManager authManager;
private LoginOverlay loginOverlay;
public LoginView() {
loginOverlay = new LoginOverlay();
loginOverlay.addLoginListener(this::authenticate);
loginOverlay.setOpened(true);
LoginI18n i18n = LoginI18n.createDefault();
i18n.setAdditionalInformation("Welcome");
loginOverlay.setI18n(i18n);
add(loginOverlay);
}
private void authenticate(AbstractLogin.LoginEvent e) {
try {
Authentication auth = authManager.authenticate(
new UsernamePasswordAuthenticationToken(e.getUsername(), e.getPassword()));
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
if (auth.isAuthenticated())
getUI().ifPresent(ui -> ui.navigate(MainView.class));
} catch (Exception ex) {
loginOverlay.setError(true);
}
}}
and the MainView:
#Route("main")
public class MainView extends VerticalLayout implements AfterNavigationObserver {
private final CertView certView;
private final UserView userView;
public MainView(CertView certView, UserView userView) {
this.certView = certView;
this.userView = userView;
}
private void createMain() {
Tab tab1 = new Tab("Certificates");
Tab tab2 = new Tab("Users");
Tabs tabs = new Tabs(tab1, tab2);
certView.setVisible(true);
userView.setVisible(false);
Map<Tab, Component> tabsToPages = new HashMap<>();
tabsToPages.put(tab1, certView);
tabsToPages.put(tab2, userView);
Div pages = new Div(certView, userView);
pages.setSizeFull();
Set<Component> pagesShown = Stream.of(certView)
.collect(Collectors.toSet());
tabs.addSelectedChangeListener(event -> {
pagesShown.forEach(page -> page.setVisible(false));
pagesShown.clear();
Component selectedPage = tabsToPages.get(tabs.getSelectedTab());
selectedPage.setVisible(true);
pagesShown.add(selectedPage);
});
add(tabs, pages);
}
#Override
public void afterNavigation(AfterNavigationEvent afterNavigationEvent) {
createMain();
}
}
CertView and UserView are #UIScoped #SpringComponents that have some DAO injected and are getting data and setting it's components right in theire cosntructor.
Now what happens when authenticate and authManager.authenticate of Login view gets called is view is being routed to the MainView which I can tell by seeing constructor being called, url changes but nothing get's rendered. Strange is that when I set the breakpoint in MainView's constructor page renders successfully.
I'm quite new to Vaadin and I don't know what should the correct navigation look like so you can comment better way of doing this, but actually I would like to stay as simple as possible.
So how do I navigate correctly or get mainView's content rendered at the correct lifecycle event?
Vaadin: 13.0.1
I had the same problem. I solved it with closing the loginOverlay before navigating to another route.
if (auth.isAuthenticated())
{
loginOverlay.close(); // <-- add this line!
getUI().ifPresent(ui -> ui.navigate(MainView.class));
}
Related
I choose Vaadin for building my very simple UI within the spring boot application.
I have:
#Route
#PWA(name = "Signing certificates manager", shortName = "CertMgr")
public class MainView extends VerticalLayout {
public MainView() {
Tab tab1 = new Tab("Certificates");
Tab tab2 = new Tab("Users");
Tabs tabs = new Tabs(tab1, tab2);
CertView certView = new CertView();
certView.setVisible(false);
UserView userView = new UserView(addUsers());
userView.setVisible(true);
Map<Tab, Component> tabsToPages = new HashMap<>();
tabsToPages.put(tab1, certView);
tabsToPages.put(tab2, userView);
Div pages = new Div(certView, userView);
Set<Component> pagesShown = Stream.of(userView)
.collect(Collectors.toSet());
tabs.addSelectedChangeListener(event -> {
pagesShown.forEach(page -> page.setVisible(false));
pagesShown.clear();
Component selectedPage = tabsToPages.get(tabs.getSelectedTab());
selectedPage.setVisible(true);
pagesShown.add(selectedPage);
});
add(tabs, pages);
}
private List<User> addUsers() {
return new LinkedList<User>() {{
add(new User("qewr", "asdf", "xzcv"));
}};
}
}
and
public class UserView extends VerticalLayout {
Grid<User> grid;
public UserView(List<User> users) {
grid = new Grid<>(User.class);
grid.setItems(users);
grid.addColumn(User::getMail).setHeader("mail");
grid.addColumn(User::getName).setHeader("name");
grid.addColumn(User::getPass).setHeader("pass");
grid.addSelectionListener(event -> {
List<User> selected = (List<User>) event.getAllSelectedItems();
add(new UserRow(selected.get(0)));
});
add(grid);
}
}
and the result of this is a view like this:
so you can see, the grid has no width and is too long (I have only one user there)
I am new to Vaadin so I am surely doing something wrong, but basically this is vaadin tabs handling from the samples.
My questions are:
how do I draw the grid properly with my current setup?
is it possible to display a view from completely different page when clicking on tab with that page would have it's own context and could use spring's DI? like to have autonomous page and not coupled objects like I have now. or advice the best pattern to handle this with vaadin, please.
Vaadin: 12.0.7
Spring-Boot: 2.1.2.RELEASE
Hope you get my point.
Thanks for advices!
just do
Div pages = new Div(certView, userView);
pages.setSizeFull();
I am getting errors like "failed to create a child event loop/failed to open a new selector/Too many open files" when there are 30 or more concurrent requests...How to solve the above errors? Am I doing anything wrong? I am using Spring boot and Java cassandra driver. Below is the connection file:
public class Connection {
public static Session getConnection() {
final Cluster cluster = Cluster.builder().addContactPoint(ConnectionBean.getCASSANDRA_DB_IP())
.withQueryOptions(new QueryOptions().setConsistencyLevel(ConsistencyLevel.LOCAL_ONE))
.withCredentials(ConnectionBean.getCASSANDRA_USER(), ConnectionBean.getCASSANDRA_PASSWORD())
.withPoolingOptions(poolingOptions)
.build();
final Session session = cluster.connect(ConnectionBean.getCASSANDRA_DB_NAME());
return session;
}
}
Below is the ConnectionBean file which I used in Connection file:
public class ConnectionBean {
public static String CASSANDRA_DB_IP;
public static String CASSANDRA_DB_NAME;
public static String CASSANDRA_USER;
public static String CASSANDRA_PASSWORD;
public ConnectionBean() {
}
public ConnectionBean(String CASSANDRA_DB_IP,String CASSANDRA_DB_NAME,String CASSANDRA_USER,String CASSANDRA_PASSWORD) {
this.CASSANDRA_DB_IP=CASSANDRA_DB_IP;
this.CASSANDRA_DB_NAME=CASSANDRA_DB_NAME;
this.CASSANDRA_USER=CASSANDRA_USER;
this.CASSANDRA_PASSWORD=CASSANDRA_PASSWORD;
}
public static String getCASSANDRA_DB_IP() {
return CASSANDRA_DB_IP;
}
public static void setCASSANDRA_DB_IP(String cASSANDRA_DB_IP) {
CASSANDRA_DB_IP = cASSANDRA_DB_IP;
}
public static String getCASSANDRA_DB_NAME() {
return CASSANDRA_DB_NAME;
}
public static void setCASSANDRA_DB_NAME(String cASSANDRA_DB_NAME) {
CASSANDRA_DB_NAME = cASSANDRA_DB_NAME;
}
public static String getCASSANDRA_USER() {
return CASSANDRA_USER;
}
public static void setCASSANDRA_USER(String cASSANDRA_USER) {
CASSANDRA_USER = cASSANDRA_USER;
}
public static String getCASSANDRA_PASSWORD() {
return CASSANDRA_PASSWORD;
}
public static void setCASSANDRA_PASSWORD(String cASSANDRA_PASSWORD) {
CASSANDRA_PASSWORD = cASSANDRA_PASSWORD;
}
}
Below is the class from where ConnectionBean variables are initialized :
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String LOGIN_PROCESSING_URL = "/login";
private static final String LOGIN_FAILURE_URL = "/login?error";
private static final String LOGIN_URL = "/login";
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private DataSource dataSource;
#Value("${spring.queries.users-query}")
private String usersQuery;
#Value("${spring.queries.roles-query}")
private String rolesQuery;
#Value("${CASSANDRA_DB_IP}")
public String CASSANDRA_DB_IP;
#Value("${CASSANDRA_DB_NAME}")
public String CASSANDRA_DB_NAME;
#Value("${CASSANDRA_USER}")
public String CASSANDRA_USER;
#Value("${CASSANDRA_PASSWORD}")
public String CASSANDRA_PASSWORD;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
ConnectionBean cb = new ConnectionBean(CASSANDRA_DB_IP, CASSANDRA_DB_NAME, CASSANDRA_USER, CASSANDRA_PASSWORD);
auth.jdbcAuthentication().usersByUsernameQuery(usersQuery).authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Not using Spring CSRF here to be able to use plain HTML for the login page
http.csrf().disable()
// Register our CustomRequestCache, that saves unauthorized access attempts, so
// the user is redirected after login.
.requestCache().requestCache(new CustomRequestCache())
// Restrict access to our application.
.and().authorizeRequests()
// Allow all flow internal requests.
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
// Allow all requests by logged in users.
.anyRequest().authenticated()
// Configure the login page.
.and().formLogin().loginPage(LOGIN_URL).permitAll().loginProcessingUrl(LOGIN_PROCESSING_URL)
.failureUrl(LOGIN_FAILURE_URL)
// Register the success handler that redirects users to the page they last tried
// to access
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
// Configure logout
.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
}
/**
* Allows access to static resources, bypassing Spring security.
*/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
// Vaadin Flow static resources
"/VAADIN/**",
// the standard favicon URI
"/favicon.ico",
// web application manifest
"/manifest.json", "/sw.js", "/offline-page.html",
// icons and images
"/icons/**", "/images/**",
// (development mode) static resources
"/frontend/**",
// (development mode) webjars
"/webjars/**",
// (development mode) H2 debugging console
"/h2-console/**",
// (production mode) static resources
"/frontend-es5/**", "/frontend-es6/**");
}
}
And finally, below is the class through which I am querying cassandra data:
public class getData {
Session session;
public getData(){
session = Connection.getConnection();
getDataTable();
}
private void getDataTable() {
try {
String query = "SELECT * FROM tableName";
ResultSet rs = session.execute(query);
for (Row row : rs) {
/*Do some stuff here using row*/
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
If getConnection() is being invoked for every request, you are creating a new Cluster instance each time.
This is discouraged because one connection is created between your client and a C* node for each Cluster instance, and for each Session a connection pool of at least one connection is created for each C* node.
If you are not closing your Cluster instances after a request completes, these connections will remain open. After a number of requests, you'll have so many connections open that you will run out of file descriptors in your OS.
To resolve this issue, create only one Cluster and Session instance and reuse it between requests. This strategy is outlined in 4 simple rules when using the DataStax drivers for Cassandra:
Use one Cluster instance per (physical) cluster (per application lifetime)
Use at most one Session per keyspace, or use a single Session and explicitely specify the keyspace in your queries
How do I propagate the listeners on the next loaded page using UI4J?
https://github.com/ui4j/ui4j
For example, The below will navigate to Google and attach to all input tags a click listener, but I would like to have the binds reattached after searching and on every page loaded on the "input" tags.
import com.ui4j.api.browser.BrowserEngine;
import com.ui4j.api.browser.BrowserFactory;
import com.ui4j.api.browser.Page;
import com.ui4j.api.dom.Element;
public class AutoWeb {
public static void main(String[] args) {
// get the instance of the webkit
BrowserEngine browser = BrowserFactory.getWebKit();
// navigate to Google
Page page = browser.navigate("http://www.google.com");
// show the browser page
page.show();
// bind the "input" tags
// This should apply for all "input" tags on any page.
for (Element element: page.getWindow().getDocument().queryAll("input")) {
element.bindClick((handler) -> {
System.out.println("Clicked!");
});
}
}
}
If I use an Interceptor, how do I access the Page Document from the afterLoad() method in order to re-apply the bindings?
Interceptor interceptor = new Interceptor() {
#Override
public void beforeLoad(Request request) {
}
#Override
public void afterLoad(Response response) {
// Apply the bindings on after load.
}
};
PageConfiguration pageConfiguration = new PageConfiguration(interceptor);
Page page = browser.navigate("http://www.google.com", pageConfiguration);
I am now going through my code to ensure it is correct and consistent. Straight way I find that I load a view in two different ways:
One is:
public HikeRecordView() {
//On load of page get the stored view data and create the page
verticalPanel.addAttachHandler(new Handler() {
public void onAttachOrDetach(AttachEvent event) {
if (event.isAttached()) {
rpc = (DBConnectionAsync) GWT.create(DBConnection.class);
ServiceDefTarget target = (ServiceDefTarget) rpc;
String moduleRelativeURL = GWT.getModuleBaseURL() + "MySQLConnection";
target.setServiceEntryPoint(moduleRelativeURL);
horizontalPanel_Existing.clear();
verticalPanel.clear();
AsyncCallback<ViewData> callback = new ViewDataHandler<ViewData>(HikeRecordView.this);
rpc.getViewData(callback);
}
}
});
initWidget(verticalPanel);
}
And the other is:
public PackHolidayView() {
rpc = (DBConnectionAsync) GWT.create(DBConnection.class);
ServiceDefTarget target = (ServiceDefTarget) rpc;
String moduleRelativeURL = GWT.getModuleBaseURL() + "MySQLConnection";
target.setServiceEntryPoint(moduleRelativeURL);
//On load of page render the page
verticalPanel.addAttachHandler(new Handler() {
public void onAttachOrDetach(AttachEvent event) {
verticalPanel.clear();
verticalPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
//On load of page get the Account Level and ID of the account holder.
AsyncCallback<ViewData> callback = new ViewDataHandler<ViewData>(PackHolidayView.this);
rpc.getViewData(callback);
}
});
initWidget(verticalPanel);
}
They both seem to work so which is the best/recommended way to load a view please?
You should think about splitting the code between Model, View and Presenter. Create appropriate layers. Consider using GIN for Dependency Injection.
Read an article located here: http://www.canoo.com/blog/2011/04/05/gwt-dependency-injection-recipes-using-gin/
I dynamic create SourcePollingChannelAdapter. After add new url into db, i create new instance of SourcePollingChannelAdapter andd start it.
Here code:
public SourcePollingChannelAdapter getAdapter(RssStream rssStream) throws MalformedURLException {
String beanIdAdapter = "adapter." + rssStream.getId();
String beanIdSource = "source." + rssStream.getId();
SourcePollingChannelAdapter adapter;
if (context.containsBean(beanIdAdapter)) {
adapter = (SourcePollingChannelAdapter) context.getBean(beanIdAdapter);
}
else {
URL url = new URL(rssStream.getLink());
FeedEntryMessageSource source = new FeedEntryMessageSource(url, "news");
source.setApplicationContext(context);
source.setBeanName("source");
source.setBeanFactory(beanFactory);
source.afterPropertiesSet();
adapter = new SourcePollingChannelAdapter();
adapter.setApplicationContext(context);
adapter.setBeanName(beanIdAdapter);
adapter.setBeanFactory(beanFactory);
adapter.setSource(source);
adapter.setOutputChannel(channel);
adapter.setTrigger(new PeriodicTrigger(5000));
adapter.afterPropertiesSet();
}
return adapter;
}
But, after delete url from db, i need stop and delete SourcePollingChannelAdapter.
public void deleteAdapter(int id) {
final String beanId = "adapter." + id;
if (context.containsBean(beanId)) {
SourcePollingChannelAdapter adapter = (SourcePollingChannelAdapter) context.getBean(beanId);
adapter.stop(new Runnable() {
#Override
public void run() {
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
beanDefinitionRegistry.removeBeanDefinition(beanId);
}
});
}
}
I create new adaptar, start it and I try to delete. But i got NoSuchBeanDefinitionException. How to solve this problem?
You need to register the bean
if (beanFactory instanceof ConfigurableListableBeanFactory) {
((ConfigurableListableBeanFactory) beanFactory).registerSingleton(
beanIdAdapter, adapter);
}
Although the framework does so, you don't really need to register the source (unless you also need to access it later).
EDIT:
However, the framework is not really designed for this activity - it would be better to keep the references in your own class. See this note.