All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jadex.platform.service.library.LibraryService Maven / Gradle / Ivy

package jadex.platform.service.library;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import jadex.bridge.BasicComponentIdentifier;
import jadex.bridge.IComponentIdentifier;
import jadex.bridge.IExternalAccess;
import jadex.bridge.IInputConnection;
import jadex.bridge.IInternalAccess;
import jadex.bridge.IResourceIdentifier;
import jadex.bridge.LocalResourceIdentifier;
import jadex.bridge.ResourceIdentifier;
import jadex.bridge.component.IExecutionFeature;
import jadex.bridge.service.RequiredServiceInfo;
import jadex.bridge.service.annotation.CheckNotNull;
import jadex.bridge.service.annotation.Excluded;
import jadex.bridge.service.annotation.Reference;
import jadex.bridge.service.annotation.Service;
import jadex.bridge.service.annotation.ServiceComponent;
import jadex.bridge.service.annotation.ServiceShutdown;
import jadex.bridge.service.annotation.ServiceStart;
import jadex.bridge.service.component.IRequiredServicesFeature;
import jadex.bridge.service.search.SServiceProvider;
import jadex.bridge.service.types.cms.IComponentManagementService;
import jadex.bridge.service.types.context.IContextService;
import jadex.bridge.service.types.library.IDependencyService;
import jadex.bridge.service.types.library.ILibraryService;
import jadex.bridge.service.types.library.ILibraryServiceListener;
import jadex.bridge.service.types.remote.ServiceOutputConnection;
import jadex.bridge.service.types.settings.ISettingsService;
import jadex.bridge.service.types.threadpool.IDaemonThreadPoolService;
import jadex.commons.IPropertiesProvider;
import jadex.commons.Properties;
import jadex.commons.SUtil;
import jadex.commons.Tuple2;
import jadex.commons.future.CollectionResultListener;
import jadex.commons.future.CounterResultListener;
import jadex.commons.future.DelegationResultListener;
import jadex.commons.future.ExceptionDelegationResultListener;
import jadex.commons.future.Future;
import jadex.commons.future.IFuture;
import jadex.commons.future.IIntermediateResultListener;
import jadex.commons.future.IResultListener;
import jadex.commons.future.IntermediateDefaultResultListener;

/**
 *  Library service for loading classpath elements.
 */
@Service(ILibraryService.class)
public class LibraryService	implements ILibraryService, IPropertiesProvider
{
	//-------- constants --------
	
	/** The (standard) Library service name. */
	public static final String LIBRARY_SERVICE = "library_service";
	
	/** The pseudo system classpath rid. */
	public static final IResourceIdentifier SYSTEMCPRID;
	
	static
	{
		IResourceIdentifier res = null;
		try
		{
			res = new ResourceIdentifier(new LocalResourceIdentifier(new BasicComponentIdentifier("PSEUDO"), new URL("http://SYSTEMCPRID")), null);
		}
		catch(Exception e)
		{
			// should not happen
			e.printStackTrace();
		}
		SYSTEMCPRID = res;
	}
	
	//-------- attributes --------
	
	/** The component. */
	@ServiceComponent
	protected IInternalAccess	component;
	
	/** LibraryService listeners. */
	protected Set listeners;

	/** The init urls. */
	protected Object[] initurls;

	/** The class loader futures for currently loading class loaders. */
	protected Map> clfuts;
	
	/** The map of managed resources 2xrid (local, remote) -> delegate loader). */
	protected Map classloaders;
	
	/** The base classloader. */
	protected ChangeableURLClassLoader baseloader;
	
	/** The delegation root loader. */
	protected DelegationURLClassLoader rootloader;
	protected IResourceIdentifier rootrid;
	// root rid is not set in rootloader as it is not a valid file url

	/** The added links. */
	protected Set> addedlinks;

	/** The remove links. */
	protected Set> removedlinks;
	
	/** The delayed add links (could not directly be added because the parent was not there). */
	protected Set> addtodo;
	
	// cached results
	
	/** The dependencies. */
	protected Tuple2>> rids;
	
	/** The non-managed urls (cached for speed). */
//	protected Set	nonmanaged;
	protected Set	nonmanaged;
	
	//-------- constructors --------
	
	/** 
	 *  Creates a new LibraryService.
	 */ 
	public LibraryService()
	{
		this((ClassLoader)null);
	}
	
	/** 
	 *  Creates a new LibraryService.
	 *  @param urls	Urls may be specified as java.net.URLs, java.io.Files or java.lang.Strings.
	 *  	Strings are interpreted as relative files (relative to current directory),
	 *  	absolute files or URLs (whatever can be found). 
	 */ 
	public LibraryService(Object[] urls)
	{
		this(urls, null);
	}
	
	/** 
	 *  Creates a new LibraryService.
	 *  @param baseloader The base classloader that is parent of all subloaders.
	 */ 
	public LibraryService(ClassLoader baseloader)
	{
		this(null, baseloader, null);
	}
	
	/** 
	 *  Creates a new LibraryService.
	 *  @param urls	Urls may be specified as java.net.URLs, java.io.Files or java.lang.Strings.
	 *  	Strings are interpreted as relative files (relative to current directory),
	 *  	absolute files or URLs (whatever can be found). 
	 */ 
	public LibraryService(Object[] urls, ClassLoader baseloader)
	{
		this(urls, baseloader, null);
	}
	
