/**
* Copyright 2011 meltmedia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xchain.framework.scanner;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.net.URL;
import org.xchain.framework.lifecycle.LifecycleClass;
import org.xchain.framework.lifecycle.LifecycleAccessor;
import org.apache.commons.collections.map.LRUMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The lifecycle scanner provides utilities for scanning the class loader of an xcahins application.
*
* @author Christian Trimble
* @author Josh Kennedy
* @author John Trimble
*/
@LifecycleClass(uri="http://www.xchain.org/scanner")
public class ScannerLifecycle
{
/*
* Flag to turn on/off memoization of ScannerLifecycle.scanNode().
*/
private static final boolean MEMOIZE_SCAN_NODE = true;
private static Logger log = LoggerFactory.getLogger(ScannerLifecycle.class);
private static ScannerLifecycle instance = new ScannerLifecycle();
private Map<ClassLoader, Map<RootUrlLocator, ScanNode>> cache;
@LifecycleAccessor
public static ScannerLifecycle getInstance()
{
return instance;
}
private Map<String, ProtocolScanner> protocolMap = new HashMap<String, ProtocolScanner>();
public ScannerLifecycle()
{
protocolMap.put("file", new FileProtocolScanner());
protocolMap.put("jar", new JarProtocolScanner());
// define the vfszip, vfsfile, and vfsmemory protocols if the jboss virtual file system classes are on the classpath.
if( isVfsDefined() ) {
protocolMap.put("vfszip", new VfsProtocolScanner());
protocolMap.put("vfsfile", new VfsProtocolScanner());
protocolMap.put("vfsmemory", new VfsProtocolScanner());
}
if( isZipDefined() ) {
protocolMap.put("zip", new ZipProtocolScanner());
}
if( isBundleDefined() ) {
protocolMap.put("bundle", new BundleProtocolScanner());
protocolMap.put("bundleresource", new BundleProtocolScanner());
}
if( MEMOIZE_SCAN_NODE ) {
cache = Collections.synchronizedMap(new WeakHashMap<ClassLoader, Map<RootUrlLocator,ScanNode>>());
}
}
/**
* Returns the root scan node. Using this node, all of the nodes on the
* classpath can be visited.
*/
public ScanNode scanNode()
throws Exception
{
return scanNode( Thread.currentThread().getContextClassLoader(), new MarkerResourceLocator("META-INF/xchain.xml") );
}
/**
* <p>Scans the root urls found by the locator and returns the root scan node for those roots.</p>
*/
public ScanNode scanNode( RootUrlLocator locator )
throws Exception
{
return scanNode( Thread.currentThread().getContextClassLoader(), locator );
}
public ScanNode scanNode( ClassLoader classLoader, RootUrlLocator locator )
throws Exception
{
if( MEMOIZE_SCAN_NODE ) {
ScanNode cachedScanNode = getCached(classLoader, locator);
if( cachedScanNode != null ) {
log.debug("Using cached ScanNode instance '{}' for ClassLoader '{}' and RootUrlLocator '{}'.", new Object[] {cachedScanNode, classLoader, locator});
return cachedScanNode;
}
}
ScanNode rootScanNode = new ScanNode();
Set<URL> roots = locator.findRoots(Thread.currentThread().getContextClassLoader());
for( URL root : roots ) {
// find the protocol scanner for this root url.
String protocol = root.getProtocol();
ProtocolScanner scanner = protocolMap.get(protocol);
if( scanner == null ) {
if( log.isDebugEnabled() ) {
log.debug("Could not scan protocol "+protocol+" of url "+root+", because a scanner for this protocol is not defined.");
}
continue;
}
scanner.scan(rootScanNode, root);
}
if( MEMOIZE_SCAN_NODE ) {
putCached(classLoader, locator, rootScanNode);
}
return rootScanNode;
}
/**
* Clears internal cache of ScanNode instances.
*/
public void clearCache() {
if( MEMOIZE_SCAN_NODE )
this.cache.clear();
}
/*
* Returns the cached ScanNode instance mapped by the (classLoader, locator) tuple.
*/
private ScanNode getCached( ClassLoader classLoader, RootUrlLocator locator ) {
Map<RootUrlLocator, ScanNode> locatorScanNodeMap = cache.get(classLoader);
if( locatorScanNodeMap != null )
return locatorScanNodeMap.get(locator);
return null;
}
/*
* Maps a (ClassLoader,RootUrlLocator) tuple to a ScanNode instance to cache.
*/
private void putCached( ClassLoader classLoader, RootUrlLocator locator, ScanNode scanNode ) {
Map<RootUrlLocator, ScanNode> locatorScanNodeMap = this.cache.get(classLoader);
if( locatorScanNodeMap == null ) {
locatorScanNodeMap = Collections.synchronizedMap(new LRUMap(20));
this.cache.put(classLoader, locatorScanNodeMap);
}
locatorScanNodeMap.put(locator, scanNode);
}
private boolean isVfsDefined() {
try {
Thread.currentThread().getContextClassLoader().loadClass("org.jboss.virtual.VirtualFile");
// ASSERT: the virtual file class loaded, so we are on jboss 5.
return true;
}
catch( Exception e ) {
return false;
}
}
private boolean isZipDefined() {
try {
Thread.currentThread().getContextClassLoader().loadClass("weblogic.utils.zip.ZipURLConnection");
return true;
}
catch( Exception e ) {
return false;
}
}
private boolean isBundleDefined() {
try {
Thread.currentThread().getContextClassLoader().loadClass("org.osgi.framework.Bundle");
return true;
}
catch( Exception e ) {
return false;
}
}
}