/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program 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 Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1986 $ by $Author: glycoslave $ on $Date:: 2010-09-08 #$
*/
package org.eurocarbdb.servlet.init;
// stdlib imports
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
// 3rd party imports
import org.apache.log4j.Logger;
import org.apache.commons.configuration.ConfigurationUtils;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.CompositeConfiguration;
import com.opensymphony.xwork.ActionProxyFactory;
import com.opensymphony.xwork.config.entities.ActionConfig;
import com.opensymphony.webwork.sitegraph.XWorkConfigRetriever;
// eurocarb imports
import org.eurocarbdb.tranche.TrancheAdmin;
import org.eurocarbdb.dataaccess.Eurocarb;
import org.eurocarbdb.dataaccess.EntityManager;
import org.eurocarbdb.dataaccess.HibernateEntityManager;
import org.eurocarbdb.dataaccess.core.Contributor;
import org.eurocarbdb.dataaccess.core.GlycanSequence;
import org.eurocarbdb.dataaccess.core.BiologicalContext;
import org.eurocarbdb.dataaccess.core.Reference;
import org.eurocarbdb.dataaccess.core.Evidence;
// static imports
import static org.eurocarbdb.util.StringUtils.join;
import static org.eurocarbdb.util.StringUtils.CR;
/**
* This class is called when the Eurocarbdb web app context is started
* or stopped. On startup, it is responsible for the following:
*<ol>
* <li>Loading the eurocarbdb application properties file</li>
* <li>Starting the Tranche server (global file system),
* if configured to do so ('ecdb.tranche.enabled' property)</li>
* <li>Initialising Hibernate, and ensuring that the database
* is receiving connections and can be queried</li>
* <li>Checking the current Action configuration is sane</li>
*</ol>
*
* @author mjh
* @version $Rev: 1986 $
*/
public class EurocarbApplicationContextHandler implements ServletContextListener
{
//~~~~~~~~~~~~~~~~~~~~~~ STATIC FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~//
/** Logging handle. */
static final Logger log = Logger.getLogger( EurocarbApplicationContextHandler.class );
/** Default name of app config file */
public static final String ECDB_APP_PROPERTIES_FILE = "eurocarbdb-application.properties";
//~~~~~~~~~~~~~~~~~~~~~~~~~~ FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~//
/**
* Called when the servlet context is started.
*
* @see ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
public void contextInitialized( ServletContextEvent arg0 )
{
initConfig();
startTrancheIfNeeded();
checkDataStore();
checkActions();
}
/* (non-Javadoc)
* @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent arg0)
{
stopTrancheIfNeeded();
}
//~~~~~~~~~~~~~~~~~~~~~~~~ PRIVATE METHODS ~~~~~~~~~~~~~~~~~~~~//
/**
* Iterates through the list of all currently known actions
* and checks that the action class is loadable.
*/
protected void checkActions()
{
log.debug("checking actions...");
StringBuilder sb = new StringBuilder();
// get current webwork config
Set<String> namespaces = (Set<String>) XWorkConfigRetriever.getNamespaces();
Set<String> action_names;
for ( String ns : namespaces )
{
action_names = (Set<String>) XWorkConfigRetriever.getActionNames( ns );
sb.append(
" namespace "
+ (ns.length() > 0 ? ns : '/' )
+ " has "
+ action_names.size()
+ " actions\n"
);
List<String> sorted_action_names = new ArrayList( action_names );
Collections.sort( sorted_action_names );
ActionConfig ac;
Set<String> result_names;
for ( String action_name : sorted_action_names )
{
ac = XWorkConfigRetriever.getActionConfig( ns, action_name );
String class_name = ac.getClassName();
// loading of action classes is somewhat redundant since it
// appears to have already been done in first call to XWorkConfigRetriever
try { Class.forName( class_name ); }
catch ( ClassNotFoundException ex )
{
log.warn(
"Couldn't load action class for action '"
+ action_name
+ "': "
, ex
);
}
/*
result_names = (Set<String>) ac.getResults().keySet();
sb.append(
" "
+ action_name
+ " ("
+ join( ", ", result_names )
+ ")\n"
);
*/
}
}
log.info(
"action config OK:\n"
+ sb
);
return;
}
/**
* Checks that it's possible to connect to the (default) data
* store, start a transaction, and perform a basic query.
*/
protected void checkDataStore()
{
log.debug("checking data store exists and is queryable...");
try
{
EntityManager em = Eurocarb.getEntityManager();
em.beginUnitOfWork();
turn_genetic_query_optimiser_off();
int count_cb = em.countAll( Contributor.class );
int count_gs = em.countAll( GlycanSequence.class );
int count_bc = em.countAll( BiologicalContext.class );
int count_rf = em.countAll( Reference.class );
int count_ev = em.countAll( Evidence.class );
if ( em.getQuery( Contributor.class.getName() + ".ACTIVE_ADMINISTRATORS" ).list().size() == 0 )
{
throw new Exception(
"There are not active administrator users.\nCowardly refusing to start webapp\n");
}
em.endUnitOfWork();
log.info(
join( "\n "
, "data store OK; default data store has:"
, count_cb + " contributor(s)"
, count_gs + " glycan sequence(s)"
, count_bc + " biological context(s)"
, count_rf + " reference(s)"
, count_ev + " evidence"
)
);
}
catch ( org.hibernate.MappingNotFoundException ex )
{
log.fatal(
"Could not query the data store: " + ex
+ "-- did you perhaps forget to add a new mapping "
+ "file to the hibernate.cfg.xml?"
);
throw new RuntimeException( ex );
}
catch ( Exception ex )
{
log.fatal("Could not query the data store: " + ex );
throw new RuntimeException( ex );
}
}
/**
* Effectively turn postgres genetic query optimiser off.
* This is *critical* for good performance of substructure queries.
* This psql option can also be set via psql's own configuration
* file but we set it here to make sure.
*/
private void turn_genetic_query_optimiser_off()
{
int max = org.eurocarbdb.dataaccess.core.seq.SubstructureQuery.MAX_SUBSTRUCTURE_RESIDUES;
try
{
log.debug("setting genetic query optimiser threshold to " + max );
((HibernateEntityManager) Eurocarb.getEntityManager())
.getHibernateSession()
.createSQLQuery( "set geqo_threshold = " + max )
.executeUpdate();
}
catch ( Exception ex )
{
log.warn("Caught exception while turning off genetic query optimiser", ex );
}
}
protected void startTrancheIfNeeded()
{
if ( Eurocarb.getConfiguration().getBoolean("ecdb.tranche.enabled") )
{
log.info("starting the Tranche server (set ecdb.tranche.enabled=false to disable)");
TrancheAdmin.StartServer();
}
else log.debug("Tranche not started (ecdb.tranche.enabled=false)");
}
protected void stopTrancheIfNeeded()
{
if ( Eurocarb.getConfiguration().getBoolean("ecdb.tranche.enabled"))
{
log.info("shutting down the Tranche server");
TrancheAdmin.StopServer();
}
}
/**
* Loads the main application properties file, given by {@link ECDB_APP_PROPERTIES_FILE}.
* Note that core-api properties take precedence over application properties;
* ideally there shouldn't be any property name collisions in the first place.
*/
protected void initConfig()
{
// need to call getConfiguration before adding our config so
// that core-api config gets initialised and logged first.
CompositeConfiguration config = Eurocarb.getConfiguration();
log.info("adding eurocarbdb application configuration: " + ECDB_APP_PROPERTIES_FILE );
try
{
config.addConfiguration(
new PropertiesConfiguration( ECDB_APP_PROPERTIES_FILE ) );
}
catch ( ConfigurationException ex )
{
throw new RuntimeException( ex );
}
if ( log.isInfoEnabled() )
{
log.info(
"Configured eurocarb-application properties:\n"
+ CR
+ ConfigurationUtils.toString( config )
);
}
}
} // end class