	/** 
	 *  Creates a new LibraryService.
	 *  @param urls	Urls may be specified as java.net.URLs, java.io.Files or java.lang.Strings.
	 *  	Strings are interpreted as relative files (relative to current directory),
	 *  	absolute files or URLs (whatever can be found). 
	 */ 
	public LibraryService(Object[] urls, ClassLoader baseloader, Map properties)
	{
		this.classloaders = new HashMap();
		this.clfuts = new HashMap>();
		this.listeners	= new LinkedHashSet();
		this.initurls = urls!=null? urls.clone(): null;
		this.baseloader = baseloader!=null? new ChangeableURLClassLoader(null, baseloader)
			: new ChangeableURLClassLoader(null, getClass().getClassLoader());
		this.rootloader = new DelegationURLClassLoader(this.baseloader, null);
		this.addedlinks = new HashSet>();
		this.removedlinks = new HashSet>();
		this.addtodo = new HashSet>();
	}
	
	//-------- methods --------
	
	/**
	 *  Check if rid has local part and if it is null.
	 */
	protected void checkLocalRid(IResourceIdentifier rid)
	{
		if(rid!=null && rid.getLocalIdentifier()!=null && rid.getLocalIdentifier().getUri()==null)
		{
			System.out.println("local null rid found: "+rid);
//			throw new RuntimeException("local rid is null");
		}
	}
	
	/**
	 *  Add a new resource identifier.
	 *  @param parid The optional parent rid.
	 *  @param orid The resource identifier.
	 */
	public IFuture addResourceIdentifier(final IResourceIdentifier parid,
		final IResourceIdentifier orid, final boolean workspace)
	{
//		System.out.println("adding: "+orid+" on: "+parid);
		checkLocalRid(parid);
		checkLocalRid(orid);
		
		final Future ret = new Future();

//		if(parid!=null && !rootloader.getAllResourceIdentifiers().contains(parid))
		if(parid!=null && !internalgetAllResourceIdentifiers().contains(parid))
		{
			ret.setException(new RuntimeException("Parent rid unknown: "+parid));
		}
		else
		{
			getDependencies(orid, workspace).addResultListener(new ExceptionDelegationResultListener
				>>, IResourceIdentifier>(ret)
			{
				public void customResultAvailable(Tuple2>> deps)
				{
					final IResourceIdentifier rid = deps.getFirstEntity();
	//				
					// If could not be resolved to local (or already was local) consider as exception.
					if(rid.getLocalIdentifier()==null)
					{
						ret.setException(new RuntimeException("Global rid could not be resolved to local: "+rid));
					}
					else
					{
						// Check if file exists
						if(checkUri(rid.getLocalIdentifier().getUri())==null)
						{
							ret.setException(new RuntimeException("Local rid url invalid: "+rid));
						}
						else
						{
	//						System.out.println("add end "+rid+" on: "+parid);
		
							// Must be added with resolved rid (what about resolving parent?)
							addLink(parid, rid);
		
							getClassLoader(rid, deps.getSecondEntity(), parid, workspace).addResultListener(
								new ExceptionDelegationResultListener(ret)
							{
								public void customResultAvailable(final DelegationURLClassLoader chil)
								{
									ret.setResult(rid);
								}
							});
						}
					}
				}
				
				public void exceptionOccurred(Exception exception)
				{
					exception.printStackTrace();
					super.exceptionOccurred(exception);
				}
			});
			
	//		System.out.println("current: "+SUtil.arrayToString(rootloader.getAllResourceIdentifiers()));
		}
		
//		ret.addResultListener(new IResultListener()
//		{
//			public void resultAvailable(IResourceIdentifier result)
//			{
//				System.out.println("good");
//			}
//
//			public void exceptionOccurred(Exception exception)
//			{
//				exception.printStackTrace();
//			}
//		});
		
		return ret;
	}
	
	/**
	 *  Remove a resource identifier.
	 *  @param uri The resource identifier.
	 */
	public IFuture removeResourceIdentifier(IResourceIdentifier parid, final IResourceIdentifier rid)
	{
		checkLocalRid(rid);
		checkLocalRid(parid);
		
//		System.out.println("remove "+rid);
		final Future ret = new Future();
		
//		if(parid!=null && !rootloader.getAllResourceIdentifiers().contains(parid))
		if(parid!=null && !internalgetAllResourceIdentifiers().contains(parid))
		{
			ret.setException(new RuntimeException("Parent rid unknown: "+parid));
		}
		else
		{
			removeLink(parid, rid);
			removeSupport(rid, parid);
			ret.setResult(null);
		}
		
		return ret;
	}
	
	/** 
	 *  Get the root resource identifier.
	 *  @param uri The url.
	 *  @return The corresponding resource identifier.
	 */
	public IResourceIdentifier getRootResourceIdentifier()
	{
		return rootrid;
	}
	
	/**
	 *  Get all resource identifiers (does not include urls of parent loader).
	 *  @return The list of resource identifiers.
	 */
	public IFuture> getAllResourceIdentifiers()
	{
		return new Future>(new ArrayList(internalgetAllResourceIdentifiers()));
//		return new Future>(new ArrayList(rootloader.getAllResourceIdentifiers()));
	}
	
	// This implementation is incorrect as not for all added rids classloaders are created (if already managed by base loader)
//	/**
//	 *  Get the rids.
//	 */
//	public IFuture>>> getResourceIdentifiers()
//	{
//		if(rids==null)
//		{
//			Map> deps = new HashMap>();
//			
//			List todo = new ArrayList();
////			todo.add(rootloader.getResourceIdentifier());
//			todo.add(null);
//			
//			while(!todo.isEmpty())
//			{
//				IResourceIdentifier clrid = todo.remove(0);
//				if(SYSTEMCPRID.equals(clrid))
//				{
//					List mydeps = new ArrayList();
//					Set nonmans = getInternalNonManagedURLs();
//					for(URI uri: nonmans)
//					{
//						URL url = SUtil.toURL0(uri);
//						if(url!=null)
//							mydeps.add(new ResourceIdentifier(new LocalResourceIdentifier(component.getComponentIdentifier().getRoot(), url), null));
//					}
//					deps.put(clrid, mydeps);
//				}
//				else
//				{
//					DelegationURLClassLoader cl = clrid==null? rootloader: classloaders.get(clrid);
//					List mydeps = cl.getDelegateResourceIdentifiers();
//					deps.put(clrid, mydeps);
//					if(clrid==null)
//					{
//						mydeps.add(SYSTEMCPRID);
//					}
//					for(IResourceIdentifier rid: mydeps)
//					{
//						if(!deps.containsKey(rid))
//						{
//							todo.add(rid);
//						}
//					}
//				}
//			}
//			
//			rids = new Tuple2>>(rootloader.getResourceIdentifier(), deps);
//		}
//		
//		return new Future>>>(rids);
//	}
	
