/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.http;
import java.util.Collection;
import java.util.concurrent.Callable;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import nl.strohalm.cyclos.annotations.Inject;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.initializations.LocalWebInitialization;
import nl.strohalm.cyclos.services.access.AccessService;
import nl.strohalm.cyclos.services.access.exceptions.NotConnectedException;
import nl.strohalm.cyclos.services.application.ApplicationService;
import nl.strohalm.cyclos.services.settings.SettingsService;
import nl.strohalm.cyclos.utils.SpringHelper;
import nl.strohalm.cyclos.utils.TransactionHelper;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData;
import nl.strohalm.cyclos.utils.transaction.TransactionEndListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* Listener for context events
* @author luis
*/
public class LifecycleListener implements ServletContextListener, HttpSessionListener {
private static final Log LOG = LogFactory.getLog(LifecycleListener.class);
private AccessService accessService;
private TransactionHelper transactionHelper;
private SettingsService settingsService;
private ApplicationService applicationService;
/**
* @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
*/
@Override
public void contextDestroyed(final ServletContextEvent event) {
LoggedUser.runAsSystem(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
final ServletContext context = event.getServletContext();
final LocalSettings settings = settingsService == null ? null : settingsService.getLocalSettings();
// Shutdown the application service
if (applicationService != null) {
applicationService.shutdown();
}
final String applicationName = settings == null ? null : settings.getApplicationName();
context.log(applicationName == null ? "Cyclos" : applicationName + " destroyed");
// Suggest a GC as probably there were several released resources
System.gc();
} catch (final Throwable e) {
LOG.error("Error on LifecycleListener.contextDestroyed()", e);
throw new RuntimeException(e);
}
return null; // required by compiler
}
});
}
/**
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(final ServletContextEvent event) {
LoggedUser.runAsSystem(new Callable<Void>() {
@Override
public Void call() {
try {
final ServletContext context = event.getServletContext();
final WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(context);
SpringHelper.injectBeans(applicationContext, LifecycleListener.this);
applicationService.initialize();
context.setAttribute("systemOnline", applicationService.isOnline());
context.setAttribute("cyclosVersion", applicationService.getCyclosVersion());
// Run web initializations
final Collection<LocalWebInitialization> initializations = applicationContext.getBeansOfType(LocalWebInitialization.class).values();
runAll(initializations);
final LocalSettings settings = settingsService.getLocalSettings();
context.log(settings.getApplicationName() + " initialized");
// Suggest a GC in order to keep the heap low right after a startup
System.gc();
} catch (final Throwable e) {
LOG.error("Error on LifecycleListener.contextInitialized()", e);
throw new RuntimeException(e);
}
return null; // required by compiler
}
});
}
/**
* @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent)
*/
@Override
public void sessionCreated(final HttpSessionEvent event) {
// Nothing to do
}
/**
* @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent)
*/
@Override
public void sessionDestroyed(final HttpSessionEvent event) {
final HttpSession session = event.getSession();
final String sessionId = session == null ? null : session.getId();
if (sessionId == null) {
return;
}
// If there is an active transaction, use an transaction end listener to actually logout
if (transactionHelper.hasActiveTransaction()) {
CurrentTransactionData.addTransactionEndListener(new TransactionEndListener() {
@Override
protected void onTransactionEnd(final boolean commit) {
doLogout(sessionId);
}
});
} else {
// Logout directly (this is in another TX)
doLogout(sessionId);
}
}
@Inject
public void setAccessService(final AccessService accessService) {
this.accessService = accessService;
}
@Inject
public void setApplicationService(final ApplicationService applicationService) {
this.applicationService = applicationService;
}
@Inject
public void setSettingsService(final SettingsService settingsService) {
this.settingsService = settingsService;
}
@Inject
public void setTransactionHelper(final TransactionHelper transactionHelper) {
this.transactionHelper = transactionHelper;
}
private void doLogout(final String sessionId) {
transactionHelper.runInNewTransaction(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(final TransactionStatus status) {
try {
accessService.logout(sessionId);
} catch (final NotConnectedException e) {
// Ok, just ignore
} catch (final RuntimeException e) {
LOG.warn("Error logging out member on session destroy", e);
status.setRollbackOnly();
}
}
});
}
/**
* Run a single initialization inside a transaction if it is required
*/
private void run(final LocalWebInitialization initialization) {
LOG.debug(String.format("Running web initialization (%s)...", initialization.getName()));
transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(final TransactionStatus status) {
try {
initialization.initialize();
} catch (final RuntimeException e) {
LOG.error(String.format("Error running web initialization: %s", initialization.getName()), e);
throw e;
}
}
});
}
/**
* Run all given initializations / finalizations
*/
private void runAll(final Collection<LocalWebInitialization> initializations) {
try {
for (final LocalWebInitialization initialization : initializations) {
run(initialization);
}
} finally {
CurrentTransactionData.cleanup();
}
}
}