/**
* 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.loader.osgi;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import lucee.loader.engine.CFMLEngineFactory;
import lucee.loader.engine.CFMLEngineFactorySupport;
import lucee.loader.util.Util;
import org.apache.felix.framework.Felix;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
public class BundleLoader {
/**
* build (if necessary) a bundle and load it
*
* @param engFac
* @param cacheRootDir
* @param jarDirectory
* @param rc
* @param old
* @return
* @throws IOException
* @throws BundleException
*/
public static BundleCollection loadBundles(final CFMLEngineFactory engFac,
final File cacheRootDir, final File jarDirectory, final File rc,
final BundleCollection old) throws IOException, BundleException {
final JarFile jf = new JarFile(rc);// TODO this should work in any case, but we should still improve this code
try {
// Manifest
final Manifest mani = jf.getManifest();
if (mani == null)
throw new IOException("lucee core [" + rc
+ "] is invalid, there is no META-INF/MANIFEST.MF File");
final Attributes attrs = mani.getMainAttributes();
// default properties
final Properties defProp = loadDefaultProperties(jf);
// Get data from Manifest and default.properties
// Lucee Core Version
//String rcv = unwrap(defProp.getProperty("lucee.core.version"));
//if(Util.isEmpty(rcv)) throw new IOException("lucee core ["+rc+"] is invalid, no core version is defined in the {Lucee-Core}/default.properties File");
//int version = CFMLEngineFactory.toInVersion(rcv);
// read the config from default.properties
final Map<String, Object> config = new HashMap<String, Object>();
{
final Iterator<Entry<Object, Object>> it = defProp.entrySet()
.iterator();
Entry<Object, Object> e;
String k;
while (it.hasNext()) {
e = it.next();
k = (String) e.getKey();
if (!k.startsWith("org.") && !k.startsWith("felix."))
continue;
config.put(
k,
CFMLEngineFactorySupport.removeQuotes(
(String) e.getValue(), true));
}
}
// close all bundles
Felix felix;
if (old != null) {
removeBundlesEL(old);
felix = old.felix;
// stops felix (wait for it)
BundleUtil.stop(felix,false);
felix = engFac.getFelix(cacheRootDir, config);
} else
felix = engFac.getFelix(cacheRootDir, config);
final BundleContext bc = felix.getBundleContext();
// get bundle needed for that core
final String rb = attrs.getValue("Require-Bundle");
if (Util.isEmpty(rb))
throw new IOException(
"lucee core ["
+ rc
+ "] is invalid, no Require-Bundle defintion found in the META-INF/MANIFEST.MF File");
// get fragments needed for that core (Lucee specific Key)
final String rbf = attrs.getValue("Require-Bundle-Fragment");
// load Required/Available Bundles
final Map<String, String> requiredBundles = readRequireBundle(rb); // Require-Bundle
final Map<String, String> requiredBundleFragments = readRequireBundle(rbf); // Require-Bundle-Fragment
final Map<String, File> availableBundles = loadAvailableBundles(jarDirectory);
// deploys bundled bundles to bundle directory
//deployBundledBundles(jarDirectory, availableBundles);
// Add Required Bundles
Entry<String, String> e;
File f;
String id;
final List<Bundle> bundles = new ArrayList<Bundle>();
Iterator<Entry<String, String>> it = requiredBundles.entrySet()
.iterator();
while (it.hasNext()) {
e = it.next();
id = e.getKey() + "|" + e.getValue();
f = availableBundles.get(id);
//StringBuilder sb=new StringBuilder();
if (f == null) {
/*sb.append(id+"\n");
Iterator<String> _it = availableBundles.keySet().iterator();
while(_it.hasNext()){
sb.append("- "+_it.next()+"\n");
}
throw new RuntimeException(sb.toString());*/
}
if (f == null)
f = engFac.downloadBundle(e.getKey(), e.getValue(), null);
bundles.add(BundleUtil.addBundle(engFac, bc, f, null));
}
// Add Required Bundle Fragments
final List<Bundle> fragments = new ArrayList<Bundle>();
it = requiredBundleFragments.entrySet().iterator();
while (it.hasNext()) {
e = it.next();
id = e.getKey() + "|" + e.getValue();
f = availableBundles.get(id);
if (f == null)
f = engFac.downloadBundle(e.getKey(), e.getValue(), null); // if identification is not defined, it is loaded from the CFMLEngine
fragments.add(BundleUtil.addBundle(engFac, bc, f, null));
}
// Add Lucee core Bundle
Bundle bundle;
//bundles.add(bundle = BundleUtil.addBundle(engFac, bc, rc,null));
bundle = BundleUtil.addBundle(engFac, bc, rc, null);
// Start the bundles
BundleUtil.start(engFac, bundles);
BundleUtil.start(engFac, bundle);
return new BundleCollection(felix, bundle, bundles);
} finally {
if (jf != null)
try {
jf.close();
} catch (final IOException ioe) {
}
}
}
private static Map<String, File> loadAvailableBundles(
final File jarDirectory) {
final Map<String, File> rtn = new HashMap<String, File>();
final File[] jars = jarDirectory.listFiles();
if(jars!=null)for (int i = 0; i < jars.length; i++) {
if (!jars[i].isFile() || !jars[i].getName().endsWith(".jar"))
continue;
try {
rtn.put(loadBundleInfo(jars[i]), jars[i]);
}
catch (final Throwable t) {
}
}
return rtn;
}
public static String loadBundleInfo(final File jar) throws IOException {
JarFile jf = new JarFile(jar);
try {
Attributes attrs = jf.getManifest().getMainAttributes();
String symbolicName = attrs.getValue("Bundle-SymbolicName");
String version = attrs.getValue("Bundle-Version");
if (Util.isEmpty(symbolicName))
throw new IOException("OSGi bundle ["+jar+"] is invalid, {Lucee-Core}META-INF/MANIFEST.MF does not contain a \"Bundle-SymbolicName\"");
if (Util.isEmpty(version))
throw new IOException("OSGi bundle ["+jar+"] is invalid, {Lucee-Core}META-INF/MANIFEST.MF does not contain a \"Bundle-Version\"");
return symbolicName + "|" + version;
}
finally {
Util.closeEL(jf);
}
}
private static Map<String, String> readRequireBundle(final String rb)
throws IOException {
final HashMap<String, String> rtn = new HashMap<String, String>();
final StringTokenizer st = new StringTokenizer(rb, ",");
StringTokenizer stl;
String line, jarName, jarVersion = null, token;
int index;
while (st.hasMoreTokens()) {
line = st.nextToken().trim();
if (Util.isEmpty(line))
continue;
stl = new StringTokenizer(line, ";");
// first is the name
jarName = stl.nextToken().trim();
while (stl.hasMoreTokens()) {
token = stl.nextToken().trim();
if (token.startsWith("bundle-version")
&& (index = token.indexOf('=')) != -1)
jarVersion = token.substring(index + 1).trim();
}
if (jarVersion == null)
throw new IOException(
"missing \"bundle-version\" info in the following \"Require-Bundle\" record: \""
+ jarName + "\"");
rtn.put(jarName, jarVersion);
}
return rtn;
}
/*private static String unwrap(String str) {
return str == null ? null : CFMLEngineFactory.removeQuotes(str, true);
}*/
public static Properties loadDefaultProperties(final JarFile jf)
throws IOException {
final ZipEntry ze = jf.getEntry("default.properties");
if (ze == null)
throw new IOException(
"the Lucee core has no default.properties file!");
final Properties prop = new Properties();
InputStream is = null;
try {
is = jf.getInputStream(ze);
prop.load(is);
} finally {
CFMLEngineFactorySupport.closeEL(is);
}
return prop;
}
public static void removeBundles(final BundleContext bc)
throws BundleException {
final Bundle[] bundles = bc.getBundles();
for (final Bundle bundle : bundles)
removeBundle(bundle);
}
public static void removeBundles(final BundleCollection bc)
throws BundleException {
final Bundle[] bundles = bc.getBundleContext().getBundles();
for (final Bundle bundle : bundles)
if (!BundleUtil.isSystemBundle(bundle))
removeBundle(bundle);
}
public static void removeBundlesEL(final BundleCollection bc) {
final Bundle[] bundles = bc.getBundleContext().getBundles();
for (final Bundle bundle : bundles)
if (!BundleUtil.isSystemBundle(bundle))
try {
removeBundle(bundle);
} catch (final BundleException e) {
// TODO remove
e.printStackTrace();
}
}
public static void removeBundle(final Bundle bundle) throws BundleException {
if (bundle == null)
return;
// wait for starting
int sleept = 0;
while (bundle.getState() == Bundle.STARTING) {
try {
Thread.sleep(10);
} catch (final InterruptedException e) {
break;
}
sleept += 10;
if (sleept > 3000)
break; // only wait for 3 seconds
}
// force stopping (even when still starting)
if (bundle.getState() == Bundle.ACTIVE
|| bundle.getState() == Bundle.STARTING)
bundle.stop();
// wait for stopping
sleept = 0;
while (bundle.getState() == Bundle.STOPPING) {
try {
Thread.sleep(10);
} catch (final InterruptedException e) {
break;
}
sleept += 10;
if (sleept > 3000)
break; // only wait for 3 seconds
}
if (bundle.getState() != Bundle.UNINSTALLED)
bundle.uninstall();
}
}