	/**
	 *  Get the rids.
	 */
	public IFuture>>> getResourceIdentifiers()
	{
		return new Future>>>(internalGetResourceIdentifiers());
	}
	
	/**
	 * 
	 */
	public Tuple2>> internalGetResourceIdentifiers()
	{
		if(rids==null)
		{
			Map> deps = new HashMap>();

			// rootloader rid
			List mydeps = rootloader.getDelegateResourceIdentifiers();
			deps.put(null, mydeps);
			mydeps.add(SYSTEMCPRID);
			
			// baseloader rid
			mydeps = new ArrayList();
			Set nonmans = getInternalNonManagedURLs();
			for(URI uri: nonmans)
			{
				mydeps.add(new ResourceIdentifier(new LocalResourceIdentifier(component.getComponentIdentifier().getRoot(), SUtil.toURL(uri)), null));
			}
			deps.put(SYSTEMCPRID, mydeps);
			
			// other rids
			for(Tuple2 link: addedlinks)
			{
				mydeps = deps.get(link.getFirstEntity());
				if(mydeps==null)
				{
					mydeps = new ArrayList();
					deps.put(link.getFirstEntity(), mydeps);
				}
				if(!mydeps.contains(link.getSecondEntity()))
				{
					mydeps.add(link.getSecondEntity());
				}
			}
			
			rids = new Tuple2>>(rootloader.getResourceIdentifier(), deps);
		}
		
		return rids;
	}
	
	/**
	 *  Add a new url.
	 *  @param uri The resource identifier.
	 */
	public IFuture addURL(final IResourceIdentifier parid, URL purl)
	{
		checkLocalRid(parid);
		
		final Future ret = new Future();

//		System.out.println("add url: "+url);
		
		URL url = checkUrl(purl);
		if(url==null)
		{
			ret.setException(new RuntimeException("URL not backed by local file: "+purl));
			return ret;
		}
//		System.out.println("add url2: "+url);
		
		// Normalize files to avoid duplicate urls.
		if("file".equals(url.getProtocol()))
		{
			try
			{
				url	= SUtil.getFile(url).getCanonicalFile().toURI().toURL();
			}
			catch(Exception e)
			{
			}
		}
		
		internalGetResourceIdentifier(url).addResultListener(
			new DelegationResultListener(ret)
		{
			public void customResultAvailable(final IResourceIdentifier rid)
			{
				// todo: should be true?
				addResourceIdentifier(parid, rid, true).addResultListener(
					new ExceptionDelegationResultListener(ret)
				{
					public void customResultAvailable(IResourceIdentifier result)
					{
						ret.setResult(rid);
					}
				});
			}
		});
		
		return ret;
	}
	
	/**
	 *  Remove a new url.
	 *  @param url The resource identifier.
	 */
	public IFuture removeURL(final IResourceIdentifier parid, final URL url)
	{
		final Future ret = new Future();
		
		internalGetResourceIdentifier(url).addResultListener(
			new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(IResourceIdentifier result)
			{
				removeResourceIdentifier(parid, result).addResultListener(new DelegationResultListener(ret));
			}
		});
		
