/***************************************
* *
* JBoss: The OpenSource J2EE WebOS *
* *
* Distributable under LGPL license. *
* See terms of license at gnu.org. *
* *
***************************************/
package org.jboss.deployment.scanner;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.jboss.deployment.IncompleteDeploymentException;
import org.jboss.net.protocol.URLLister;
import org.jboss.net.protocol.URLListerFactory;
import org.jboss.system.server.ServerConfigLocator;
import org.jboss.system.server.ServerConfig;
import org.jboss.util.NullArgumentException;
/**
* A URL-based deployment scanner. Supports local directory
* scanning for file-based urls.
*
* @jmx:mbean extends="org.jboss.deployment.scanner.DeploymentScannerMBean"
*
* @version $Revision: 1.20.2.9 $
* @author Jason Dillon
*/
public class URLDeploymentScanner
extends AbstractDeploymentScanner
implements DeploymentScanner, URLDeploymentScannerMBean
{
/** The list of URLs to scan. */
protected List urlList = Collections.synchronizedList(new ArrayList());
/** A set of scanned urls which have been deployed. */
protected Set deployedSet = Collections.synchronizedSet(new HashSet());
/** The server's home directory, for relative paths. */
protected File serverHome;
protected URL serverHomeURL;
/** A sorter urls from a scaned directory to allow for coarse dependency
ordering based on file type
*/
protected Comparator sorter;
/** Allow a filter for scanned directories */
protected URLLister.URLFilter filter;
protected IncompleteDeploymentException lastIncompleteDeploymentException;
// indicates if we should directly search for files inside directories
// whose names containing no dots
//
protected boolean doRecursiveSearch = true;
/**
* @jmx:managed-attribute
*/
public void setRecursiveSearch (boolean recurse)
{
doRecursiveSearch = recurse;
}
/**
* @jmx:managed-attribute
*/
public boolean getRecursiveSearch ()
{
return doRecursiveSearch;
}
/**
* @jmx:managed-attribute
*/
public void setURLList(final List list)
{
if (list == null)
throw new NullArgumentException("list");
boolean debug = log.isDebugEnabled();
// start out with a fresh list
urlList.clear();
Iterator iter = list.iterator();
while (iter.hasNext())
{
URL url = (URL)iter.next();
if (url == null)
throw new NullArgumentException("list element");
addURL(url);
}
if (debug)
{
log.debug("URL list: " + urlList);
}
}
/**
* @jmx:managed-attribute
*
* @param classname The name of a Comparator class.
*/
public void setURLComparator(String classname)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException
{
sorter = (Comparator)Thread.currentThread().getContextClassLoader().loadClass(classname).newInstance();
}
/**
* @jmx:managed-attribute
*/
public String getURLComparator()
{
if (sorter == null)
return null;
return sorter.getClass().getName();
}
/**
* @jmx:managed-attribute
*
* @param classname The name of a FileFilter class.
*/
public void setFilter(String classname)
throws ClassNotFoundException, IllegalAccessException, InstantiationException
{
Class filterClass = Thread.currentThread().getContextClassLoader().loadClass(classname);
filter = (URLLister.URLFilter) filterClass.newInstance();
}
/**
* @jmx:managed-attribute
*/
public String getFilter()
{
if (filter == null)
return null;
return filter.getClass().getName();
}
/**
* @jmx:managed-attribute
*/
public List getURLList()
{
// too bad, List isn't a cloneable
return new ArrayList(urlList);
}
/**
* @jmx:managed-operation
*/
public void addURL(final URL url)
{
if (url == null)
throw new NullArgumentException("url");
urlList.add(url);
if (log.isDebugEnabled())
{
log.debug("Added url: " + url);
}
}
/**
* @jmx:managed-operation
*/
public void removeURL(final URL url)
{
if (url == null)
throw new NullArgumentException("url");
boolean success = urlList.remove(url);
if (success && log.isDebugEnabled())
{
log.debug("Removed url: " + url);
}
}
/**
* @jmx:managed-operation
*/
public boolean hasURL(final URL url)
{
if (url == null)
throw new NullArgumentException("url");
return urlList.contains(url);
}
/////////////////////////////////////////////////////////////////////////
// Management/Configuration Helpers //
/////////////////////////////////////////////////////////////////////////
/**
* @jmx:managed-attribute
*/
public void setURLs(final String listspec) throws MalformedURLException
{
if (listspec == null)
throw new NullArgumentException("listspec");
boolean debug = log.isDebugEnabled();
List list = new LinkedList();
StringTokenizer stok = new StringTokenizer(listspec, ",");
while (stok.hasMoreTokens())
{
String urlspec = stok.nextToken().trim();
if (debug)
{
log.debug("Adding URL from spec: " + urlspec);
}
URL url = makeURL(urlspec);
if (debug)
{
log.debug("URL: " + url);
}
list.add(url);
}
setURLList(list);
}
/**
* A helper to make a URL from a full url, or a filespec.
*/
protected URL makeURL(String urlspec) throws MalformedURLException
{
// First replace URL with appropriate properties
//
urlspec = org.jboss.util.Strings.replaceProperties (urlspec);
return new URL(serverHomeURL, urlspec);
}
/**
* @jmx:managed-operation
*/
public void addURL(final String urlspec) throws MalformedURLException
{
addURL(makeURL(urlspec));
}
/**
* @jmx:managed-operation
*/
public void removeURL(final String urlspec) throws MalformedURLException
{
removeURL(makeURL(urlspec));
}
/**
* @jmx:managed-operation
*/
public boolean hasURL(final String urlspec) throws MalformedURLException
{
return hasURL(makeURL(urlspec));
}
/**
* A helper to deploy the given URL with the deployer.
*/
protected void deploy(final DeployedURL du)
{
// If the deployer is null simply ignore the request
if( deployer == null )
return;
if (log.isTraceEnabled())
{
log.trace("Deploying: " + du);
}
try
{
deployer.deploy(du.url);
}
catch (IncompleteDeploymentException e)
{
lastIncompleteDeploymentException = e;
}
catch (Exception e)
{
log.debug("Failed to deploy: " + du, e);
} // end of try-catch
du.deployed();
if (!deployedSet.contains(du))
{
deployedSet.add(du);
}
}
/**
* A helper to undeploy the given URL from the deployer.
*/
protected void undeploy(final DeployedURL du)
{
try
{
if (log.isTraceEnabled())
{
log.trace("Undeploying: " + du);
}
deployer.undeploy(du.url);
deployedSet.remove(du);
}
catch (Exception e)
{
log.error("Failed to undeploy: " + du, e);
}
}
/**
* Checks if the url is in the deployed set.
*/
protected boolean isDeployed(final URL url)
{
DeployedURL du = new DeployedURL(url);
return deployedSet.contains(du);
}
public synchronized void scan() throws Exception
{
lastIncompleteDeploymentException = null;
if (urlList == null)
throw new IllegalStateException("not initialized");
boolean trace = log.isTraceEnabled();
URLListerFactory factory = new URLListerFactory();
List urlsToDeploy = new LinkedList();
// Scan for deployments
if (trace)
{
log.trace("Scanning for new deployments");
}
synchronized (urlList)
{
for (Iterator i = urlList.iterator(); i.hasNext();)
{
URL url = (URL) i.next();
if (url.toString().endsWith("/"))
{
// treat URL as a collection
URLLister lister = factory.createURLLister(url);
urlsToDeploy.addAll(lister.listMembers(url, filter, doRecursiveSearch));
}
else
{
// treat URL as a deployable unit
urlsToDeploy.add(url);
}
}
}
if (trace)
{
log.trace("Updating existing deployments");
}
LinkedList urlsToRemove = new LinkedList();
LinkedList urlsToCheckForUpdate = new LinkedList();
synchronized (deployedSet)
{
// remove previously deployed URLs no longer needed
for (Iterator i = deployedSet.iterator(); i.hasNext();)
{
DeployedURL deployedURL = (DeployedURL) i.next();
if (urlsToDeploy.contains(deployedURL.url))
{
urlsToCheckForUpdate.add(deployedURL);
}
else
{
urlsToRemove.add(deployedURL);
}
}
}
// ********
// Undeploy
// ********
for (Iterator i = urlsToRemove.iterator(); i.hasNext();)
{
DeployedURL deployedURL = (DeployedURL) i.next();
if (trace)
{
log.trace("Removing " + deployedURL.url);
}
undeploy(deployedURL);
}
// ********
// Redeploy
// ********
// compute the DeployedURL list to update
ArrayList urlsToUpdate = new ArrayList(urlsToCheckForUpdate.size());
for (Iterator i = urlsToCheckForUpdate.iterator(); i.hasNext();)
{
DeployedURL deployedURL = (DeployedURL) i.next();
if (deployedURL.isModified())
{
if (trace)
{
log.trace("Re-deploying " + deployedURL.url);
}
urlsToUpdate.add(deployedURL);
}
}
// sort to update list
Collections.sort(urlsToUpdate, new Comparator()
{
public int compare(Object o1, Object o2)
{
return sorter.compare(((DeployedURL) o1).url, ((DeployedURL) o2).url);
}
});
// Undeploy in order
for (int i = urlsToUpdate.size() - 1; i >= 0;i--)
{
undeploy((DeployedURL) urlsToUpdate.get(i));
}
// Deploy in order
for (int i = 0; i < urlsToUpdate.size();i++)
{
deploy((DeployedURL) urlsToUpdate.get(i));
}
// ******
// Deploy
// ******
Collections.sort(urlsToDeploy, sorter);
for (Iterator i = urlsToDeploy.iterator(); i.hasNext();)
{
URL url = (URL) i.next();
DeployedURL deployedURL = new DeployedURL(url);
if (deployedSet.contains(deployedURL) == false)
{
if (trace)
{
log.trace("Deploying " + deployedURL.url);
}
deploy(deployedURL);
}
}
// Validate that there are still incomplete deployments
if (lastIncompleteDeploymentException != null)
{
try
{
Object[] args = {};
String[] sig = {};
getServer().invoke(getDeployer(),
"checkIncompleteDeployments", args, sig);
}
catch (Exception e)
{
log.error(e);
}
}
}
/////////////////////////////////////////////////////////////////////////
// Service/ServiceMBeanSupport //
/////////////////////////////////////////////////////////////////////////
public ObjectName preRegister(MBeanServer server, ObjectName name)
throws Exception
{
// get server's home for relative paths, need this for setting
// attribute final values, so we need to do it here
ServerConfig serverConfig = ServerConfigLocator.locate();
serverHome = serverConfig.getServerHomeDir();
serverHomeURL = serverConfig.getServerHomeURL();
return super.preRegister(server, name);
}
/////////////////////////////////////////////////////////////////////////
// DeployedURL //
/////////////////////////////////////////////////////////////////////////
/**
* A container and help class for a deployed URL.
* should be static at this point, with the explicit scanner ref, but I'm (David) lazy.
*/
protected class DeployedURL
{
public URL url;
public URL watchUrl;
public long deployedLastModified;
public DeployedURL(final URL url)
{
this.url = url;
}
public void deployed()
{
deployedLastModified = getLastModified();
}
public boolean isFile()
{
return url.getProtocol().equals("file");
}
public File getFile()
{
return new File(url.getFile());
}
public boolean isRemoved()
{
if (isFile())
{
File file = getFile();
return !file.exists();
}
return false;
}
public long getLastModified()
{
if (watchUrl == null)
{
//
// jason: getWatchUrl() is not part of Deployer interface.. wtf is this?
//
try
{
Object o = getServer().invoke(getDeployer(), "getWatchUrl",
new Object[] { url },
new String[] { URL.class.getName() });
watchUrl = o == null ? url : (URL)o;
getLog().debug("Watch URL for: " + url + " -> " + watchUrl);
}
catch (Exception e)
{
watchUrl = url;
getLog().debug("Unable to obtain watchUrl from deployer. Use url: " + url, e);
}
}
try
{
URLConnection connection;
if (watchUrl != null)
{
connection = watchUrl.openConnection();
} else
{
connection = url.openConnection();
}
// no need to do special checks for files...
// org.jboss.net.protocol.file.FileURLConnection correctly
// implements the getLastModified method.
long lastModified = connection.getLastModified();
return lastModified;
}
catch (java.io.IOException e)
{
log.warn("Failed to check modfication of deployed url: " + url, e);
}
return -1;
}
public boolean isModified()
{
long lastModified = getLastModified();
if (lastModified == -1) {
// ignore errors fetching the timestamp - see bug 598335
return false;
}
return deployedLastModified != lastModified;
}
public int hashCode()
{
return url.hashCode();
}
public boolean equals(final Object other)
{
if (other instanceof DeployedURL)
{
return ((DeployedURL)other).url.equals(this.url);
}
return false;
}
public String toString()
{
return super.toString() +
"{ url=" + url +
", deployedLastModified=" + deployedLastModified +
" }";
}
}
}