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:
+ *
+ * - if Communote is not yet installed, the context will contain only beans required by the
+ * installer
+ * - if Communote is already installed, the context will contain all beans needed for normal
+ * operation and will have the core application context (backend beans) as a parent context so that
+ * these beans can be autowired and don't have to be fetched via the ServiceLocator
+ * - if Communote is not yet installed but the installation process is completed by the user, the
+ * web application context of the DispatcherServlet is refreshed to contain the beans and have the
+ * parent context as described above
+ *
+ * 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 @@
-
+