diff --git a/communote/webapp/src/main/java/com/communote/server/web/bootstrap/DispatcherServletInitializer.java b/communote/webapp/src/main/java/com/communote/server/web/bootstrap/DispatcherServletInitializer.java index a693ba2..99c5d5e 100644 --- a/communote/webapp/src/main/java/com/communote/server/web/bootstrap/DispatcherServletInitializer.java +++ b/communote/webapp/src/main/java/com/communote/server/web/bootstrap/DispatcherServletInitializer.java @@ -3,8 +3,13 @@ import javax.servlet.ServletContext; import javax.servlet.ServletRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.access.BootstrapException; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.SourceFilteringListener; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; @@ -14,12 +19,42 @@ import com.communote.server.core.bootstrap.InstallationPreparedCallback; /** - * - * @author Communote GmbH - http://www.communote.com/ + * Component which initializes the main Spring DispatcherServlet which handles all requests. This + * component ensures that the web application context of the DispatcherServlet is configured + * correctly: + * + * For Spring security to work correctly after the refresh a special delegating filter proxy + * (com.communote.server.web.commons.filter.RefreshAwareDelegatingFilterProxy) is used. + * + * @author Communote team - http://communote.github.io/ */ public class DispatcherServletInitializer implements ApplicationPreparedCallback, InstallationPreparedCallback { + private class ContextRefreshListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + // just delegate the refresh event to the DispatcherServlet + mainDispatcherServlet.onApplicationEvent(event); + } + } + + private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherServletInitializer.class); + // param holding location of web-app context configuration which should be used after installation (see web.xml) + private static final String PARAM_WEB_CONTEXT_CONFIG_LOCATION = "communoteWebContextConfigLocation"; + // param holding location of web-app context configuration which should be used during installation + private static final String PARAM_INSTALLER_WEB_CONTEXT_CONFIG_LOCATION = "communoteInstallerWebContextConfigLocation"; + private final ServletContext servletContext; private DispatcherServlet mainDispatcherServlet; @@ -36,34 +71,35 @@ public void applicationPrepared(ApplicationContext applicationContext) { // completely initialized CommunoteRuntime.getInstance().addInitializationCondition( WebAppReadyListener.WEB_APPLICATION_CONTEXT_INITIALIZATION_CONDITION); - // there is currently no benefit in setting the applicationContext as parent context - // because of the installer use-case. As long as wee do not find a way to have a - // separate web app context for the installer components the web-beans cannot have - // autowired core beans because these are not available when not yet installed - // (BeanCreationExeptions). - createMainDispatcherServlet(null); + // set the core application context as parent context of the web app context + LOGGER.debug("Creating main DispatcherServlet with web application context and parent core context"); + createMainDispatcherServlet(PARAM_WEB_CONTEXT_CONFIG_LOCATION, applicationContext); } else { - // if we find a way to solve the installer-use-case as mentioned above we could use this - // else branch to add the core context as parent and refresh the web app context like - // so: + // the main DispatcherServlet already exists which means when Communote was started it + // was not yet installed. Now the installation process got that far that the core app + // context is available. Thus, we refresh the web app context. - // First we need to add a refresh listener otherwise the dispatcherServlet won't be + // First we need to add a refresh listener otherwise the DispatcherServlet won't be // informed about the refresh of the context. URL handler mappings and other stuff // wouldn't be updated then. The listener must also be added to the static listeners - // otherwise it will be lost when the context is refreshed - // mainWebApplicationContext.getApplicationListeners().add( new - // SourceFilteringListener(mainWebApplicationContext, new ContextRefreshListener())); - // mainWebApplicationContext.setParent(applicationContext); - // mainDispatcherServlet.refresh(); - // The ContextRefreshListener is just an implementation of ApplicationListener which - // delegates the applicationEvent to the mainDispatcherServlet. + // otherwise it will be lost when the context is refreshed. + mainWebApplicationContext.getApplicationListeners().add(new SourceFilteringListener( + mainWebApplicationContext, new ContextRefreshListener())); + // set core app context as parent + mainWebApplicationContext.setParent(applicationContext); + // set the new config location containing the beans to be used after installation + mainWebApplicationContext + .setConfigLocation(getRequiredInitParameter(PARAM_WEB_CONTEXT_CONFIG_LOCATION)); + LOGGER.info("Refreshing web application context"); + mainDispatcherServlet.refresh(); } } - private void createMainDispatcherServlet(ApplicationContext rootContext) { + private void createMainDispatcherServlet(String contextConfigLocationParameter, + ApplicationContext rootContext) { mainWebApplicationContext = new XmlWebApplicationContext(); mainWebApplicationContext - .setConfigLocation(getRequiredInitParameter("communoteWebContextConfigLocation")); + .setConfigLocation(getRequiredInitParameter(contextConfigLocationParameter)); mainWebApplicationContext.setParent(rootContext); // add ContextLoaderListener with web-ApplicationContext which publishes it under a // ServletContext attribute and closes it on shutdown. The former is required for @@ -97,26 +133,14 @@ private String getRequiredInitParameter(String parameterName) { @Override public void installationPrepared() { - // TODO could add a separate dispatcher servlet which only handles the installer. With a - // special filter that forwards to internal/installer the installer could than be removed - // from spring security. Moreover the installer servlet wouldn't be needed when starting - // Communote after the installation is done. - - // another idea would be to have one dispatcher servlet and a special 'installer' bean - // profile which includes only the installer beans. If not installed we activate the - // installer profile otherwise the 'default' one. In applicationPrepared the installer - // profile is deactivated and the context is refreshed. But at this point the installation - // is not complete and thus some (all?) installer beans also have to be in the default - // profile. It is also unsure what happens if a request is sent while the context is - // being refreshed... - // When programmatically creating servlets all servlets have to be created before the // servlet context is initialized. But since the (root) application context cannot be - // initialized until the installation is done we do not set aparent context. This will be + // initialized until the installation is done we don't set a parent context. This will be // done when applicationPrepared is called. The null check should avoid re-creating the - // dispatcher servlet if the Runtime is restarted (by the installer). + // dispatcher servlet if the Runtime is restarted by the installer. if (this.mainDispatcherServlet == null) { - createMainDispatcherServlet(null); + LOGGER.debug("Creating main DispatcherServlet with installer web application context"); + createMainDispatcherServlet(PARAM_INSTALLER_WEB_CONTEXT_CONFIG_LOCATION, null); } } diff --git a/communote/webapp/src/main/java/com/communote/server/web/bootstrap/WebAppReadyListener.java b/communote/webapp/src/main/java/com/communote/server/web/bootstrap/WebAppReadyListener.java index 234b693..c0376dc 100644 --- a/communote/webapp/src/main/java/com/communote/server/web/bootstrap/WebAppReadyListener.java +++ b/communote/webapp/src/main/java/com/communote/server/web/bootstrap/WebAppReadyListener.java @@ -1,5 +1,7 @@ package com.communote.server.web.bootstrap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; @@ -11,11 +13,13 @@ * context is ready for use. * * @author Communote GmbH - http://www.communote.com/ - * + * @see com.communote.server.web.bootstrap.DispatcherServletInitializer */ @Component public class WebAppReadyListener implements ApplicationListener { + private static final Logger LOGGER = LoggerFactory.getLogger(WebAppReadyListener.class); + /** * ID of the initialization condition which will be fulfilled as soon as the web application * context is ready for use. @@ -24,6 +28,7 @@ public class WebAppReadyListener implements ApplicationListener + * Extension to {@link DelegatingFilterProxy} which is aware of refreshs of the application context + * that manages the filter delegate. After a refresh of the context it will delegate to the new + * instance of the filter bean. For the refresh observation to work the + * {@link WebApplicationContext} needs to extend {@link AbstractApplicationContext}. + *

+ * Most of the code is directly taken from {@link DelegatingFilterProxy}. + * + * @author Communote team - http://communote.github.io/ + * @see com.communote.server.web.bootstrap.DispatcherServletInitializer + */ +public class RefreshAwareDelegatingFilterProxy extends DelegatingFilterProxy { + + private static final Logger LOGGER = LoggerFactory + .getLogger(RefreshAwareDelegatingFilterProxy.class); + + private volatile Filter delegate; + + private final Object delegateMonitor = new Object(); + private boolean refreshListenerRegistered; + + private class ContextRefreshListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + LOGGER.debug("Web application was refreshed - resetting filter delegate"); + synchronized (delegateMonitor) { + // reset the delegate, doFilter will init it again with the bean from the refreshed + // web app context + delegate = null; + } + } + } + + @Override + protected void initFilterBean() throws ServletException { + synchronized (this.delegateMonitor) { + if (this.delegate == null) { + // If no target bean name specified, use filter name. + if (this.getTargetBeanName() == null) { + this.setTargetBeanName(getFilterName()); + } + // Fetch Spring root application context and initialize the delegate early, + // if possible. If the root application context will be started after this + // filter proxy, we'll have to resort to lazy initialization. + WebApplicationContext wac = findWebApplicationContext(); + if (wac != null) { + LOGGER.debug("Initializing filter delegate during bean initialization"); + this.delegate = init(wac); + } else { + LOGGER.debug("Web application context not available. Filter delegate will be initialized lazily."); + } + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // Lazily initialize the delegate if necessary. + Filter delegateToUse = this.delegate; + if (delegateToUse == null) { + LOGGER.debug("Lazily initializing filter delegate"); + synchronized (this.delegateMonitor) { + if (this.delegate == null) { + WebApplicationContext wac = findWebApplicationContext(); + if (wac == null) { + throw new IllegalStateException( + "No WebApplicationContext found: no ContextLoaderListener registered?"); + } + this.delegate = init(wac); + } + delegateToUse = this.delegate; + } + } + + // Let the delegate perform the actual doFilter operation. + invokeDelegate(delegateToUse, request, response, filterChain); + } + + /** + * Register a listener for the context refresh event and init the filter delegate as defined in + * {@link #initDelegate(WebApplicationContext)}. + * + * @param wac + * the web application context + * @return the filter delegate + * @throws ServletException + * if thrown by the filter + */ + private Filter init(WebApplicationContext wac) throws ServletException { + if (!refreshListenerRegistered) { + LOGGER.debug("Registering listener to observe refreshes of web application context"); + if (wac instanceof AbstractApplicationContext) { + // add refresh listener. Add to static listeners otherwise it would get lost during + // refresh. + ((AbstractApplicationContext) wac).getApplicationListeners().add( + new SourceFilteringListener(wac, new ContextRefreshListener())); + refreshListenerRegistered = true; + } else { + LOGGER.warn( + "Refreshs of the web application context cannot be tracked because the context implementation does not support this"); + } + } + return initDelegate(wac); + } +} diff --git a/communote/webapp/src/main/java/com/communote/server/web/fe/installer/controller/InstallationController.java b/communote/webapp/src/main/java/com/communote/server/web/fe/installer/controller/InstallationController.java index 45738af..61a2874 100644 --- a/communote/webapp/src/main/java/com/communote/server/web/fe/installer/controller/InstallationController.java +++ b/communote/webapp/src/main/java/com/communote/server/web/fe/installer/controller/InstallationController.java @@ -547,7 +547,9 @@ protected void postProcessPage(HttpServletRequest request, Object command, Error // and load its data. If user switched back to DB setup after entering application // details his previous input will be preserved if there is no existing data in the // database. - checkForExistingApplication(request, (InstallerForm) command); + if (isDatabaseInitialized()) { + checkForExistingApplication(request, (InstallerForm) command); + } break; case 3: handleApplicationDetails(request, command, errors); diff --git a/communote/webapp/src/main/java/com/communote/server/web/fe/portal/user/client/controller/ClientOverviewController.java b/communote/webapp/src/main/java/com/communote/server/web/fe/portal/user/client/controller/ClientOverviewController.java index d44edbf..e68a635 100644 --- a/communote/webapp/src/main/java/com/communote/server/web/fe/portal/user/client/controller/ClientOverviewController.java +++ b/communote/webapp/src/main/java/com/communote/server/web/fe/portal/user/client/controller/ClientOverviewController.java @@ -3,9 +3,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.ModelAndView; -import com.communote.server.api.ServiceLocator; import com.communote.server.api.core.blog.BlogManagement; import com.communote.server.core.blog.helper.BlogManagementHelper; import com.communote.server.core.common.LimitHelper; @@ -26,10 +26,23 @@ */ public class ClientOverviewController extends SimpleViewController { + private UserManagement userManagement; + private NoteService noteService; + private BlogManagement blogManagement; + private RepositoryConnectorDelegate repoConnectorDelegate; + + @Autowired + public ClientOverviewController(UserManagement userManagement, NoteService noteService, + BlogManagement blogManagement, RepositoryConnectorDelegate repoConnectorDelegate) { + this.userManagement = userManagement; + this.noteService = noteService; + this.blogManagement = blogManagement; + this.repoConnectorDelegate = repoConnectorDelegate; + } @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { - long userCount = ServiceLocator.findService(UserManagement.class).getActiveUserCount(); + long userCount = userManagement.getActiveUserCount(); long userLimit = UserManagementHelper.getCountLimit(); request.setAttribute("userCount", userCount); request.setAttribute("userCountLimit", LimitHelper.getCountLimitAsString(userLimit)); @@ -42,7 +55,7 @@ public ModelAndView handleRequest(HttpServletRequest request, HttpServletRespons prepareFileRepositorySizeDetails(request); - long userTaggedCount = ServiceLocator.findService(NoteService.class).getNoteCount(); + long userTaggedCount = noteService.getNoteCount(); long userTaggedLimit = ResourceStoringHelper.getCountLimit(); request.setAttribute("userTaggedSize", userTaggedCount); request.setAttribute("userTaggedLimit", LimitHelper.getCountLimitAsString(userTaggedLimit)); @@ -53,7 +66,7 @@ public ModelAndView handleRequest(HttpServletRequest request, HttpServletRespons request.setAttribute("userTaggedLimitReached", LimitHelper.isCountLimitReached(userTaggedCount, userTaggedLimit)); - long blogCount = ServiceLocator.findService(BlogManagement.class).getBlogCount(); + long blogCount = blogManagement.getBlogCount(); long blogLimit = BlogManagementHelper.getCountLimit(); request.setAttribute("blogSize", blogCount); request.setAttribute("blogLimit", LimitHelper.getCountLimitAsString(blogLimit)); @@ -75,8 +88,7 @@ public ModelAndView handleRequest(HttpServletRequest request, HttpServletRespons private void prepareFileRepositorySizeDetails(HttpServletRequest request) { long repoSize = -1; try { - RepositoryConnector connector = ServiceLocator.findService( - RepositoryConnectorDelegate.class).getDefaultRepositoryConnector(); + RepositoryConnector connector = repoConnectorDelegate.getDefaultRepositoryConnector(); if (connector instanceof FilesystemConnector) { repoSize = connector.getRepositorySize(); } diff --git a/communote/webapp/src/main/webapp/WEB-INF/config/communote-installer-web-context.xml b/communote/webapp/src/main/webapp/WEB-INF/config/communote-installer-web-context.xml new file mode 100644 index 0000000..6c641d6 --- /dev/null +++ b/communote/webapp/src/main/webapp/WEB-INF/config/communote-installer-web-context.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + /WEB-INF/config/installer/tiles-def-installer.xml + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/communote/webapp/src/main/webapp/WEB-INF/config/communote-web-context.xml b/communote/webapp/src/main/webapp/WEB-INF/config/communote-web-context.xml index e54f86b..c00e8f5 100644 --- a/communote/webapp/src/main/webapp/WEB-INF/config/communote-web-context.xml +++ b/communote/webapp/src/main/webapp/WEB-INF/config/communote-web-context.xml @@ -1,5 +1,6 @@ - +