/** * Copyright (c) 2015, Lucee Assosication Switzerland. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.runtime.osgi; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import lucee.commons.io.IOUtil; import lucee.commons.io.SystemUtil; import lucee.commons.io.log.Log; import lucee.commons.io.res.Resource; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringList; import lucee.commons.lang.StringUtil; import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; import lucee.loader.osgi.BundleCollection; import lucee.loader.osgi.BundleUtil; import lucee.loader.util.Util; import lucee.runtime.config.Config; import lucee.runtime.config.ConfigWebUtil; import lucee.runtime.config.Identification; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.PageException; import lucee.runtime.op.Caster; import lucee.runtime.type.Array; import lucee.runtime.type.util.ListUtil; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Version; import org.osgi.framework.wiring.BundleRevision; import org.osgi.resource.Requirement; public class OSGiUtil { private static final FilenameFilter JAR_EXT_FILTER = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".jar"); } }; private static String[] bootDelegation; /** * only installs a bundle, if the bundle does not already exist, if the bundle exists the existing bundle is unloaded first. * @param factory * @param context * @param bundle * @return * @throws IOException * @throws BundleException */ public static Bundle installBundle(BundleContext context,Resource bundle, boolean checkExistence) throws IOException, BundleException { if(checkExistence) { BundleFile bf=new BundleFile(bundle); if(!bf.isBundle()) throw new BundleException(bundle+" is not a valid bundle!"); Bundle existing = loadBundleFromLocal(context,bf.getSymbolicName(),bf.getVersion(),false,null); if(existing!=null) return existing; } return _loadBundle(context, bundle.getAbsolutePath(), bundle.getInputStream(), true); } /** * does not check if the bundle already exists! * @param context * @param path * @param is * @param closeStream * @return * @throws BundleException */ private static Bundle _loadBundle(BundleContext context,String path, InputStream is, boolean closeStream) throws BundleException { log(Log.LEVEL_INFO,"add bundle:" + path); try { // we make this very simply so an old loader that is calling this still works return context.installBundle(path, is); } finally { // we make this very simply so an old loader that is calling this still works if(closeStream && is!=null){ try { is.close(); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } } } /** * only installs a bundle, if the bundle does not already exist, if the bundle exists the existing bundle is unloaded first. * the bundle is not stored physically on the system. * @param factory * @param context * @param bundle * @return * @throws IOException * @throws BundleException */ public static Bundle installBundle(BundleContext context,InputStream bundleIS,boolean closeStream, boolean checkExistence) throws IOException, BundleException { // store locally to test the bundle String name=System.currentTimeMillis()+".tmp"; Resource dir = SystemUtil.getTempDirectory(); Resource tmp = dir.getRealResource(name); int count=0; while(tmp.exists())tmp=dir.getRealResource((count++)+"_"+name); IOUtil.copy(bundleIS, tmp, closeStream); try { return installBundle(context, tmp,checkExistence); } finally{ tmp.delete(); } } public static Version toVersion(String version, Version defaultValue) { if(StringUtil.isEmpty(version)) return defaultValue; // String[] arr = ListUtil.listToStringArray(version, '.'); String[] arr; try { arr = ListUtil.toStringArrayTrim(ListUtil.listToArray(version.trim(), '.')); } catch (PageException e) { return defaultValue; // should not happen } Integer major,minor,micro; String qualifier; if(arr.length==1) { major=Caster.toInteger(arr[0],null); minor=0; micro=0; qualifier=null; } else if(arr.length==2) { major=Caster.toInteger(arr[0],null); minor=Caster.toInteger(arr[1],null); micro=0; qualifier=null; } else if(arr.length==3) { major=Caster.toInteger(arr[0],null); minor=Caster.toInteger(arr[1],null); micro=Caster.toInteger(arr[2],null); qualifier=null; } else { major=Caster.toInteger(arr[0],null); minor=Caster.toInteger(arr[1],null); micro=Caster.toInteger(arr[2],null); qualifier=arr[3]; } if(major==null || minor==null || micro==null) return defaultValue; if(qualifier==null) return new Version(major,minor,micro); return new Version(major,minor,micro,qualifier); } public static Version toVersion(String version) throws BundleException { Version v = toVersion(version,null); if(v!=null) return v; throw new BundleException("given version ["+version+"] is invalid, a valid version is following this pattern <major-number>.<minor-number>.<micro-number>[.<qualifier>]"); } private static Manifest getManifest(Resource bundle) throws IOException { InputStream is=null; Manifest mf=null; try{ is=bundle.getInputStream(); ZipInputStream zis = new ZipInputStream(is); ZipEntry entry; while ((entry = zis.getNextEntry()) != null && mf==null) { if("META-INF/MANIFEST.MF".equals(entry.getName())) { mf=new Manifest(zis); } zis.closeEntry(); } } finally { IOUtil.closeEL(is); } return mf; } /*public static FrameworkFactory getFrameworkFactory() throws Exception { ClassLoader cl = OSGiUtil.class.getClassLoader(); java.net.URL url = cl.getResource("META-INF/services/org.osgi.framework.launch.FrameworkFactory"); if (url != null) { BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream())); try { for (String s = br.readLine(); s != null; s = br.readLine()) { s = s.trim(); // Try to load first non-empty, non-commented line. if ((s.length() > 0) && (s.charAt(0) != '#')) { return (FrameworkFactory) ClassUtil.loadInstance(cl, s); } } } finally { if (br != null) br.close(); } } throw new Exception("Could not find framework factory."); }*/ /** * tries to load a class with ni bundle defintion * @param name * @param version * @param id * @param startIfNecessary * @return * @throws BundleException */ public static Class loadClass(String className, Class defaultValue) { className=className.trim(); CFMLEngine engine = CFMLEngineFactory.getInstance(); BundleCollection bc = engine.getBundleCollection(); // first we try to load the class from the Lucee core try { // load from core return bc.core.loadClass(className); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} // class is not visible to the Lucee core // now we check all started bundled (not only bundles used by core) Bundle[] bundles = bc.getBundleContext().getBundles(); for(Bundle b:bundles){ if(b==bc.core) continue; try { return b.loadClass(className); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} // class is not visible to that bundle } // now we check lucee loader (SystemClassLoader?) CFMLEngineFactory factory = engine.getCFMLEngineFactory(); try { //print.e("loader:"); return factory.getClass().getClassLoader().loadClass(className); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} /* try { return Class.forName(className); } catch (Throwable t3) { }*/ // now we check bundles not loaded Set<String> loaded=new HashSet<String>(); for(Bundle b:bundles){ loaded.add(b.getSymbolicName()+"|"+b.getVersion()); } try { File dir=factory.getBundleDirectory(); File[] children = dir.listFiles(JAR_EXT_FILTER); BundleFile bf; for(int i=0;i<children.length;i++){ try { bf=new BundleFile(children[i]); if(bf.isBundle() && !loaded.contains(bf.getSymbolicName()+"|"+bf.getVersion()) && bf.hasClass(className)) { Bundle b=null; try { b = _loadBundle(bc.getBundleContext(), bf.getFile()); } catch (IOException e) {} if(b!=null) { startIfNecessary(b); return b.loadClass(className); } } } catch(Throwable t2){ExceptionUtil.rethrowIfNecessary(t2);} } } catch(Throwable t1){ExceptionUtil.rethrowIfNecessary(t1);} return defaultValue; } public static Bundle loadBundle(BundleFile bf, Bundle defaultValue) { if(!bf.isBundle()) return defaultValue; try { return loadBundle(bf); } catch (Exception e) { return defaultValue; } } public static Bundle loadBundle(BundleFile bf) throws IOException, BundleException { CFMLEngine engine = CFMLEngineFactory.getInstance(); // check in loaded bundles BundleContext bc = engine.getBundleContext(); Bundle[] bundles = bc.getBundles(); for(Bundle b:bundles){ if(bf.getSymbolicName().equals(b.getSymbolicName())) { if(b.getVersion().equals(bf.getVersion())) return b; } } return _loadBundle(bc, bf.getFile()); } public static Bundle loadBundleByPackage(String packageName, List<VersionDefinition> versionDefinitions, Set<Bundle> loadedBundles, boolean startIfNecessary, Set<Bundle> parents) throws BundleException, IOException { CFMLEngine engine = CFMLEngineFactory.getInstance(); CFMLEngineFactory factory = engine.getCFMLEngineFactory(); // if part of bootdelegation we ignore if(OSGiUtil.isPackageInBootelegation(packageName)) { return null; } // is it in jar directory but not loaded File dir=factory.getBundleDirectory(); File[] children = dir.listFiles(JAR_EXT_FILTER); List<PackageDefinition> pds; for(File child:children) { BundleFile bf=new BundleFile(child); if(bf.isBundle()) { pds=toPackageDefinitions(bf.getExportPackage(), packageName, versionDefinitions); if(pds!=null && !pds.isEmpty()) { Bundle b=exists(loadedBundles,bf); if(b!=null) { if(startIfNecessary && !parents.contains(b)) _startIfNecessary(b,parents); return null; } b = loadBundle(bf); if(b!=null) { loadedBundles.add(b); if(startIfNecessary && !parents.contains(b))_startIfNecessary(b,parents); return b; } } } } return null; } private static Bundle exists(Set<Bundle> loadedBundles, BundleFile bf) { if(loadedBundles!=null) { Bundle b; Iterator<Bundle> it = loadedBundles.iterator(); while(it.hasNext()) { b=it.next(); if(b.getSymbolicName().equals(bf.getSymbolicName()) && b.getVersion().equals(bf.getVersion())) return b; } } return null; } private static Bundle exists(Set<Bundle> loadedBundles, BundleDefinition bd) { if(loadedBundles!=null) { Bundle b; Iterator<Bundle> it = loadedBundles.iterator(); while(it.hasNext()) { b=it.next(); if(b.getSymbolicName().equals(bd.getName()) && b.getVersion().equals(bd.getVersion())) return b; } } return null; } public static Bundle loadBundle(String name, Version version,Identification id, boolean startIfNecessary) throws BundleException { try { return _loadBundle(name, version, id, startIfNecessary,null); } catch (StartFailedException sfe) { throw sfe.bundleException; } } public static Bundle _loadBundle(String name, Version version,Identification id, boolean startIfNecessary, Set<Bundle> parents) throws BundleException, StartFailedException { name=name.trim(); CFMLEngine engine = CFMLEngineFactory.getInstance(); CFMLEngineFactory factory = engine.getCFMLEngineFactory(); // check in loaded bundles BundleContext bc = engine.getBundleContext(); Bundle[] bundles = bc.getBundles(); StringBuilder versionsFound=new StringBuilder(); for(Bundle b:bundles){ if(name.equalsIgnoreCase(b.getSymbolicName())) { if(version==null || version.equals(b.getVersion())) { if(startIfNecessary) { try{ _startIfNecessary(b,parents); } catch(BundleException be) { throw new StartFailedException(be,b); } } return b; } if(versionsFound.length()>0) versionsFound.append(", "); versionsFound.append(b.getVersion().toString()); } } // is it in jar directory but not loaded BundleFile bf = _getBundleFile(factory, name, version, versionsFound); if(bf!=null && bf.isBundle()) { Bundle b=null; try { b = _loadBundle(bc, bf.getFile()); } catch (IOException e) { e.printStackTrace(); } if(b!=null) { if(startIfNecessary){ try{ startIfNecessary(b); } catch(BundleException be) { throw new StartFailedException(be,b); } } return b; } } // if not found try to download if(version!=null) { try{ File f = factory.downloadBundle(name, version.toString(),id); Bundle b = _loadBundle(bc, f); if(startIfNecessary){ try{ start(b); } catch(BundleException be) { throw new StartFailedException(be,b); } } return b; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } String localDir=""; try { localDir = " ("+factory.getBundleDirectory()+")"; } catch (IOException e) {} String upLoc=""; try { upLoc = " ("+factory.getUpdateLocation()+")"; } catch (IOException e) {} if(versionsFound.length()>0) throw new BundleException("The OSGi Bundle with name ["+name+"] is not available in version ["+version+"] locally"+localDir+" or from the update provider"+upLoc+", the following versions are available locally ["+versionsFound+"]."); if(version!=null) throw new BundleException("The OSGi Bundle with name ["+name+"] in version ["+version+"] is not available locally"+localDir+" or from the update provider"+upLoc+"."); throw new BundleException("The OSGi Bundle with name ["+name+"] is not available locally"+localDir+" or from the update provider"+upLoc+"."); } private static List<PackageDefinition> toPackageDefinitions(String str, String filterPackageName, List<VersionDefinition> versionDefinitions) { if(StringUtil.isEmpty(str)) return null; StringTokenizer st=new StringTokenizer(str, ","); List<PackageDefinition> list=new ArrayList<PackageDefinition>(); PackageDefinition pd; while(st.hasMoreTokens()) { pd=toPackageDefinition(st.nextToken().trim(),filterPackageName,versionDefinitions); if(pd!=null) list.add(pd); } return list; } private static PackageDefinition toPackageDefinition(String str, String filterPackageName, List<VersionDefinition> versionDefinitions) { // first part is the package StringList list = ListUtil.toList(str, ';'); PackageDefinition pd=null; String token; Version v; while(list.hasNext()) { token=list.next().trim(); if(pd==null) { if(!token.equals(filterPackageName)) return null; pd=new PackageDefinition(token); } // only intressted in version else { StringList entry = ListUtil.toList(token, '='); if(entry.size()==2 && entry.next().trim().equalsIgnoreCase("version")) { String version=StringUtil.unwrap(entry.next().trim()); if(!version.equals("0.0.0")) { v = OSGiUtil.toVersion(version,null); if(v!=null) { if(versionDefinitions!=null) { Iterator<VersionDefinition> it = versionDefinitions.iterator(); while(it.hasNext()) { if(!it.next().matches(v)) { return null; } } } pd.setVersion(v); } } } } } return pd; } /** * this should be used when you not want to load a Bundle to the system * @param name * @param version * @param id only necessray if downloadIfNecessary is set to true * @param downloadIfNecessary * @return * @throws BundleException */ public static BundleFile getBundleFile(String name, Version version,Identification id, boolean downloadIfNecessary) throws BundleException { name=name.trim(); CFMLEngine engine = CFMLEngineFactory.getInstance(); CFMLEngineFactory factory = engine.getCFMLEngineFactory(); StringBuilder versionsFound=new StringBuilder(); // is it in jar directory but not loaded BundleFile bf=_getBundleFile(factory,name,version,versionsFound); if(bf!=null) return bf; // if not found try to download if(downloadIfNecessary && version!=null) { try{ bf=new BundleFile(factory.downloadBundle(name, version.toString(),id)); if(bf.isBundle()) return bf; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } if(versionsFound.length()>0) throw new BundleException("The OSGi Bundle with name ["+name+"] is not available in version ["+version+"] locally or from the update provider, the following versions are available locally ["+versionsFound+"]."); if(version!=null) throw new BundleException("The OSGi Bundle with name ["+name+"] in version ["+version+"] is not available locally or from the update provider."); throw new BundleException("The OSGi Bundle with name ["+name+"] is not available locally or from the update provider."); } public static boolean isNewerThan(Version left, Version right) { return Util.isNewerThan(left, right); } public static BundleFile getBundleFile(String name, Version version,Identification id, boolean downloadIfNecessary, BundleFile defaultValue) { name=name.trim(); CFMLEngine engine = CFMLEngineFactory.getInstance(); CFMLEngineFactory factory = engine.getCFMLEngineFactory(); StringBuilder versionsFound=new StringBuilder(); // is it in jar directory but not loaded BundleFile bf=_getBundleFile(factory,name,version,versionsFound); if(bf!=null) return bf; // if not found try to download if(downloadIfNecessary && version!=null) { try{ bf=new BundleFile(factory.downloadBundle(name, version.toString(),id)); if(bf.isBundle()) return bf; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } return defaultValue; } private static BundleFile _getBundleFile(CFMLEngineFactory factory, String name, Version version, StringBuilder versionsFound) { try{ File dir=factory.getBundleDirectory(); // first we check if there is a file match (fastest solution) if(version!=null){ File[] jars = new File[]{ new File(dir, name + "-"+ version.toString() + (".jar")), new File(dir, name + "-"+ version.toString().replace('.', '-') + (".jar")), new File(dir, name.replace('.', '-') + "-"+ version.toString().replace('.', '-') + (".jar")) }; for(int i=0;i<jars.length;i++) { File jar=jars[i]; if(jar.exists()) { BundleFile bf=new BundleFile(jar); if(bf.isBundle() && name.equalsIgnoreCase(bf.getSymbolicName())) { if(version.equals(bf.getVersion())) { return bf; } } } } } File[] children = dir.listFiles(JAR_EXT_FILTER); // now we make a closer filename test String curr; if(version!=null) { File match=null; String v=version.toString(); for(int i=0;i<children.length;i++){ curr=children[i].getName(); if( curr.equalsIgnoreCase(name+"-"+v.replace('-', '.')) || curr.equalsIgnoreCase(name.replace('.', '-')+"-"+v) || curr.equalsIgnoreCase(name.replace('.', '-')+"-"+v.replace('.', '-')) || curr.equalsIgnoreCase(name.replace('.', '-')+"-"+v.replace('-', '.')) || curr.equalsIgnoreCase(name.replace('-', '.')+"-"+v) || curr.equalsIgnoreCase(name.replace('-', '.')+"-"+v.replace('.', '-')) || curr.equalsIgnoreCase(name.replace('-', '.')+"-"+v.replace('-', '.')) ) { match=children[i]; break; } } if(match!=null) { BundleFile bf=new BundleFile(match); if(bf.isBundle() && name.equalsIgnoreCase(bf.getSymbolicName())) { if(version.equals(bf.getVersion())) { return bf; } } } } else { List<BundleFile> matches=new ArrayList<BundleFile>(); BundleFile bf; for(int i=0;i<children.length;i++){ curr=children[i].getName(); if( curr.startsWith(name+"-") || curr.startsWith(name.replace('-', '.')+"-") || curr.startsWith(name.replace('.', '-')+"-") ) { bf=new BundleFile(children[i]); if(bf.isBundle() && name.equalsIgnoreCase(bf.getSymbolicName())) { matches.add(bf); } } } if(!matches.isEmpty()) { bf=null; BundleFile _bf; Iterator<BundleFile> it = matches.iterator(); while(it.hasNext()) { _bf=it.next(); if(bf==null || Util.isNewerThan(_bf.getVersion(),bf.getVersion())) bf=_bf; } if(bf!=null) { return bf; } } } // now we check by Manifest comparsion BundleFile bf; for(int i=0;i<children.length;i++){ bf=new BundleFile(children[i]); if(bf.isBundle() && name.equalsIgnoreCase(bf.getSymbolicName())) { if(version==null || version.equals(bf.getVersion())) { return bf; } if(versionsFound!=null) { if(versionsFound.length()>0) versionsFound.append(", "); versionsFound.append(bf.getVersionAsString()); } } } } catch(Exception e){} return null; } /** * get all local bundles (even bundles not loaded/installed) * @param name * @param version * @return */ public static List<BundleDefinition> getBundleDefinitions() { CFMLEngine engine = ConfigWebUtil.getEngine(ThreadLocalPageContext.getConfig()); return getBundleDefinitions(engine.getBundleContext()); } public static List<BundleDefinition> getBundleDefinitions(BundleContext bc) { Set<String> set=new HashSet<>(); List<BundleDefinition> list=new ArrayList<>(); Bundle[] bundles = bc.getBundles(); for(Bundle b:bundles){ list.add(new BundleDefinition(b)); set.add(b.getSymbolicName()+":"+b.getVersion()); } // is it in jar directory but not loaded CFMLEngine engine = ConfigWebUtil.getEngine(ThreadLocalPageContext.getConfig()); CFMLEngineFactory factory = engine.getCFMLEngineFactory(); try{ File[] children = factory.getBundleDirectory().listFiles(JAR_EXT_FILTER); BundleFile bf; for(int i=0;i<children.length;i++){ try { bf=new BundleFile(children[i]); if(bf.isBundle() && !set.contains(bf.getSymbolicName()+":"+bf.getVersion())) list.add(new BundleDefinition(bf.getSymbolicName(),bf.getVersion())); } catch(Throwable t){ExceptionUtil.rethrowIfNecessary(t);} } } catch(IOException ioe){} return list; } public static Bundle getBundleLoaded(String name, Version version, Bundle defaultValue) { CFMLEngine engine = ConfigWebUtil.getEngine(ThreadLocalPageContext.getConfig()); return getBundleLoaded(engine.getBundleContext(), name, version, defaultValue); } public static Bundle getBundleLoaded(BundleContext bc,String name, Version version, Bundle defaultValue) { name=name.trim(); Bundle[] bundles = bc.getBundles(); for(Bundle b:bundles){ if(name.equalsIgnoreCase(b.getSymbolicName())) { if(version==null || version.equals(b.getVersion())) { return b; } } } return defaultValue; } public static Bundle loadBundleFromLocal(String name, Version version, boolean loadIfNecessary, Bundle defaultValue) { CFMLEngine engine = ConfigWebUtil.getEngine(ThreadLocalPageContext.getConfig()); return loadBundleFromLocal(engine.getBundleContext(), name, version,loadIfNecessary, defaultValue); } public static Bundle loadBundleFromLocal(BundleContext bc,String name, Version version, boolean loadIfNecessary, Bundle defaultValue) { name=name.trim(); Bundle[] bundles = bc.getBundles(); for(Bundle b:bundles){ if(name.equalsIgnoreCase(b.getSymbolicName())) { if(version==null || version.equals(b.getVersion())) { return b; } } } if(!loadIfNecessary) return defaultValue; // is it in jar directory but not loaded CFMLEngine engine = ConfigWebUtil.getEngine(ThreadLocalPageContext.getConfig()); CFMLEngineFactory factory = engine.getCFMLEngineFactory(); BundleFile bf = _getBundleFile(factory, name, version, null); if(bf!=null) { try { return _loadBundle(bc, bf.getFile()); } catch (Exception e) {} } return defaultValue; } /** * get local bundle, but does not download from update provider! * @param name * @param version * @return * @throws BundleException */ public static void removeLocalBundle(String name, Version version, boolean removePhysical, boolean doubleTap) throws BundleException { name=name.trim(); CFMLEngine engine = CFMLEngineFactory.getInstance(); CFMLEngineFactory factory = engine.getCFMLEngineFactory(); BundleFile bf = _getBundleFile(factory, name, version, null); if(bf!=null) { BundleDefinition bd = bf.toBundleDefinition(); if(bd!=null) { Bundle b = bd.getLocalBundle(); if(b!=null) { stopIfNecessary(b); b.uninstall(); } } } if(!removePhysical) return; // remove file if(bf!=null) { if(!bf.getFile().delete() && doubleTap) bf.getFile().deleteOnExit(); } } public static void removeLocalBundleSilently(String name, Version version, boolean removePhysical) { try { removeLocalBundle(name, version, removePhysical, true); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } // bundle stuff public static void startIfNecessary(Bundle[] bundles) throws BundleException { for(Bundle b:bundles){ startIfNecessary(b); } } public static Bundle startIfNecessary(Bundle bundle) throws BundleException { return _startIfNecessary(bundle, null); } private static Bundle _startIfNecessary(Bundle bundle, Set<Bundle> parents) throws BundleException { if(bundle.getState()==Bundle.ACTIVE) return bundle; return _start(bundle,parents); } public static Bundle start(Bundle bundle) throws BundleException { return _start(bundle, null); } public static Bundle _start(Bundle bundle, Set<Bundle> parents) throws BundleException { String fh = bundle.getHeaders().get("Fragment-Host"); if (!Util.isEmpty(fh)) { log(Log.LEVEL_INFO, "do not start [" + bundle.getSymbolicName() + "], because this is a fragment bundle for [" + fh + "]"); return bundle; } log(Log.LEVEL_INFO, "start bundle:" + bundle.getSymbolicName() + ":" + bundle.getVersion().toString()); try { BundleUtil.start(bundle); } catch(BundleException be){ // check if required related bundles are missing and load them if necessary final List<BundleDefinition> failedBD = new ArrayList<OSGiUtil.BundleDefinition>(); Set<Bundle> loadedBundles = new HashSet<Bundle>(); loadedBundles.add(bundle); if(parents==null) parents=new HashSet<Bundle>(); loadBundles(parents,loadedBundles,bundle,failedBD); try { //startIfNecessary(loadedBundles.toArray(new Bundle[loadedBundles.size()])); BundleUtil.start(bundle); } catch(BundleException be2) { List<PackageQuery> listPackages = getRequiredPackages(bundle); List<PackageQuery> failedPD = new ArrayList<PackageQuery>(); loadPackages(parents,loadedBundles,listPackages,bundle,failedPD); try { //startIfNecessary(loadedBundles.toArray(new Bundle[loadedBundles.size()])); BundleUtil.start(bundle); } catch(BundleException be3) { if(failedBD.size()>0) { Iterator<BundleDefinition> itt = failedBD.iterator(); BundleDefinition _bd; StringBuilder sb=new StringBuilder(" Lucee was not able to download the following bundles ["); while(itt.hasNext()){ _bd=itt.next(); sb.append(_bd.name+":"+_bd.getVersionAsString()).append(';'); } sb.append("]"); throw new BundleException(be2.getMessage()+sb,be2.getCause()); } throw be3; } } } return bundle; } private static void loadPackages(final Set<Bundle> parents, final Set<Bundle> loadedBundles, List<PackageQuery> listPackages, final Bundle bundle, final List<PackageQuery> failedPD) { PackageQuery pq; Iterator<PackageQuery> it = listPackages.iterator(); while(it.hasNext()){ pq=it.next(); try{ //if(parents==null) parents=new HashSet<Bundle>(); parents.add(bundle); loadBundleByPackage(pq.getName(),pq.getVersionDefinitons(),loadedBundles,true,parents); } catch(Exception _be){ //if(failedPD==null) failedPD=new ArrayList<OSGiUtil.PackageQuery>(); failedPD.add(pq); log(_be); } } } private static void loadBundles(final Set<Bundle> parents, final Set<Bundle> loadedBundles, final Bundle bundle, final List<BundleDefinition> failedBD) throws BundleException { List<BundleDefinition> listBundles = getRequiredBundles(bundle); Bundle b; BundleDefinition bd; Iterator<BundleDefinition> it = listBundles.iterator(); List<StartFailedException> secondChance = null; while(it.hasNext()) { bd=it.next(); b=exists(loadedBundles, bd); if(b!=null) { startIfNecessary(b); continue; } try{ //if(parents==null) parents=new HashSet<Bundle>(); parents.add(bundle); b=_loadBundle( bd.name, bd.getVersion(), ThreadLocalPageContext .getConfig() .getIdentification(), true,parents); loadedBundles.add(b); } catch(StartFailedException sfe) { sfe.setBundleDefinition(bd); if(secondChance==null) secondChance=new ArrayList<StartFailedException>(); secondChance.add(sfe); } catch(BundleException _be) { //if(failedBD==null) failedBD=new ArrayList<OSGiUtil.BundleDefinition>(); failedBD.add(bd); log(_be); } } // we do this because it maybe was relaying on other bundles now loaded // TODO rewrite the complete impl so didd is not necessary if(secondChance!=null) { Iterator<StartFailedException> _it = secondChance.iterator(); StartFailedException sfe; while(_it.hasNext()) { sfe = _it.next(); try { start(sfe.bundle); loadedBundles.add(sfe.bundle); } catch(BundleException _be) { //if(failedBD==null) failedBD=new ArrayList<OSGiUtil.BundleDefinition>(); failedBD.add(sfe.getBundleDefinition()); log(_be); } } } } public static void stopIfNecessary(Bundle bundle) throws BundleException { if(isFragment(bundle) || bundle.getState()!=Bundle.ACTIVE) return; stop(bundle); } public static void stop(Bundle b) throws BundleException { b.stop(); } public static void uninstall(Bundle b) throws BundleException { b.uninstall(); } public static boolean isFragment(Bundle bundle) { return (bundle.adapt(BundleRevision.class).getTypes() & BundleRevision.TYPE_FRAGMENT) != 0; } public static boolean isFragment(BundleFile bf) { return !StringUtil.isEmpty(bf.getFragementHost(),true); } public static List<BundleDefinition> getRequiredBundles(Bundle bundle) throws BundleException { List<BundleDefinition> rtn=new ArrayList<BundleDefinition>(); BundleRevision br = bundle.adapt(BundleRevision.class); List<Requirement> requirements = br.getRequirements(null); Iterator<Requirement> it = requirements.iterator(); Requirement r; Entry<String, String> e; String value,name; int index,start,end,op; BundleDefinition bd; while(it.hasNext()){ r = it.next(); Iterator<Entry<String, String>> iit = r.getDirectives().entrySet().iterator(); while(iit.hasNext()){ e = iit.next(); if(!"filter".equals(e.getKey())) continue; value=e.getValue(); // name index=value.indexOf("(osgi.wiring.bundle"); if(index==-1) continue; start=value.indexOf('=',index); end=value.indexOf(')',index); if(start==-1 || end==-1 || end<start) continue; name=value.substring(start+1,end).trim(); rtn.add(bd=new BundleDefinition(name)); // version op=-1; index=value.indexOf("(bundle-version"); if(index==-1) continue; end=value.indexOf(')',index); start=value.indexOf("<=",index); if(start!=-1 && start<end) { op=VersionDefinition.LTE; start+=2; } else { start=value.indexOf(">=",index); if(start!=-1 && start<end) { op=VersionDefinition.GTE; start+=2; } else { start=value.indexOf("=",index); if(start!=-1 && start<end) { op=VersionDefinition.EQ; start++; } } } if(op==-1 || start==-1 || end==-1 || end<start) continue; bd.setVersion(op,value.substring(start,end).trim()); } } return rtn; // (&(osgi.wiring.bundle=slf4j.api)(bundle-version>=1.6.4)) } public static List<PackageQuery> getRequiredPackages(Bundle bundle) throws BundleException { List<PackageQuery> rtn=new ArrayList<PackageQuery>(); BundleRevision br = bundle.adapt(BundleRevision.class); List<Requirement> requirements = br.getRequirements(null); Iterator<Requirement> it = requirements.iterator(); Requirement r; Entry<String, String> e; String value; PackageQuery pd; while(it.hasNext()){ r = it.next(); Iterator<Entry<String, String>> iit = r.getDirectives().entrySet().iterator(); inner:while(iit.hasNext()){ e = iit.next(); if(!"filter".equals(e.getKey())) continue; value=e.getValue(); pd=toPackageQuery(value); if(pd!=null)rtn.add(pd); } } return rtn; } private static PackageQuery toPackageQuery(String value) throws BundleException { // name(&(osgi.wiring.package=org.jboss.logging)(version>=3.3.0)(!(version>=4.0.0))) int index=value.indexOf("(osgi.wiring.package"); if(index==-1) { return null; } int start=value.indexOf('=',index); int end=value.indexOf(')',index); if(start==-1 || end==-1 || end<start) { return null; } String name=value.substring(start+1,end).trim(); PackageQuery pd = new PackageQuery(name); int last=end,op; boolean not; // version while((index=value.indexOf("(version",last))!=-1) { op=-1; end=value.indexOf(')',index); start=value.indexOf("<=",index); if(start!=-1 && start<end) { op=VersionDefinition.LTE; start+=2; } else { start=value.indexOf(">=",index); if(start!=-1 && start<end) { op=VersionDefinition.GTE; start+=2; } else { start=value.indexOf("==",index); if(start!=-1 && start<end) { op=VersionDefinition.EQ; start+=2; } else { start=value.indexOf("!=",index); if(start!=-1 && start<end) { op=VersionDefinition.NEQ; start+=2; } else { start=value.indexOf("=",index); if(start!=-1 && start<end) { op=VersionDefinition.EQ; start+=1; } else { start=value.indexOf("<",index); if(start!=-1 && start<end) { op=VersionDefinition.LT; start+=1; } else { start=value.indexOf(">",index); if(start!=-1 && start<end) { op=VersionDefinition.GT; start+=1; } } } } } } } not=value.charAt(index-1)=='!'; last=end; if(op==-1 || start==-1 || end==-1 || end<start) continue; pd.addVersion(op,value.substring(start,end).trim(),not); } return pd; } private static Bundle _loadBundle(BundleContext context, File bundle) throws IOException, BundleException { return _loadBundle(context, bundle.getAbsolutePath(),new FileInputStream(bundle),true); } public static class VersionDefinition implements Serializable { private static final long serialVersionUID = 4915024473510761950L; public static final int LTE = 1; public static final int GTE = 2; public static final int EQ = 4; public static final int LT = 8; public static final int GT = 16; public static final int NEQ = 32; private Version version; private int op; public VersionDefinition(Version version, int op, boolean not) { this.version=version; if(not) { if(op==LTE) {op=GT;not=false;} else if(op==LT) {op=GTE;not=false;} else if(op==GTE) {op=LT;not=false;} else if(op==GT) {op=LTE;not=false;} else if(op==EQ) {op=NEQ;not=false;} else if(op==NEQ) {op=EQ;not=false;} } this.op=op; } public boolean matches(Version v) { if(EQ==op) return v.compareTo(version)==0; if(LTE==op) return v.compareTo(version)<=0; if(LT==op) return v.compareTo(version)<0; if(GTE==op) return v.compareTo(version)>=0; if(GT==op) return v.compareTo(version)>0; if(NEQ==op) return v.compareTo(version)!=0; return false; } public Version getVersion() { return version; } public int getOp() { return op; } public String getVersionAsString() { return version==null?null:version.toString(); } public String toString() { StringBuilder sb=new StringBuilder("version "); sb.append(getOpAsString()).append(' ') .append(version); return sb.toString(); } public String getOpAsString() { switch(getOp()){ case EQ:return "EQ"; case LTE:return "LTE"; case GTE:return "GTE"; case NEQ:return "NEQ"; case LT:return "LT"; case GT:return "GT"; } return null; } } public static class PackageQuery { private final String name; private List<VersionDefinition> versions=new ArrayList<OSGiUtil.VersionDefinition>(); public PackageQuery(String name) { this.name=name; } public void addVersion(int op, String version, boolean not) throws BundleException { versions.add(new VersionDefinition(OSGiUtil.toVersion(version),op,not)); } public String getName() { return name; } public List<VersionDefinition> getVersionDefinitons() { return versions; } public String toString() { StringBuilder sb=new StringBuilder(); sb.append("name:").append(name); Iterator<VersionDefinition> it = versions.iterator(); while(it.hasNext()) { sb.append(';').append(it.next()); } return sb.toString(); } } public static class PackageDefinition { private final String name; private Version version; public PackageDefinition(String name) { this.name=name; } public void setVersion(String version) throws BundleException { this.version=OSGiUtil.toVersion(version); } public void setVersion(Version version) { this.version=version; } public String getName() { return name; } public Version getVersion() { return version; } public String toString() { StringBuilder sb=new StringBuilder(); sb.append("name:").append(name); sb.append("version:").append(version); return sb.toString(); } } public static class BundleDefinition implements Serializable { private final String name; private Bundle bundle; private VersionDefinition versionDef; public BundleDefinition(String name) { this.name=name; } public BundleDefinition(String name, String version) throws BundleException { this.name=name; if(name==null) throw new IllegalArgumentException("name cannot be null"); setVersion(VersionDefinition.EQ, version); } public BundleDefinition(String name, Version version) { this.name=name; if(name==null) throw new IllegalArgumentException("name cannot be null"); setVersion(VersionDefinition.EQ, version); } public BundleDefinition(Bundle bundle) { this.name=bundle.getSymbolicName(); if(name==null) throw new IllegalArgumentException("name cannot be null"); setVersion(VersionDefinition.EQ, bundle.getVersion()); this.bundle=bundle; } public String getName() { return name; } /** * only return a bundle if already loaded, does not load the bundle * @return */ public Bundle getLoadedBundle() { return bundle; } /** * get Bundle, also load if necessary from local or remote * @return * @throws BundleException * @throws StartFailedException */ public Bundle getBundle(Config config) throws BundleException { if(bundle==null) { config = ThreadLocalPageContext.getConfig(config); bundle=OSGiUtil.loadBundle(name, getVersion(), config==null?null:config.getIdentification(), false); } return bundle; } public Bundle getLocalBundle() { if(bundle==null) { bundle=OSGiUtil.loadBundleFromLocal(name, getVersion(),true, null); } return bundle; } public BundleFile getBundleFile(boolean downloadIfNecessary) throws BundleException { Config config = ThreadLocalPageContext.getConfig(); return OSGiUtil.getBundleFile(name, getVersion(), config==null?null:config.getIdentification(),downloadIfNecessary); } public int getOp() { return versionDef==null?VersionDefinition.EQ:versionDef.getOp(); } public Version getVersion() { return versionDef==null?null:versionDef.getVersion(); } public VersionDefinition getVersionDefiniton() { return versionDef; } public String getVersionAsString() { return versionDef==null?null:versionDef.getVersionAsString(); } public void setVersion(int op, String version) throws BundleException { setVersion(op,OSGiUtil.toVersion(version)); } public void setVersion(int op, Version version) { this.versionDef=new VersionDefinition(version, op, false); } @Override public String toString() { return "name:"+name+";version:"+versionDef+";"; } @Override public boolean equals(Object obj){ if(this==obj) return true; if(!(obj instanceof BundleDefinition)) return false; return toString().equals(obj.toString()); } } private static void log(int level, String msg) { try { Config config = ThreadLocalPageContext.getConfig(); Log log = config!=null?config.getLog("application"):null; if(log!=null) log.log(level, "OSGi", msg); } catch(Throwable t) { ExceptionUtil.rethrowIfNecessary(t); /* this can fail when called from an old loader */ System.out.println(msg); } } private static void log(Throwable t) { try { Config config = ThreadLocalPageContext.getConfig(); Log log = config!=null?config.getLog("application"):null; if(log!=null) log.log(Log.LEVEL_ERROR, "OSGi", t); } catch(Throwable _t) { ExceptionUtil.rethrowIfNecessary(_t); /* this can fail when called from an old loader */ System.out.println(t.getMessage()); } } public static String toState(int state, String defaultValue) { switch(state){ case Bundle.ACTIVE: return "active"; case Bundle.INSTALLED: return "installed"; case Bundle.UNINSTALLED: return "uninstalled"; case Bundle.RESOLVED: return "resolved"; case Bundle.STARTING: return "starting"; case Bundle.STOPPING: return "stopping"; } return defaultValue; } /** * value can be a String (for a single entry) or a List<String> for multiple entries * @param b * @return */ public static Map<String,Object> getHeaders(Bundle b) { Dictionary<String, String> headers = b.getHeaders(); Enumeration<String> keys = headers.keys(); Enumeration<String> values = headers.elements(); String key,value; Object existing; List<String> list; Map<String, Object> _headers=new HashMap<String, Object>(); while(keys.hasMoreElements()){ key=keys.nextElement(); value=StringUtil.unwrap(values.nextElement()); existing = _headers.get(key); if(existing!=null) { if(existing instanceof String) { list=new ArrayList<>(); list.add((String)existing); _headers.put(key, list); } else list=(List<String>) existing; list.add(value); } else _headers.put(key, value); } return _headers; } public static String[] getBootdelegation() { if(bootDelegation==null) { InputStream is = null; try { Properties prop = new Properties(); is = OSGiUtil.class.getClassLoader().getResourceAsStream("default.properties"); prop.load(is); String bd = prop.getProperty("org.osgi.framework.bootdelegation"); if(!StringUtil.isEmpty(bd)) { bd+=",java.lang,java.lang.*"; bootDelegation=ListUtil.trimItems(ListUtil.listToStringArray(StringUtil.unwrap(bd),',')); } } catch(IOException ioe){ } finally { IOUtil.closeEL(is); } } if(bootDelegation==null) return new String[0]; return bootDelegation; } public static boolean isClassInBootelegation(String className) { return isInBootelegation(className, false); } public static boolean isPackageInBootelegation(String className) { return isInBootelegation(className, true); } private static boolean isInBootelegation(String name, boolean isPackage) { // extract package String pack; if(isPackage) pack=name; else { int index=name.lastIndexOf('.'); if(index==-1) return false; pack=name.substring(0,index); } String[] arr = OSGiUtil.getBootdelegation(); for(String bd:arr){ bd=bd.trim(); // with wildcard if(bd.endsWith(".*")) { bd=bd.substring(0,bd.length()-1); if(pack.startsWith(bd)) return true; } // no wildcard else { if(bd.equals(pack)) return true; } } return false; } public static BundleDefinition[] toBundleDefinitions( BundleInfo[] bundles) { if(bundles==null) return new BundleDefinition[0]; BundleDefinition[] rtn=new BundleDefinition[bundles.length]; for(int i=0;i<bundles.length;i++){ rtn[i]=bundles[i].toBundleDefinition(); } return rtn; } public static Bundle getFrameworkBundle(Config config, Bundle defaultValue) { Bundle[] bundles = ConfigWebUtil.getEngine(config).getBundleContext().getBundles(); Bundle b=null; for(int i=0;i<bundles.length;i++) { b=bundles[i]; if(b!=null && isFrameworkBundle(b)) return b; } return defaultValue; } public static boolean isFrameworkBundle(Bundle b) {// FELIX specific return "org.apache.felix.framework".equalsIgnoreCase(b.getSymbolicName()); // TODO move to cire util class tha does not exist yet } }