/***************************************************************************
* Copyright (C) by Fabrizio Montesi *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 2 of the *
* License, or (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* For details about the authors of this software, see the AUTHORS file. *
***************************************************************************/
package jolie;
import jolie.lang.Constants;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import jolie.net.CommCore;
import jolie.net.ext.CommChannelFactory;
import jolie.net.ext.CommListenerFactory;
import jolie.net.ext.CommProtocolFactory;
import jolie.runtime.AndJarDeps;
import jolie.runtime.CanUseJars;
import jolie.runtime.JavaService;
/**
* JolieClassLoader is used to resolve the loading of JOLIE extensions and external libraries.
* @author Fabrizio Montesi
*/
public class JolieClassLoader extends URLClassLoader
{
private final static Pattern extensionSplitPattern = Pattern.compile( ":" );
private final Map< String, String > channelExtensionClassNames = new HashMap< String, String >();
private final Map< String, String > listenerExtensionClassNames = new HashMap< String, String >();
private final Map< String, String > protocolExtensionClassNames = new HashMap< String, String >();
private void init( URL[] urls )
throws IOException
{
for( URL url : urls ) {
if ( "jar".equals( url.getProtocol() ) ) {
try {
checkJarForJolieExtensions( (JarURLConnection)url.openConnection() );
} catch( IOException e ) {
throw new IOException( "Loading failed for jolie extension jar " + url.toString(), e );
}
}
}
}
/**
* Constructor
* @param urls the urls to use for the lookup of libraries
* @param parent the parent class loader to use for lookup fallback
* @throws java.io.IOException if the initialization fails,
* e.g. if a required dependency in some specified
* file can not be satisfied
*/
public JolieClassLoader( URL[] urls, ClassLoader parent )
throws IOException
{
super( urls, parent );
init( urls );
}
@Override
protected Class<?> findClass( String className )
throws ClassNotFoundException
{
Class<?> c = super.findClass( className );
if ( JavaService.class.isAssignableFrom( c ) ) {
checkForJolieAnnotations( c );
}
return c;
}
@Override
public Class<?> loadClass( String className )
throws ClassNotFoundException
{
try {
Class<?> c = findLoadedClass( className );
if ( c == null ) {
c = findClass( className );
}
return c;
} catch( ClassNotFoundException e ) {
return getParent().loadClass( className );
}
}
private void checkForJolieAnnotations( Class<?> c )
{
AndJarDeps needsJars = c.getAnnotation( AndJarDeps.class );
if ( needsJars != null ) {
for( String filename : needsJars.value() ) {
/*
* TODO jar unloading when service is unloaded?
* Consider other services needing the same jars in that.
*/
try {
addJarResource( filename );
} catch( MalformedURLException e ) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
}
}
CanUseJars canUseJars = c.getAnnotation( CanUseJars.class );
if ( canUseJars != null ) {
for( String filename : canUseJars.value() ) {
/*
* TODO jar unloading when service is unloaded?
* Consider other services needing the same jars in that.
*/
try {
addJarResource( filename );
} catch( MalformedURLException e ) {
} catch( IOException e ) {
}
}
}
}
private Class<?> loadExtensionClass( String className )
throws ClassNotFoundException
{
Class<?> c = loadClass( className );
checkForJolieAnnotations( c );
return c;
}
/**
* Creates and returns a <code>CommChannelFactory</code>, selecting it
* from the built-in and externally loaded JOLIE extensions.
* @param name the identifier of the factory to create
* @param commCore the <code>CommCore</code> instance to use for constructing the factory
* @return the requested factory
* @throws java.io.IOException if the factory could not have been created
*/
public synchronized CommChannelFactory createCommChannelFactory( String name, CommCore commCore )
throws IOException
{
CommChannelFactory factory = null;
String className = channelExtensionClassNames.get( name );
if ( className != null ) {
try {
Class<?> c = loadExtensionClass( className );
if ( CommChannelFactory.class.isAssignableFrom( c ) ) {
Class< ? extends CommChannelFactory > fClass = (Class< ? extends CommChannelFactory >)c;
factory = fClass.getConstructor( CommCore.class ).newInstance( commCore );
}
} catch( ClassNotFoundException e ) {
throw new IOException( e );
} catch( InstantiationException e ) {
throw new IOException( e );
} catch( IllegalAccessException e ) {
throw new IOException( e );
} catch( NoSuchMethodException e ) {
throw new IOException( e );
} catch( InvocationTargetException e ) {
throw new IOException( e );
}
}
return factory;
}
private void checkForChannelExtension( Attributes attrs )
throws IOException
{
String extension = attrs.getValue( Constants.Manifest.ChannelExtension );
if ( extension != null ) {
String[] pair = extensionSplitPattern.split( extension );
if ( pair.length == 2 ) {
channelExtensionClassNames.put( pair[0], pair[1] );
} else {
throw new IOException( "Invalid extension definition found in manifest file: " + extension );
}
}
}
/**
* Creates and returns a <code>CommListenerFactory</code>, selecting it
* from the built-in and externally loaded JOLIE extensions.
* @param name the identifier of the factory to create
* @param commCore the <code>CommCore</code> instance to use for constructing the factory
* @return the requested factory
* @throws java.io.IOException if the factory could not have been created
*/
public synchronized CommListenerFactory createCommListenerFactory( String name, CommCore commCore )
throws IOException
{
CommListenerFactory factory = null;
String className = listenerExtensionClassNames.get( name );
if ( className != null ) {
try {
Class<?> c = loadExtensionClass( className );
if ( CommListenerFactory.class.isAssignableFrom( c ) ) {
Class< ? extends CommListenerFactory > fClass = (Class< ? extends CommListenerFactory >)c;
factory = fClass.getConstructor( CommCore.class ).newInstance( commCore );
}
} catch( ClassNotFoundException e ) {
throw new IOException( e );
} catch( InstantiationException e ) {
throw new IOException( e );
} catch( IllegalAccessException e ) {
throw new IOException( e );
} catch( NoSuchMethodException e ) {
throw new IOException( e );
} catch( InvocationTargetException e ) {
throw new IOException( e );
}
}
return factory;
}
private void checkForListenerExtension( Attributes attrs )
throws IOException
{
String extension = attrs.getValue( Constants.Manifest.ListenerExtension );
if ( extension != null ) {
String[] pair = extensionSplitPattern.split( extension );
if ( pair.length == 2 ) {
listenerExtensionClassNames.put( pair[0], pair[1] );
} else {
throw new IOException( "Invalid extension definition found in manifest file: " + extension );
}
}
}
/**
* Creates and returns a <code>CommProtocolFactory</code>, selecting it
* from the built-in and externally loaded JOLIE extensions.
* @param name the identifier of the factory to create
* @param commCore the <code>CommCore</code> instance to use for constructing the factory
* @return the requested factory
* @throws java.io.IOException if the factory could not have been created
*/
public synchronized CommProtocolFactory createCommProtocolFactory( String name, CommCore commCore )
throws IOException
{
CommProtocolFactory factory = null;
String className = protocolExtensionClassNames.get( name );
if ( className != null ) {
try {
Class<?> c = loadExtensionClass( className );
if ( CommProtocolFactory.class.isAssignableFrom( c ) ) {
Class< ? extends CommProtocolFactory > fClass = (Class< ? extends CommProtocolFactory >)c;
factory = fClass.getConstructor( CommCore.class ).newInstance( commCore );
}
} catch( ClassNotFoundException e ) {
throw new IOException( e );
} catch( InstantiationException e ) {
throw new IOException( e );
} catch( IllegalAccessException e ) {
throw new IOException( e );
} catch( NoSuchMethodException e ) {
throw new IOException( e );
} catch( InvocationTargetException e ) {
throw new IOException( e );
}
}
return factory;
}
private void checkForProtocolExtension( Attributes attrs )
throws IOException
{
String extension = attrs.getValue( Constants.Manifest.ProtocolExtension );
if ( extension != null ) {
String[] pair = extensionSplitPattern.split( extension );
if ( pair.length == 2 ) {
protocolExtensionClassNames.put( pair[0], pair[1] );
} else {
throw new IOException( "Invalid extension definition found in manifest file: " + extension );
}
}
}
private void checkJarForJolieExtensions( JarURLConnection jarConnection )
throws IOException
{
Manifest manifest = jarConnection.getManifest();
if ( manifest != null ) {
Attributes attrs = manifest.getMainAttributes();
checkForChannelExtension( attrs );
checkForListenerExtension( attrs );
checkForProtocolExtension( attrs );
}
}
/**
* Adds a Jar file to the pool of resource to look into for extensions.
* @param jarName the Jar filename
* @throws java.net.MalformedURLException
* @throws java.io.IOException if the Jar file could not be found or if jarName does not refer to a Jar file
*/
public void addJarResource( String jarName )
throws MalformedURLException, IOException
{
URL url = findResource( jarName );
if ( url == null ) {
throw new IOException( "Resource not found: " + jarName );
}
if ( url.getProtocol().startsWith( "jap" ) ) {
addURL( new URL( url + "!/" ) );
} else {
addURL( new URL( "jap:" + url + "!/" ) );
}
}
}