/**
* 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.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.xchain.framework.osgi.ManifestParser;
import org.xchain.framework.osgi.ParsedClassPathEntry;
import static java.lang.String.format;
/**
* @author Christian Trimble
* @author John Trimble
*/
public class BundleProtocolScanner
implements ProtocolScanner
{
private static Logger log = LoggerFactory.getLogger(BundleProtocolScanner.class);
private static BundleContext context = null;
public static void setBundleContext( BundleContext context )
{
BundleProtocolScanner.context = context;
}
/**
* <p>Scans the entries in a Weblogic Zip URL and adds all of the entries of that jar into the tree rooted at rootNode. Missing
* directory nodes will be added.</p>
*
* @param rootNode the root node of the scan.
* @param jarUrl the url of the jar with entries to be added to the scan node.
*/
public void scan( ScanNode rootNode, URL bundleUrl )
throws IOException
{
if( log.isDebugEnabled() ) {
log.debug("Bundle url "+bundleUrl.toExternalForm());
}
// get the bundle for this class. This is used to get other bundles.
Bundle scannerBundle = FrameworkUtil.getBundle(BundleProtocolScanner.class);
BundleContext scannerBundleContext = scannerBundle.getBundleContext();
// get the class path entry for this bundle.
String classPathHeader = (String)scannerBundle.getHeaders().get("Bundle-ClassPath");
// parse the header.
List<ParsedClassPathEntry> classPathEntryList = null;
try {
classPathEntryList = ManifestParser.parseClassPathEntries( classPathHeader );
}
catch( Exception e ) {
throw new IOException("Could not scan "+bundleUrl);
}
for( ParsedClassPathEntry entry : classPathEntryList ) {
for( String target : entry.getTargetList() ) {
if( target.endsWith(".jar") ) {
scanJar( rootNode, target, scannerBundle );
}
else {
scanDirectory( rootNode, target, scannerBundle );
}
}
}
}
public void scanJar( ScanNode scanNode, String path, Bundle bundle )
throws IOException
{
URL jarUrl = bundle.getEntry(path);
JarInputStream in = null;
try {
in = new JarInputStream(jarUrl.openStream());
JarEntry entry = null;
while( (entry = in.getNextJarEntry()) != null ) {
String resourceName = entry.getName();
if( entry.isDirectory() && !resourceName.endsWith("/") )
resourceName = resourceName + "/";
insertEntryNodes( scanNode, resourceName );
}
}
catch( Exception e ) {
e.printStackTrace();
log.warn(format("Error occurred while processing JAR '%s'.", path), e);
}
}
/**
* <p>Adds an entry to the root node for a JarEntry. This method is declared to be protected, so that test cases an interact with it.
* In general, this method should be treaded as private.</p>
*
* @param rootNode the root node of the scan.
* @param entry the jar entry to place under the root node.
*/
void insertEntryNodes( ScanNode rootNode, String name )
{
// TODO: verify that this works on windows.
String[] parts = name.split("\\/");
int depth = 0;
ScanNode parentNode = rootNode;
// create any missing directory nodes.
for( int i = 0; i < parts.length - 1; i++ ) {
ScanNode currNode = parentNode.getChildMap().get(parts[i]);
if( currNode == null ) {
currNode = new ScanNode();
currNode.setResourceName("".equals(parentNode.getResourceName())?parts[i]:parentNode.getResourceName()+"/"+parts[i]);
currNode.setName(parts[i]);
currNode.setDirectory(true);
currNode.setFile(false);
parentNode.getChildMap().put(parts[i], currNode);
}
else {
currNode.setDirectory(true);
}
parentNode = currNode;
}
// ASSERT: parentNode is now the parent node of this entry.
// create the entry node if it is not already in the scan tree.
ScanNode currNode = parentNode.getChildMap().get(parts[parts.length-1]);
if( currNode == null ) {
currNode = new ScanNode();
currNode.setResourceName("".equals(parentNode.getResourceName())?parts[parts.length-1]:parentNode.getResourceName()+"/"+parts[parts.length-1]);
currNode.setName(parts[parts.length-1]);
currNode.setDirectory(isDirectory(name)?true:false);
currNode.setFile(isDirectory(name)?false:true);
parentNode.getChildMap().put(parts[parts.length-1], currNode);
}
else {
if( isDirectory(name) ) {
currNode.setDirectory(true);
}
else {
currNode.setFile(true);
}
}
}
private boolean isDirectory(String path)
{
return path.endsWith("/");
}
public void scanDirectory( ScanNode scanNode, String path, Bundle bundle )
throws IOException
{
// The following excerpt from section 3.8.6 of the OSGi 4.2 Core Specification relates to bundle entry URLs:
//
// The getPath method for a bundle entry URL must return an absolute path (a path that starts with '/') to a
// resource or entry in a bundle. For example, the URL returned from getEntry("myimages/test.gif") must have a
// path of /myimages/test.gif.
// normalize the path
try {
path = new URI("/" + path + "/").normalize().getPath();
} catch( URISyntaxException e ) {
throw new IllegalArgumentException(String.format("Given path '%s' has an invalid syntax.", path), e);
}
if( ".".equals(path) ) { path = ""; }
Enumeration<URL> pathEnum = (Enumeration<URL>)bundle.findEntries(path, "*", true);
// make sure we have a normalized path.
if( pathEnum != null ) {
while( pathEnum.hasMoreElements() ) {
URL entry = pathEnum.nextElement();
if( entry.getPath().startsWith(path) ) {
String resourceName = entry.getPath().substring(path.length());
insertEntryNodes(scanNode, resourceName);
}
}
}
}
}