/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* 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.deployers.plugins.scanner;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.jboss.deployers.client.spi.Deployment;
import org.jboss.deployers.client.spi.main.MainDeployer;
import org.jboss.deployers.vfs.spi.client.VFSDeploymentFactory;
import org.jboss.logging.Logger;
import org.jboss.util.StringPropertyReplacer;
import org.jboss.virtual.VFS;
import org.jboss.virtual.VirtualFile;
import org.jboss.virtual.VirtualFileFilter;
/**
* A DeploymentScanner built on the ProfileService and MainDeployer.
*
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
* @author Scott.Stark@jboss.org
* @version $Revision: 85526 $
*/
public class VFSDeploymentScannerImpl
implements Runnable
{
private static final Logger log = Logger.getLogger(VFSDeploymentScannerImpl.class);
// Private Data --------------------------------------------------
private MainDeployer mainDeployer;
/** The deployment factory */
private VFSDeploymentFactory deploymentFactory = VFSDeploymentFactory.getInstance();
/** */
private VirtualFileFilter filter;
/** The ExecutorService/ThreadPool for performing scans */
private ScheduledExecutorService scanExecutor;
private ScheduledFuture activeScan;
/** The URIfied ServerHomeURL */
private URI serverHomeURI;
/** The list of URIs to scan */
private List<URI> uriList = Collections.synchronizedList(new ArrayList<URI>());
/** The list of VirtualFiles to scan */
private List<VirtualFile> vdfList = Collections.synchronizedList(new ArrayList<VirtualFile>());
/** Whether to search for files inside directories whose names containing no dots */
private boolean doRecursiveSearch = true;
/** Period in ms between deployment scans */
private long scanPeriod = 5000;
private int scanCount;
/** A set of scanned VirtualFiles which have been deployed */
private Map<VirtualFile, DeploymentInfo> deployedMap = new ConcurrentHashMap<VirtualFile, DeploymentInfo>();
// Constructor ---------------------------------------------------
public VFSDeploymentScannerImpl()
{
// empty
}
// Attributes ----------------------------------------------------
public void setMainDeployer(MainDeployer deployer)
{
this.mainDeployer = deployer;
}
public VirtualFileFilter getFilterInstance()
{
return filter;
}
public void setFilterInstance(VirtualFileFilter filter)
{
this.filter = filter;
}
/**
* @return Returns the scanExecutor.
*/
public ScheduledExecutorService getScanExecutor()
{
return this.scanExecutor;
}
/**
* @param scanExecutor The scanExecutor to set.
*/
public void setScanExecutor(ScheduledExecutorService scanExecutor)
{
this.scanExecutor = scanExecutor;
}
/* (non-Javadoc)
* @see org.jboss.deployment.scanner.VFSDeploymentScanner#getScanPeriod()
*/
public long getScanPeriod()
{
return scanPeriod;
}
/* (non-Javadoc)
* @see org.jboss.deployment.scanner.VFSDeploymentScanner#setScanPeriod(long)
*/
public void setScanPeriod(long period)
{
this.scanPeriod = period;
}
/**
* Are deployment scans enabled.
*
* @return whether scan is enabled
*/
public boolean isScanEnabled()
{
return activeScan != null;
}
public synchronized int getScanCount()
{
return scanCount;
}
public synchronized void resetScanCount()
{
this.scanCount = 0;
}
/**
* Enable/disable deployment scans.
* @param scanEnabled true to enable scans, false to disable.
*/
public synchronized void setScanEnabled(boolean scanEnabled)
{
if( scanEnabled == true && activeScan == null )
{
activeScan = this.scanExecutor.scheduleWithFixedDelay(this, 0,
scanPeriod, TimeUnit.MILLISECONDS);
}
else if( scanEnabled == false && activeScan != null )
{
activeScan.cancel(true);
activeScan = null;
}
}
/**
* Set the urls to scan
*
* @param listspec the urls
* @throws URISyntaxException
* @throws IOException
*/
public void setURIs(final String listspec) throws URISyntaxException, IOException
{
if (listspec == null)
{
this.uriList.clear();
return;
}
List<URI> list = new LinkedList<URI>();
StringTokenizer stok = new StringTokenizer(listspec, ",");
while (stok.hasMoreTokens())
{
String urispec = stok.nextToken().trim();
log.debug("Adding URI from spec: " + urispec);
URI uri = makeURI(urispec);
log.debug("URI: " + uri);
list.add(uri);
}
setURIList(list);
}
/**
* Set uris to scan
*
* @param list the urls to scan
* @throws IOException
*/
public void setURIList(final List<URI> list) throws IOException
{
if (list == null)
{
return;
}
// start out with a fresh list
uriList.clear();
for(int n = 0; n < list.size(); n ++)
{
URI uri = list.get(n);
if (uri == null)
{
throw new IllegalArgumentException("list element["+n+"] is null");
}
addURI(uri);
}
log.debug("URI list: " + uriList);
}
public List<URI> getURIList()
{
return new ArrayList<URI>(uriList);
}
public void setRecursiveSearch(boolean recurse)
{
doRecursiveSearch = recurse;
}
public boolean getRecursiveSearch()
{
return doRecursiveSearch;
}
// Operations ----------------------------------------------------
public void addURI(final URI uri) throws IOException
{
if (uri == null)
{
throw new NullPointerException("uri argument cannot be null");
}
if( uriList.add(uri) == true )
{
log.debug("Added URI: " + uri);
VirtualFile vf = VFS.getRoot(uri);
vdfList.add(vf);
}
}
public void removeURI(final URI uri)
throws IOException
{
if (uri == null)
{
throw new NullPointerException("uri argument cannot be null");
}
VirtualFile vf = VFS.getRoot(uri);
vdfList.remove(vf);
boolean success = uriList.remove(uri);
if (success)
{
log.debug("Removed URI: " + uri);
}
}
public boolean hasURI(final URI uri)
{
if (uri == null)
{
throw new NullPointerException("uri argument cannot be null");
}
return uriList.contains(uri);
}
public void start() throws Exception
{
// synchronize uriList and vdfList because only at this point
// setVirtualFileFactory() injection has been performed
vdfList.clear();
for (URI uri : uriList)
{
VirtualFile vf = VFS.getRoot(uri);
vdfList.add(vf);
}
// Default to a single thread executor
if( scanExecutor == null )
{
scanExecutor = Executors.newSingleThreadScheduledExecutor(
new ThreadFactory()
{
public Thread newThread(Runnable r)
{
return new Thread(r, "VFSDeploymentScanner");
}
}
);
}
activeScan = scanExecutor.scheduleWithFixedDelay(this, 0,
scanPeriod, TimeUnit.MILLISECONDS);
}
/**
* Executes scan
*
*/
public void run()
{
try
{
scan();
}
catch(Throwable e)
{
log.warn("Scan failed", e);
}
finally
{
incScanCount();
}
}
public void stop()
{
if( activeScan != null )
{
activeScan.cancel(true);
activeScan = null;
}
}
public synchronized void scan() throws Exception
{
if (vdfList == null)
{
throw new IllegalStateException("not initialized");
}
boolean trace = log.isTraceEnabled();
// Scan for deployments
log.debug("Begin deployment scan");
// VirtualFiles to deploy
List<VirtualFile> toDeployList = new LinkedList<VirtualFile>();
synchronized (vdfList)
{
for (VirtualFile vf : vdfList)
{
if( trace )
log.trace("Checking file: "+vf);
if (vf.isLeaf())
{
// treat this as a deployable unit
toDeployList.add(vf);
}
else
{
// process (possibly recursively) the dir
addDeployments(toDeployList, vf);
}
}
}
if (trace)
{
log.trace("toDeployList: "+toDeployList);
}
LinkedList<VirtualFile> toRemoveList = new LinkedList<VirtualFile>();
LinkedList<VirtualFile> toCheckForUpdateList = new LinkedList<VirtualFile>();
synchronized (deployedMap)
{
// remove previously deployed URLs no longer needed
Iterator<VirtualFile> iter = deployedMap.keySet().iterator();
while( iter.hasNext() )
{
VirtualFile vf = iter.next();
if (toDeployList.contains(vf))
{
toCheckForUpdateList.add(vf);
}
else
{
toRemoveList.add(vf);
}
}
}
// ********
// Undeploy
// ********
for (VirtualFile vf : toRemoveList)
{
undeploy(vf);
}
// ********
// Redeploy
// ********
// compute the DeployedURL list to update
ArrayList<VirtualFile> toUpdateList = new ArrayList<VirtualFile>(toCheckForUpdateList.size());
for (VirtualFile vf : toUpdateList)
{
DeploymentInfo info = deployedMap.get(vf);
long modified = vf.getLastModified();
Long prevLastDeployed = info.lastModified;;
if (prevLastDeployed.compareTo(modified) < 0)
{
info.lastModified = modified;
if (trace)
{
log.trace("Re-deploying " + vf);
}
toUpdateList.add(vf);
}
}
// sort to update list
//Collections.sort(toUpdateList, sorter);
// Undeploy in order
for (int i = toUpdateList.size() - 1; i >= 0; i--)
{
VirtualFile vf = toUpdateList.get(i);
undeploy(vf);
}
// Deploy in order
for (int i = 0; i < toUpdateList.size(); i++)
{
VirtualFile vf = toUpdateList.get(i);
deploy(vf);
}
// ******
// Deploy
// ******
//Collections.sort(toDeployList, sorter);
for (Iterator i = toDeployList.iterator(); i.hasNext();)
{
VirtualFile vf = (VirtualFile)i.next();
// if vf is not deployed already, deploy it
if (!deployedMap.containsKey(vf))
{
deploy(vf);
}
// vf must have been deployed by now, so remove it from list
i.remove();
}
log.debug("End deployment scan");
}
/**
* Inc the scanCount and to a notifyAll.
*
*/
protected synchronized void incScanCount()
{
scanCount ++;
notifyAll();
}
// Private -------------------------------------------------------
/**
* A helper to make a URI from a full/partial urispec
*/
private URI makeURI(String urispec) throws URISyntaxException
{
// First replace URI with appropriate properties
urispec = StringPropertyReplacer.replaceProperties(urispec);
return serverHomeURI.resolve(urispec);
}
/**
* A helper to find all deployments under a directory vf
* and add them to the supplied list.
*
* We may recurse.
*/
private void addDeployments(List<VirtualFile> list, VirtualFile root)
throws IOException
{
List<VirtualFile> components = root.getChildren();
for (VirtualFile vf : components)
{
if (vf.isLeaf())
{
// the first arg in filter.accept is not used!
if (filter == null || filter.accepts(vf))
{
list.add(vf);
}
}
else
{
if (vf.getName().indexOf('.') == -1 && this.doRecursiveSearch)
{
// recurse if not '.' in name and recursive search is enabled
addDeployments(list, vf);
}
else
{
list.add(vf);
}
}
}
}
/**
* A helper to deploy the given vf using the deployer.
*/
private void deploy(final VirtualFile vf)
{
// If the deployer is null simply ignore the request
log.debug("Deploying: " + vf);
Deployment deployment = deploymentFactory.createVFSDeployment(vf);
try
{
mainDeployer.addDeployment(deployment);
mainDeployer.process();
}
catch (Exception e)
{
log.warn("Failed to deploy: " + vf, e);
// TODO: somehow need to ignore bad deployments to avoid repeated errors
return;
}
/* TODO: this differs from the previous behavior. We would need a type to metainf location
if we want to watch the same file as jboss4. But since not all files have a deployment
descriptor, we need to be able to watch the deployment root anyway.
*/
try
{
DeploymentInfo info = new DeploymentInfo(deployment, vf.getLastModified());
if (!deployedMap.containsKey(vf))
{
deployedMap.put(vf, info);
}
}
catch(IOException e)
{
log.warn("Failed to obtain lastModified for: "+vf, e);
}
}
/**
* A helper to undeploy the given vf using the deployer.
*/
private void undeploy(final VirtualFile vf)
{
try
{
log.debug("Undeploying: " + vf);
DeploymentInfo info = deployedMap.remove(vf);
mainDeployer.removeDeployment(info.deployment);
}
catch (Exception e)
{
log.error("Failed to undeploy: " + vf, e);
}
}
/**
* DeploymentInfo.
*/
private class DeploymentInfo
{
/** The deployment */
Deployment deployment;
/** The last modified time */
long lastModified;
/**
* Create a new DeploymentInfo.
*
* @param deployment the deployment
* @param lastModified the last modified
*/
public DeploymentInfo(Deployment deployment, long lastModified)
{
if (deployment == null)
throw new IllegalArgumentException("Null deployment");
this.deployment = deployment;
this.lastModified = lastModified;
}
}
}