/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.soa.esb.listeners.config;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.xml.transform.stream.StreamSource;
import org.apache.log4j.Logger;
import org.jboss.soa.esb.lifecycle.LifecycleResourceManager;
import org.jboss.soa.esb.listeners.LifecycleUtil;
import org.jboss.soa.esb.listeners.config.model.ModelAdapter;
import org.jboss.soa.esb.listeners.config.model.ModelParser;
import org.jboss.soa.esb.listeners.lifecycle.ManagedLifecycle;
import org.jboss.soa.esb.listeners.lifecycle.ManagedLifecycleController;
import org.jboss.soa.esb.listeners.lifecycle.ManagedLifecycleException;
import org.jboss.soa.esb.parameters.ParamRepositoryFactory;
import org.jboss.soa.esb.parameters.ParamRepositoryException;
import org.jboss.soa.esb.util.ClassUtil;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* The controller monitors changes in the jbossesb configuration file call jbossesb.xml. When
* changes are detected it validates the XML, and when it passes validation it goes on and
* generates the jbossesb-listener.xml and jbossesb-gateway.xml.
*
* @author kstam
*
*/
public class ConfigurationController implements Runnable
{
private static final int SLEEP_MILLIS = 1000; // default interval between parameter reloads
private static final int ENDED_MILLIS = 20000; // default interval between parameter reloads
private static final String JBOSSESB_XSD = "/jbossesb-1.0.1.xsd";
private static final Logger mLogger = Logger.getLogger(ConfigurationController.class);
private final Lock endedLock = new ReentrantLock() ;
private final Condition endedChanged = endedLock.newCondition() ;
private final Condition endRequested = endedLock.newCondition() ;
private final String mConfigFileName;
private final String mConfigName;
private final String mValidationFileName;
private final File mConfigDirectory;
private final String mListenerConfigFile;
private final String mGatewayConfigFile;
private long mPreviousFileTimestamp;
private boolean mIsEndRequested;
private boolean ended;
private ManagedLifecycleController controller ;
/**
* Start the Controller externally.
* @param args - arg[0] - the parameter file name
*/
public static void main(String[] args)
{
ConfigurationController configurationController = new ConfigurationController(args[0],null);
configurationController.run();
}
/**
* Construct a Configuration Manager from the named repository based
* configuration. The default jbossesb-1.0.1.xsd will be used for validation.
*
* @param configFileName - Name of the configuration repository.
*
*/
public ConfigurationController(String configFileName)
{
this(configFileName, null);
}
/**
* Construct a Configuration Manager from the named repository based
* configuration.
*
* @param configFileName - Name of the configuration repository.
* @param validationFileName - Name of the file name used for validation (xsd or dtd).
*
*/
public ConfigurationController(String configFileName, String validationFileName)
{
mConfigFileName = configFileName;
File configFile = new File(configFileName);
final String absolute = configFile.getAbsoluteFile().toURI().getPath() ;
final int separator = absolute.indexOf(':') ;
mConfigName = (separator >= 0 ? absolute.substring(separator+1) : absolute) ;
File parent = configFile.getParentFile();
mConfigDirectory = (null!=parent) ? parent : new File("");
final File listenerConfigFile = new File(mConfigDirectory, Generator.ESB_CONFIG_XML_FILE) ;
mListenerConfigFile = listenerConfigFile.getAbsolutePath() ;
final File gatewayConfigFile = new File(mConfigDirectory, Generator.ESB_CONFIG_GATEWAY_XML_FILE) ;
mGatewayConfigFile = gatewayConfigFile.getAbsolutePath() ;
//Try to obtain a handle to the validation file (xsd)
if (validationFileName==null) {
mValidationFileName=JBOSSESB_XSD;
} else {
mValidationFileName = validationFileName;
}
processConfiguration() ;
}
protected String getListenerConfigFile() {
return mListenerConfigFile;
}
protected String getGatewayConfigFile() {
return mGatewayConfigFile;
}
/**
* Thread that observes the configuration (file). If the configuration is updated it is
* validated and new set jbossesb-listener.xml and jbossesb-gateway.xml is created for the
* current server.
*/
public void run()
{
mLogger.info("Configuration Controller instance started.");
try {
if (mConfigFileName!=null) {
do {
processConfiguration() ;
} while(!waitForRequestedEnd(SLEEP_MILLIS)) ;
stopController() ;
} else {
mLogger.fatal("The name of the configuran file was null: " + mConfigFileName);
}
} finally {
LifecycleResourceManager.getSingleton().cleanupAllResources() ;
mLogger.info("Exiting Config Controller...");
setEnded(true) ;
}
}
/**
* Has the controller instance ended.
* @return True if this controller instance has ended, otherwise false..
*/
public boolean hasEnded() {
endedLock.lock() ;
try
{
return ended ;
}
finally
{
endedLock.unlock() ;
}
}
/**
* Set the ended flag.
* @param ended The value of the ended flag.
*/
private void setEnded(final boolean ended)
{
endedLock.lock() ;
try
{
this.ended = ended ;
endedChanged.signalAll() ;
}
finally
{
endedLock.unlock() ;
}
}
/**
* Wait until the ended flag has been set.
* @return true if the ended flag has been set, false otherwise
*/
public boolean waitUntilEnded()
{
return waitUntilEnded(ENDED_MILLIS) ;
}
/**
* Wait until the ended flag has been set.
* @param maxDelay The maximum time to wait for the flag to be set.
* @return true if the ended flag has been set, false otherwise
*/
public boolean waitUntilEnded(final long maxDelay)
{
final long endTime = System.currentTimeMillis() + maxDelay ;
endedLock.lock() ;
try
{
try
{
while (!ended)
{
final long delay = endTime - System.currentTimeMillis() ;
if ((delay <= 0) || !endedChanged.await(delay, TimeUnit.MILLISECONDS))
{
break ;
}
}
}
catch (final InterruptedException ie) {} // ignore
return ended ;
}
finally
{
endedLock.unlock() ;
}
}
/**
* Wait for a request to end.
* @param maxDelay The maximum time to wait in milliseconds.
*/
public boolean waitForRequestedEnd(final long maxDelay)
{
final long endTime = System.currentTimeMillis() + maxDelay ;
endedLock.lock() ;
try
{
try
{
while (!mIsEndRequested)
{
final long delay = endTime - System.currentTimeMillis() ;
if ((delay <= 0) || !endRequested.await(delay, TimeUnit.MILLISECONDS))
{
break ;
}
}
}
catch (final InterruptedException ie) {} // ignore
return mIsEndRequested ;
}
finally
{
endedLock.unlock() ;
}
}
/**
* To request the end of processing.
*/
public void requestEnd()
{
endedLock.lock() ;
try
{
mIsEndRequested = true;
endRequested.signalAll() ;
}
finally
{
endedLock.unlock() ;
}
}
/**
* Process the configuration.
*/
private void processConfiguration()
{
if (isReloadNeeded()) {
try {
StreamSource validationInputSource=null;
InputStream validationInputStream = ClassUtil.getResourceAsStream(mValidationFileName, getClass());
//if this fails try using the
if (validationInputStream==null) {
File validationFile = new File(mValidationFileName);
mLogger.debug("Validation file " + mValidationFileName + " exists?:" + validationFile.exists());
try {
validationInputStream = new FileInputStream(validationFile);
} catch (FileNotFoundException e) {
mLogger.error(e.getMessage(),e);
throw new IllegalStateException("ESB validation file [" + (new File(mValidationFileName)).getAbsolutePath() + "] not found.", e);
}
}
if (validationInputStream==null) {
mLogger.warn("Could not obtain validation file " + mValidationFileName);
} else {
mLogger.debug("Reading validation info from " + mValidationFileName);
validationInputSource = new StreamSource(validationInputStream);
}
mLogger.info("loading configuration..");
String configXml = ParamRepositoryFactory.getInstance().get(mConfigFileName);
mLogger.debug("Start validation on configXml=" + configXml);
if (ModelParser.getParser().validate(new StringReader(configXml))) {
mLogger.debug("Configuration file " + mConfigFileName + " passed validation. Starting " +
" the generation process of the jbossesb-listener.xml and the jbossesb-gateway.xml.");
Generator generator = new Generator(new ByteArrayInputStream(configXml.getBytes()));
generator.generate(mConfigDirectory);
mLogger.info("Parameter reload completed.");
stopController() ;
// TODO: Get rid of generating config files to disk. It's nuts. Use in memory stream buffers!!
controller = startController(generator.getModel());
}
} catch (Exception e) {
mLogger.error("The current versions of the jbossesb-listener.xml and/or"
+ " jbossesb-gateway.xml are kept in place until the error is resolved: "
+ e.getMessage(), e);
}
}
}
public ManagedLifecycleController startController(final ModelAdapter configModel)
throws ParamRepositoryException, SAXException, ManagedLifecycleException, ConfigurationException
{
LifecycleResourceManager.getSingleton().associateDeployment(mConfigName) ;
return startController(configModel, LifecycleUtil.getConfigTree(mListenerConfigFile), LifecycleUtil.getConfigTree(mGatewayConfigFile)) ;
}
public static ManagedLifecycleController startController(ModelAdapter configModel, ConfigTree listenerConfig, ConfigTree gatewayConfig) throws ParamRepositoryException, SAXException, ManagedLifecycleException, ConfigurationException {
final List<ManagedLifecycle> instances = LifecycleUtil.getListeners(listenerConfig);
instances.addAll(LifecycleUtil.getGateways(gatewayConfig));
ManagedLifecycleController lifecycleController = new ManagedLifecycleController(instances);
try {
lifecycleController.start();
// In parallel, create a map of the contract publication info...
ServicePublisher.addServicePublishers(lifecycleController, configModel.getServicePublishers());
} catch (final ManagedLifecycleException mle) {
lifecycleController = null;
mLogger.error("Unexpected exception starting controller", mle);
}
return lifecycleController;
}
/**
* Check the file timestamp and return true when it changes. In other
* words this only works for files for now.
*
* @return true if the file timestamp changed.
*/
private boolean isReloadNeeded()
{
File configFile = new File(mConfigFileName);
if (configFile.exists()) {
long currentFileTimestamp = configFile.lastModified();
if (mPreviousFileTimestamp==0 || currentFileTimestamp > mPreviousFileTimestamp) {
if (mLogger.isDebugEnabled()) {
mLogger.debug("The previous timestamp on the file was: " + new Date(mPreviousFileTimestamp)
+ " the new timestamp on the file is: " + new Date(currentFileTimestamp));
}
mPreviousFileTimestamp = currentFileTimestamp;
return true;
}
} else {
mLogger.error("The configuration file " + configFile + " could not be found.");
}
return false;
}
/**
* Stop the controller if it is active.
*/
private void stopController()
{
stopController(controller);
LifecycleResourceManager.getSingleton().disassociateDeployment(mConfigFileName) ;
controller = null ;
}
/**
* Stop the controller if it is active.
*/
public static void stopController(ManagedLifecycleController lifecycleController) {
if (lifecycleController != null)
{
try
{
ServicePublisher.removeServicePublishers(lifecycleController);
lifecycleController.stop();
}
catch (final ManagedLifecycleException mle)
{
mLogger.error("Unexpected exception stopping controller", mle) ;
}
}
}
}