		return ret;
	}
	
	/**
	 *  Add a top level url. A top level url will
	 *  be available for all subordinated resources. 
	 *  @param uri The url.
	 */
	public IFuture addTopLevelURL(@CheckNotNull URL purl)
	{
		URL url = checkUrl(purl);
		if(url==null)
			return new Future(new RuntimeException("URL not backed by local file: "+purl));
		
		baseloader.addURL(url);
		nonmanaged = null;
		IResourceIdentifier rid = new ResourceIdentifier(new LocalResourceIdentifier(component.getComponentIdentifier().getRoot(), url), null);
		addLink(SYSTEMCPRID, rid);
		notifyAdditionListeners(SYSTEMCPRID, rid);
		return IFuture.DONE;
	}

	/**
	 *  Remove a top level url. A top level url will
	 *  be available for all subordinated resources. 
	 *  @param url The url.
	 *  
	 *  note: top level url removal will only take 
	 *  effect after restart of the platform.
	 */
	public IFuture removeTopLevelURL(@CheckNotNull URL url)
	{
		baseloader.removeURL(url);
		nonmanaged = null;
		IResourceIdentifier rid = new ResourceIdentifier(new LocalResourceIdentifier(component.getComponentIdentifier().getRoot(), url), null);
		removeLink(SYSTEMCPRID, rid);
		notifyRemovalListeners(SYSTEMCPRID, rid);
		return IFuture.DONE;
	}
	
	/**
	 *  Get other contained (but not directly managed) urls from parent classloaders.
	 *  @return The list of urls.
	 */
	public IFuture> getNonManagedURLs()
	{
		Set res = getInternalNonManagedURLs();
		List ret = new ArrayList();
		if(res!=null)
		{
			for(URI uri: res)
			{
				try
				{
					ret.add(uri.toURL());
				}
				catch(Exception e)
				{
					System.out.println("Problem with url: "+uri);
				}
			}
		}
		return new Future>(ret);
	}
	
	/**
	 *  Get other contained (but not directly managed) urls from parent classloaders.
	 *  @return The set of urls.
	 */
	protected Set	getInternalNonManagedURLs()
	{
		if(nonmanaged==null)
		{
			nonmanaged	= new LinkedHashSet();
			collectClasspathURLs(baseloader, nonmanaged, new HashSet());
		}
		return nonmanaged;
	}
	
	/**
	 *  Get all urls (managed, indirect and non-managed from parent loader).
	 *  @return The list of urls.
	 */
	public IFuture> getAllURLs()
	{
		final long	start	= System.currentTimeMillis();
		
		final Future> ret = new Future>();
		
		getAllResourceIdentifiers().addResultListener(new ExceptionDelegationResultListener, List>(ret)
		{
			public void customResultAvailable(List result)
			{
				final List res = new ArrayList();
				for(int i=0; i re = getInternalNonManagedURLs();
				for(URI uri: re)
				{
					URL url = SUtil.toURL0(uri);
					if(url!=null)
						res.add(url);
				}
//				res.addAll();
				
//				System.out.println("getAllUrls: "+(System.currentTimeMillis()-start));
				ret.setResult(res);
			}
		});
		return ret;
	}
		
	/** 
	 *  Returns the current ClassLoader.
	 *  @return the current ClassLoader
	 */
	@Excluded
	public @Reference IFuture getClassLoader(IResourceIdentifier rid)
	{
		return getClassLoader(rid, true);
	}
	
	/** 
	 *  Returns the current ClassLoader.
	 *  @param rid The resource identifier (null for current global loader).
	 *  @return the current ClassLoader
	 */
	@Excluded()
	public IFuture getClassLoader(final IResourceIdentifier rid, boolean workspace)
	{
		checkLocalRid(rid);
		final Future ret = new Future();
		
		if(rid==null || rid.equals(rootloader.getResourceIdentifier()))
		{
			ret.setResult(rootloader);
//			System.out.println("root classloader: "+rid);
		}
		else if(ResourceIdentifier.isLocal(rid, component.getComponentIdentifier().getRoot()) && getInternalNonManagedURLs().contains(rid.getLocalIdentifier().getUri()))
		{
			ret.setResult(baseloader);
//			System.out.println("base classloader: "+rid);
		}	
		else
		{
			if(!internalGetResourceIdentifiers().getSecondEntity().containsKey(rid))
			{
				addLink(null, rid);
			}
			
			getClassLoader(rid, null, rootloader.getResourceIdentifier(), workspace).addResultListener(new ExceptionDelegationResultListener(ret)
			{
				public void customResultAvailable(DelegationURLClassLoader result)
				{
					ret.setResult(result);
//					System.out.println("custom classloader: "+result.hashCode()+" "+rid);
				}
			});
		}
		
		return ret;
	}
	
	/** 
	 *  Get the resource identifier for an url.
	 *  @return The resource identifier.
	 */
	public IFuture getResourceIdentifier(final URL url)
	{
		final Future ret = new Future();
		
		component.getComponentFeature(IRequiredServicesFeature.class).searchService(IDependencyService.class, RequiredServiceInfo.SCOPE_PLATFORM)
			.addResultListener(new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(IDependencyService drs)
			{
				drs.getResourceIdentifier(url).addResultListener(new DelegationResultListener(ret));
			}
		});
		
		return ret;
	}

	//-------- listener methods --------
	
    /**
	 *  Add an Library Service listener.
	 *  The listener is registered for changes in the loaded library states.
	 *  @param listener The listener to be added.
	 */
	public IFuture addLibraryServiceListener(ILibraryServiceListener listener)
	{
		if(listener==null)
			throw new IllegalArgumentException();
			
		listeners.add(listener);
		return IFuture.DONE;
	}

	/**
	 *  Remove an Library Service listener.
	 *  @param listener  The listener to be removed.
	 */
	public IFuture removeLibraryServiceListener(ILibraryServiceListener listener)
	{
		listeners.remove(listener);
		return IFuture.DONE;
	}
	
	//-------- internal methods --------
	
	/**
	 *  Get or create a classloader for a rid.
	 */
	protected IFuture getClassLoader(final IResourceIdentifier rid, 
		Map> alldeps, final IResourceIdentifier support, final boolean workspace)
	{
		final Future ret;
		
		// full=(global, local) calls followed by any other call are ok, because global and local can be cached
		// pure global call followed by pure local call -> would mean rids have not been resolved
		// pure local call followed by pure global call -> would mean rids have not been resolved
		if(ResourceIdentifier.isLocal(rid, component.getComponentIdentifier().getRoot()) && getInternalNonManagedURLs().contains(rid.getLocalIdentifier().getUri()))
		{
			ret	= new Future((DelegationURLClassLoader)null);
			notifyAdditionListeners(support, rid);
		}
		else if(clfuts.containsKey(rid))
		{
			ret	= clfuts.get(rid);
		}
		else
		{
	//		final URL url = rid.getLocalIdentifier().getSecondEntity();
			
			ret = new Future();
			DelegationURLClassLoader cl = (DelegationURLClassLoader)classloaders.get(rid);
			
			if(cl!=null)
			{
				addSupport(rid, support);
				ret.setResult(cl);
			}
			else
			{
				clfuts.put(rid, ret);
//				if(rid.getGlobalIdentifier()==null)
//					System.out.println("getClassLoader(): "+rid);
				
				if(alldeps==null)
				{
//					System.out.println("getdeps in getcl: "+component.getComponentIdentifier()+", "+rid);
					getDependencies(rid, workspace).addResultListener(
						new ExceptionDelegationResultListener>>, DelegationURLClassLoader>(ret)
					{
						public void customResultAvailable(Tuple2>> deps)
						{
							createClassLoader(deps.getFirstEntity(), deps.getSecondEntity(), support, workspace).addResultListener(new DelegationResultListener(ret)
							{
								public void customResultAvailable(DelegationURLClassLoader result)
								{
									clfuts.remove(rid);
									super.customResultAvailable(result);
								}
								
								public void exceptionOccurred(Exception exception)
								{
									clfuts.remove(rid);
									super.exceptionOccurred(exception);
								}
							});
						}
						
						public void exceptionOccurred(Exception exception)
						{
							clfuts.remove(rid);
							super.exceptionOccurred(exception);
						}
					});
				}
				else
				{
//					System.out.println("create cl: "+component.getComponentIdentifier()+", "+rid);
					createClassLoader(rid, alldeps, support, workspace).addResultListener(new DelegationResultListener(ret)
					{
						public void customResultAvailable(DelegationURLClassLoader result)
						{
							clfuts.remove(rid);
							super.customResultAvailable(result);
						}
						
						public void exceptionOccurred(Exception exception)
						{
							clfuts.remove(rid);
							super.exceptionOccurred(exception);
						}
					});
				}
			}
		}
		
		return ret;
	}
	
	/**
	 *  Create a new classloader.
	 */
	protected IFuture createClassLoader(final IResourceIdentifier rid, 
		final Map> alldeps, final IResourceIdentifier support, final boolean workspace)
	{
		checkLocalRid(rid);
		
		// Class loaders shouldn't be created for local URLs, which are already available in base class loader.
		assert rid.getLocalIdentifier()==null || !ResourceIdentifier.isLocal(rid, component.getComponentIdentifier().getRoot()) || !getInternalNonManagedURLs().contains(rid.getLocalIdentifier().getUri());
		
		final Future ret = new Future();
		
		if(isAvailable(rid))
		{
			final DelegationURLClassLoader cl = createNewDelegationClassLoader(rid, baseloader, null);
			classloaders.put(rid, cl);
			
//			System.out.println("createClassLoader() put: "+component.getComponentIdentifier()+", "+rid);
			
			final List deps = alldeps.get(rid);
			CollectionResultListener lis = new CollectionResultListener
				(deps.size(), true, new ExceptionDelegationResultListener, DelegationURLClassLoader>(ret)
			{
				public void customResultAvailable(Collection result)
				{
					// Strip null values of provided dependencies from results.
					for(Iterator it=result.iterator(); it.hasNext(); )
					{
						if(it.next()==null)
							it.remove();
					}
					
					for(DelegationURLClassLoader dcl: result)
					{
						cl.addDelegateClassLoader(dcl);
					}
					addSupport(rid, support);
					ret.setResult(cl);
				}
			});
			
			for(int i=0; i(ret)
			{
				public void customResultAvailable(Void result)
				{
					createClassLoader(rid, alldeps, support, workspace).addResultListener(new DelegationResultListener(ret));
				}
			});
		}
		
		return ret;
	}

	/**
	 * Handle instantiation here, so the DelegationURLClassLoader can be another
	 * implementation.
	 * @param rid
	 * @param baseloader
	 * @param delegates
	 * @return {@link DelegationURLClassLoader} or subclass.
	 */
	protected DelegationURLClassLoader createNewDelegationClassLoader(final IResourceIdentifier rid, ClassLoader baseloader, DelegationURLClassLoader[] delegates)
	{
		URL url = getRidUrl(rid);
		return new DelegationURLClassLoader(rid, url, baseloader, delegates);
	}
	
	/**
	 *  Get the local file url for a rid.
	 */
	protected URL getRidUrl(final IResourceIdentifier rid)
	{
		URL	url;
		if(ResourceIdentifier.isLocal(rid, component.getComponentIdentifier().getRoot()))
		{
			url	= rid!=null && rid.getLocalIdentifier()!=null && rid.getLocalIdentifier().getUri()!=null? SUtil.toURL(rid.getLocalIdentifier().getUri()): null;
		}
		else
		{
			File	jar	= getHashRidFile(rid);
			if(!jar.exists())
			{
				throw new RuntimeException("Resource not found: "+jar);
			}
			else
			{
				try
				{
					url	= jar.toURI().toURL();
				}
				catch(Exception e)
				{
					throw new RuntimeException(e);
				}
			}
		}
		return url;
	}
	
	/**
	 * Test, if a resource is available locally.
	 */
	protected boolean	isAvailable(IResourceIdentifier rid)	
	{
		// Do not check existence of manually added (local) resources
		return ResourceIdentifier.isLocal(rid, component.getComponentIdentifier().getRoot())
			|| getHashRidFile(rid).exists();
	}

	/**
	 *  Get the file for a hash rid.
	 */
	protected File	getHashRidFile(IResourceIdentifier rid)
	{
		assert ResourceIdentifier.isHashGid(rid);

		// http://tools.ietf.org/html/rfc3548#section-4 for local storage of hashed resources
		String	name	= rid.getGlobalIdentifier().getResourceId().substring(2).replace('+', '-').replace('/', '_') + ".jar";
		IContextService localService = SServiceProvider.getLocalService(component, IContextService.class);
		// use contextService to get private data dir on android
		IFuture future = localService.getFile(SUtil.JADEXDIR + "resources/"+name);
		File file = future.get();
		return file;
	}
	
	/**
	 *  Download a resource from another platform.
	 */
	protected IFuture	downloadResource(final IResourceIdentifier rid)
	{
		assert rid!=null && rid.getLocalIdentifier()!=null && rid.getLocalIdentifier().getComponentIdentifier()!=null;
		
		final Future	ret	= new Future();
		final IComponentIdentifier	remote	= rid.getLocalIdentifier().getComponentIdentifier();
		SServiceProvider.getService(component.getExternalAccess(), IComponentManagementService.class, RequiredServiceInfo.SCOPE_PLATFORM)
			.addResultListener(new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(IComponentManagementService cms)
			{
				cms.getExternalAccess(remote).addResultListener(new ExceptionDelegationResultListener(ret)
				{
					public void customResultAvailable(IExternalAccess exta)
					{
						SServiceProvider.getService(exta, ILibraryService.class, RequiredServiceInfo.SCOPE_PLATFORM)
							.addResultListener(new ExceptionDelegationResultListener(ret)
						{
							public void customResultAvailable(ILibraryService ls)
							{
								ls.getResourceAsStream(rid)
									.addResultListener(new ExceptionDelegationResultListener(ret)
								{
									public void customResultAvailable(IInputConnection icon)
									{
										try
										{
											File	f	= getHashRidFile(rid);
											f.getParentFile().mkdirs();
											final OutputStream	os	= new BufferedOutputStream(new FileOutputStream(f));
											icon.writeToOutputStream(os, component.getExternalAccess())
												.addResultListener(new IIntermediateResultListener()
											{
												public void exceptionOccurred(Exception exception)
												{
													exception.printStackTrace();
													try
													{
														os.close();
													}
													catch(Exception e)
													{
														// ignore
													}
													ret.setException(null);
												}
												
												public void finished()
												{
//													System.out.println("finished");
													try
													{
														os.close();
													}
													catch(Exception e)
													{
														// ignore
													}
													ret.setResult(null);
												}
												
												public void intermediateResultAvailable(Long result)
												{
													// ignore
//													System.out.println("update: "+result);
												}
												
												public void resultAvailable(Collection result)
												{
													// should not be called.
												}
											});
										}
										catch(FileNotFoundException e)
										{
											throw new RuntimeException(e);
										}
									}
								});
							}
						});
					}
				});
			}
		});
		
		return ret;
	}
	
	/**
	 *  Get a resource as stream (jar).
	 */
	public IFuture	getResourceAsStream(IResourceIdentifier rid)
	{
		try
		{
			final InputStream	is;
			final File	file	= SUtil.getFile(getRidUrl(rid));
			
			if(file.isDirectory())
			{
				final PipedOutputStream	pos	= new PipedOutputStream();
				is	= new PipedInputStream(pos, 8192*4);
				
				SServiceProvider.getService(component.getExternalAccess(), IDaemonThreadPoolService.class, RequiredServiceInfo.SCOPE_PLATFORM)
					.addResultListener(new IResultListener()
				{
					public void resultAvailable(IDaemonThreadPoolService tps)
					{
						tps.execute(new Runnable()
						{
							public void run()
							{
								SUtil.writeDirectory(file, new BufferedOutputStream(pos));
//								try
//								{
//									is.close();
//								}
//								catch(IOException e)
//								{
//								}
							}
						});
					}
					
					public void exceptionOccurred(Exception exception)
					{
						// Shouldn't happen...
						exception.printStackTrace();
					}
				});
			}
			else
			{
				is	= new FileInputStream(file);
			}
			
			ServiceOutputConnection	soc	= new ServiceOutputConnection();
			soc.writeFromInputStream(is, component.getExternalAccess())
				.addResultListener(new IntermediateDefaultResultListener()
			{
				public void finished()
				{
					try
					{
						is.close();
					}
					catch(IOException e)
					{
						// ignore
					}
				}
				
				public void exceptionOccurred(Exception exception)
				{
					try
					{
						is.close();
					}
					catch(IOException e)
					{
						// ignore
					}
				}
			});
			
			return new Future(soc.getInputConnection());
		}
		catch(IOException e)
		{
			return new Future(e);
		}
	}

	
	/**
	 *  Get the dependent urls.
	 */
	protected IFuture>>> 
		getDependencies(final IResourceIdentifier rid, final boolean workspace)
	{
		final Future>>> ret = new Future>>>();
		
		component.getComponentFeature(IRequiredServicesFeature.class).searchService(IDependencyService.class, RequiredServiceInfo.SCOPE_PLATFORM)
			.addResultListener(new ExceptionDelegationResultListener>>>(ret)
		{
			public void customResultAvailable(IDependencyService drs)
			{
				drs.loadDependencies(rid, workspace).addResultListener(new DelegationResultListener>>>(ret));
			}
		});
		
		return ret;
	}
	
	/**
	 *  Add support for a rid.
	 */
	protected void addSupport(IResourceIdentifier rid, IResourceIdentifier parid)
	{
		if(rid==null)
			throw new IllegalArgumentException("Rid must not null.");
		checkLocalRid(rid);		
		checkLocalRid(parid);
		
		DelegationURLClassLoader pacl = parid==null || rootrid.equals(parid)? rootloader: (DelegationURLClassLoader)classloaders.get(parid);
		// special case that parid is local and already handled by baseloader
		if(pacl==null && ResourceIdentifier.isLocal(parid, component.getComponentIdentifier().getRoot()) && getInternalNonManagedURLs().contains(parid.getLocalIdentifier().getUri()))
		{
			pacl = rootloader;
		}
		DelegationURLClassLoader cl = (DelegationURLClassLoader)classloaders.get(rid);
		pacl.addDelegateClassLoader(cl);
		
		if(cl.addParentClassLoader(pacl))
		{
			rids = null;
			notifyAdditionListeners(parid, rid);
		}
		
		// Check if pending user entries can be restored
		for(Iterator> it=addtodo.iterator(); it.hasNext(); )
		{
			Tuple2 link = it.next();
			if(rid.equals(link.getFirstEntity()))
			{
				addResourceIdentifier(link.getFirstEntity(), link.getSecondEntity(), true);
				it.remove();
			}
		}
	}
	
	/**
	 *  Remove support for a rid.
	 */
	protected void removeSupport(IResourceIdentifier rid, IResourceIdentifier parid)
	{
		if(rid==null)
			throw new IllegalArgumentException("Rid must not null.");

		DelegationURLClassLoader pacl = parid==null || rootrid.equals(parid)? rootloader: (DelegationURLClassLoader)classloaders.get(parid);
		// special case that parid is local and already handled by baseloader
		if(pacl==null && ResourceIdentifier.isLocal(parid, component.getComponentIdentifier().getRoot()) && getInternalNonManagedURLs().contains(parid.getLocalIdentifier().getUri()))
		{
			pacl = rootloader;
		}
		DelegationURLClassLoader cl = (DelegationURLClassLoader)classloaders.get(rid);
		pacl.removeDelegateClassLoader(cl);
		if(cl.removeParentClassLoader(pacl))
		{
			rids = null;
			notifyRemovalListeners(parid, rid);
		}
		
		// If last support, delete removeSupport to children.
		if(!cl.hasParentClassLoader())
		{
			DelegationURLClassLoader[] dels = cl.getDelegateClassLoaders();
			for(DelegationURLClassLoader del: dels)
			{
				removeSupport(del.getResourceIdentifier(), rid);
			}
			classloaders.remove(rid);
		}
	}
	
	/**
	 *  Notify listeners about addition.
	 */
	protected void notifyAdditionListeners(final IResourceIdentifier parid, final IResourceIdentifier rid)
	{
		boolean rem = addedlinks.contains(new Tuple2(parid, rid));
		// Do not notify listeners with lock held!
		ILibraryServiceListener[] lis = (ILibraryServiceListener[])listeners.toArray(new ILibraryServiceListener[listeners.size()]);
		for(int i=0; i()
			{
				public void resultAvailable(Void result)
				{
				}
				public void exceptionOccurred(Exception exception) 
				{
					// todo: how to handle timeouts?! allow manual retry?
	//				exception.printStackTrace();
					removeLibraryServiceListener(liscopy);
				};
			});
		}
	}
	
	/**
	 *  Notify listeners about removal.
	 */
	protected void notifyRemovalListeners(final IResourceIdentifier parid, final IResourceIdentifier rid)
	{
		// Do not notify listeners with lock held!
		ILibraryServiceListener[] lis = (ILibraryServiceListener[])listeners.toArray(new ILibraryServiceListener[listeners.size()]);
		for(int i=0; i()
			{
				public void resultAvailable(Void result)
				{
				}
				public void exceptionOccurred(Exception exception) 
				{
					// todo: how to handle timeouts?! allow manual retry?
	//				exception.printStackTrace();
					removeLibraryServiceListener(liscopy);
				};
			});
		}
	}
	
	/**
	 *  Get the resource identifier for an url.
	 */
	protected IFuture internalGetResourceIdentifier(final URL url)
	{
		final Future ret = new Future();
		
		component.getComponentFeature(IRequiredServicesFeature.class).searchService(IDependencyService.class, RequiredServiceInfo.SCOPE_PLATFORM)
			.addResultListener(component.getComponentFeature(IExecutionFeature.class).createResultListener(new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(IDependencyService drs)
			{
				drs.getResourceIdentifier(url).addResultListener(new DelegationResultListener(ret));
			}
		}));
		
		return ret;
	}
	
	//-------- methods --------
	
	/**
	 *  Start the service.
	 */
	@ServiceStart
	public IFuture	startService()
	{
		try
		{
			this.rootrid = new ResourceIdentifier(new LocalResourceIdentifier(component.getComponentIdentifier(), new URL("http://ROOTRID")), null);
			this.rootloader.setResourceIdentifier(rootrid);
//			this.classloaders.put(rootrid, rootloader);
		}
		catch(Exception e)
		{
			// should not happen
			e.printStackTrace();
		}
		
		final Future	urlsdone	= new Future();
		if(initurls!=null)
		{
			CounterResultListener lis = new CounterResultListener(
				initurls.length, new DelegationResultListener(urlsdone));
			for(int i=0; i	ret	= new Future();
		urlsdone.addResultListener(new DelegationResultListener(ret)
		{
			public void customResultAvailable(Void result) 
			{
				component.getComponentFeature(IRequiredServicesFeature.class).searchService(ISettingsService.class, RequiredServiceInfo.SCOPE_PLATFORM)
					.addResultListener(new ExceptionDelegationResultListener(ret)
				{
					public void customResultAvailable(ISettingsService settings)
					{
						settings.registerPropertiesProvider(LIBRARY_SERVICE, LibraryService.this)
							.addResultListener(new DelegationResultListener(ret));
					}
					public void exceptionOccurred(Exception exception)
					{
						// No settings service: ignore
						ret.setResult(null);
					}
				});
			}
		});
		return ret;
	}

	/** 
	 *  Shutdown the service.
	 *  Releases all cached resources and shuts down the library service.
	 *  @param listener The listener.
	 */
	@ServiceShutdown
	public IFuture	shutdownService()
	{
//		System.out.println("shut");
		final Future	saved	= new Future();
		component.getComponentFeature(IRequiredServicesFeature.class).searchService(ISettingsService.class, RequiredServiceInfo.SCOPE_PLATFORM)
			.addResultListener(new ExceptionDelegationResultListener(saved)
		{
			public void customResultAvailable(ISettingsService settings)
			{
				settings.deregisterPropertiesProvider(LIBRARY_SERVICE)
					.addResultListener(new DelegationResultListener(saved));
			}
			public void exceptionOccurred(Exception exception)
			{
				// No settings service: ignore
				saved.setResult(null);
			}
		});
		
		final Future	ret	= new Future();
		saved.addResultListener(new DelegationResultListener(ret)
		{
			public void customResultAvailable(Void result)
			{
				synchronized(this)
				{
//					libcl = null;
					listeners.clear();
					ret.setResult(null);
				}
			}
		});
			
		return ret;
	}

	//-------- IPropertiesProvider interface --------
		
	/**
	 *  Collect all URLs belonging to a class loader.
	 */
	protected void	collectClasspathURLs(ClassLoader classloader, Set set, Set jarnames)
	{
		assert classloader!=null;
		
		if(classloader.getParent()!=null)
		{
			collectClasspathURLs(classloader.getParent(), set, jarnames);
		}
		
		if(classloader instanceof URLClassLoader)
		{
			URL[] urls = ((URLClassLoader)classloader).getURLs();
			for(int i=0; i set, Set jarnames)
	{
//		System.out.println("collectMainifestUrls: "+url);
		
		File	file	= SUtil.urlToFile(url.toString());
		if(file!=null && file.exists() && !file.isDirectory())	// Todo: load manifest also from directories!?
		{
			JarFile jarfile = null;
	        try 
	        {
	            jarfile	= new JarFile(file);
	            Manifest manifest = jarfile.getManifest();
	            if(manifest!=null)
	            {
	                String	classpath	= manifest.getMainAttributes().getValue(new Attributes.Name("Class-Path"));
	                if(classpath!=null)
	                {
	                	StringTokenizer	tok	= new StringTokenizer(classpath, " ");
	            		while(tok.hasMoreElements())
	            		{
	            			String path = tok.nextToken();
	            			File	urlfile;
	            			
	            			// Search in directory of original jar (todo: also search in local dir!?)
	            			urlfile = new File(file.getParentFile(), path);
	            			
	            			// Try as absolute path
	            			if(!urlfile.exists())
	            			{
	            				urlfile	= new File(path);
	            			}
	            			
	            			// Try as url
	            			if(!urlfile.exists())
	            			{
	            				urlfile	= SUtil.urlToFile(path);
	            			}
	
	            			if(urlfile!=null && urlfile.exists())
	            			{
		            			try
			                	{
		            				if(urlfile.getName().endsWith(".jar"))
		            				{
		            					String jarname	= getJarName(urlfile.getName());
		            					jarnames.add(jarname);
		            				}
		            				URL depurl = urlfile.toURI().toURL();
		            				if(set.add(depurl.toURI()))
		            				{
		            					collectManifestURLs(depurl.toURI(), set, jarnames);
		            				}
		            			}
		                    	catch (Exception e)
		                    	{
		                    		component.getLogger().warning("Error collecting manifest URLs for "+urlfile+": "+e);
		                    	}
	                    	}
	            			else if(!path.endsWith(".jar") || !jarnames.contains(getJarName(path)))
	            			{
	            				component.getLogger().info("Jar not found: "+file+", "+path);
	            			}
	               		}
	                }
	            }
		    }
		    catch(Exception e)
		    {
				component.getLogger().warning("Error collecting manifest URLs for "+url+": "+e);
		    }
	        finally
	        {
	        	try
	        	{
	        		if(jarfile!=null)
	        			jarfile.close();
	        	}
	        	catch(Exception e)
	        	{
	        	}
	        }
		}
	}
	
	/**
	 *  Add a link.
	 */
	protected void addLink(IResourceIdentifier parid, IResourceIdentifier rid)
	{
		Tuple2 link = new Tuple2(parid, rid);
		if(!removedlinks.remove(link))
		{
			addedlinks.add(link);
		}
	}
	
	/**
	 *  Remove a link.
	 */
	protected void removeLink(IResourceIdentifier parid, IResourceIdentifier rid)
	{
		Tuple2 link = new Tuple2(parid, rid);
		if(!addedlinks.remove(link))
		{
			removedlinks.add(link);
		}
	}
	
	/**
	 *  Get the removable links.
	 */
	public IFuture>> getRemovableLinks()
	{
		Set> ret = (Set>)((HashSet)addedlinks).clone();
		return new Future>>(ret);
	}

	/**
	 *  Update from given properties.
	 */
	public IFuture setProperties(Properties props)
	{
		Properties[] links = props.getSubproperties("link");
		
		Set> todo = new HashSet>();
		
		addtodo.clear();
		for(int i=0; i(a, b));
				}
				else
				{
					addtodo.add(new Tuple2(a, b));
				}
			}
		}
		
		for(Tuple2 link: todo)
		{
			addResourceIdentifier(link.getFirstEntity(), link.getSecondEntity(), true); // workspace?
		}
		
//		System.out.println("todo: "+todo);
//		System.out.println("addtodo: "+addtodo);
		
		return IFuture.DONE;
	}
	
	/**
	 *  Write current state into properties.
	 */
	public IFuture getProperties()
	{
		Properties props = new Properties();
		
		for(Tuple2 link: addedlinks)
		{
			Properties plink = new Properties();
			Properties a = ResourceIdentifier.ridToProperties(link.getFirstEntity(), component.getComponentIdentifier().getRoot());
			Properties b = ResourceIdentifier.ridToProperties(link.getSecondEntity(), component.getComponentIdentifier().getRoot());
			plink.addSubproperties("a", a);
			plink.addSubproperties("b", b);
			props.addSubproperties("link", plink);
		}
		
		return new Future(props);		
	}
		
	/**
	 *  Check if a local url is backed by a file.
	 */
	protected URL checkUri(URI uri)
	{
		try
		{
			return checkUrl(uri.toURL());
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	/**
	 *  Check if a local url is backed by a file.
	 */
	protected URL checkUrl(URL url)
	{
		URL ret = null;
		
		if("file".equals(url.getProtocol()))
		{
			File f = SUtil.getFile(url);
			if(f.exists())
			{
				try
				{
					ret = f.getCanonicalFile().toURI().toURL();
				}
				catch(Exception e)
				{
					throw new RuntimeException(e);
				}
			}
		}
		
		return ret;
	}
	
	/**
	 *  Get all managed resource identifiers inlcuding all subdependencies.
	 *  @return The resource identifiers.
	 */
	public Set internalgetAllResourceIdentifiers()
	{
		Set ret = new HashSet();
		Tuple2>> all = internalGetResourceIdentifiers();
		for(Map.Entry> entry: all.getSecondEntity().entrySet())
		{
			ret.add(entry.getKey());
			ret.addAll(entry.getValue());
		}
		ret.remove(null);
		return ret;
	}
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy