package org.argeo.api.a2; import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.INFO; import static java.lang.System.Logger.Level.TRACE; import static org.argeo.api.a2.A2Source.SCHEME_A2; import static org.argeo.api.a2.A2Source.SCHEME_A2_REFERENCE; import java.io.File; import java.io.UnsupportedEncodingException; import java.lang.System.Logger; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.osgi.framework.wiring.FrameworkWiring; /** Loads provisioning sources into an OSGi context. */ public class ProvisioningManager { private final static Logger logger = System.getLogger(ProvisioningManager.class.getName()); private BundleContext bc; private OsgiContext osgiContext; private List sources = Collections.synchronizedList(new ArrayList<>()); public ProvisioningManager(BundleContext bc) { this.bc = bc; osgiContext = new OsgiContext(bc); osgiContext.load(); } protected void addSource(ProvisioningSource source) { sources.add(source); } void installWholeSource(ProvisioningSource source) { Set updatedBundles = new HashSet<>(); for (A2Contribution contribution : source.listContributions(null)) { for (A2Component component : contribution.components.values()) { A2Module module = component.last().last(); Bundle bundle = installOrUpdate(module); if (bundle != null) updatedBundles.add(bundle); } } // FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); // frameworkWiring.refreshBundles(updatedBundles); } public void registerSource(String uri) { try { URI u = new URI(uri); // XOR Map> properties = queryToMap(u); Map xOr = new HashMap<>(); List includes = null; List excludes = null; for (String key : properties.keySet()) { List lst = properties.get(key); if (A2Source.INCLUDE.equals(key)) { includes = new ArrayList<>(lst); } else if (A2Source.EXCLUDE.equals(key)) { excludes = new ArrayList<>(lst); } else { if (lst.size() != 1) throw new IllegalArgumentException("Invalid XOR definitions in " + uri); xOr.put(key, lst.get(0)); } } if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) { if (u.getHost() == null || "".equals(u.getHost())) { String baseStr = u.getPath(); if (File.separatorChar == '\\') {// MS Windows baseStr = baseStr.substring(1).replace('/', File.separatorChar); } Path base = Paths.get(baseStr); if (Files.exists(base)) { FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()), includes, excludes); source.load(); addSource(source); logger.log(DEBUG, () -> "Registered " + uri + " as source"); // OS specific / native String localRelPath = A2Contribution.localOsArchRelativePath(); Path localLibBase = base.resolve(A2Contribution.LIB).resolve(localRelPath); if (Files.exists(localLibBase)) { FsA2Source libSource = new FsA2Source(localLibBase, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()), includes, excludes); libSource.load(); addSource(libSource); logger.log(DEBUG, () -> "Registered OS-specific base " + localLibBase + " for source " + uri); } } else { logger.log(TRACE, () -> "Source " + base + " does not exist, ignoring."); } } else { throw new UnsupportedOperationException( "Remote installation is not yet supported, cannot add source " + u); } } else { throw new IllegalArgumentException("Unkown scheme: for source " + u); } } catch (Exception e) { throw new A2Exception("Cannot add source " + uri, e); } } public boolean registerDefaultSource() { String frameworkLocation = bc.getProperty("osgi.framework"); try { URI frameworkLocationUri = new URI(frameworkLocation); if ("file".equals(frameworkLocationUri.getScheme())) { Path frameworkPath = Paths.get(frameworkLocationUri); if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) { Path base = frameworkPath.getParent().getParent(); String baseStr = base.toString(); if (File.separatorChar == '\\')// MS Windows baseStr = '/' + baseStr.replace(File.separatorChar, '/'); URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null); registerSource(baseUri.toString()); logger.log(TRACE, () -> "Default source from framework location " + frameworkLocation); return true; } } } catch (Exception e) { logger.log(ERROR, "Cannot register default source based on framework location " + frameworkLocation, e); } return false; } public void install(String spec) { if (spec == null) { for (ProvisioningSource source : sources) { installWholeSource(source); } } } /** @return the new/updated bundle, or null if nothing was done. */ protected Bundle installOrUpdate(A2Module module) { try { ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource(); Version moduleVersion = module.getVersion(); A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion); if (osgiBranch == null) { Bundle bundle = moduleSource.install(bc, module); // TODO make it more dynamic, based on OSGi APIs osgiContext.registerBundle(bundle); // if (OsgiBootUtils.isDebug()) // OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion); return bundle; } else { A2Module lastOsgiModule = osgiBranch.last(); int compare = moduleVersion.compareTo(lastOsgiModule.getVersion()); if (compare >= 0) {// update (also if same version) Bundle bundle = (Bundle) lastOsgiModule.getLocator(); if (bundle.getBundleId() == 0)// ignore framework bundle return null; moduleSource.update(bundle, module); // TODO make it more dynamic, based on OSGi APIs // TODO remove old module? Or keep update history? osgiContext.registerBundle(bundle); if (compare == 0) logger.log(TRACE, () -> "Updated bundle " + bundle.getLocation() + " to same version " + moduleVersion); else logger.log(INFO, "Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); return bundle; } else { logger.log(TRACE, () -> "Did not install as bundle module " + module + " since a module with higher version " + lastOsgiModule.getVersion() + " is already installed for branch " + osgiBranch); } } } catch (Exception e) { logger.log(ERROR, "Could not install module " + module + ": " + e.getMessage(), e); } return null; } public Collection update() { boolean fragmentsUpdated = false; Set updatedBundles = new HashSet<>(); bundles: for (Bundle bundle : bc.getBundles()) { for (ProvisioningSource source : sources) { String componentId = bundle.getSymbolicName(); Version version = bundle.getVersion(); A2Branch branch = source.findBranch(componentId, version); if (branch == null) continue bundles; A2Module module = branch.last(); Version moduleVersion = module.getVersion(); int compare = moduleVersion.compareTo(version); if (compare > 0) {// update try { source.update(bundle, module); // bundle.update(in); String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST); if (fragmentHost != null) fragmentsUpdated = true; logger.log(INFO, "Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); updatedBundles.add(bundle); } catch (Exception e) { logger.log(ERROR, "Cannot update with module " + module, e); } } } } FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); if (fragmentsUpdated)// refresh all frameworkWiring.refreshBundles(null); else frameworkWiring.refreshBundles(updatedBundles); return updatedBundles; } private static Map> queryToMap(URI uri) { return queryToMap(uri.getQuery()); } private static Map> queryToMap(String queryPart) { try { final Map> query_pairs = new LinkedHashMap>(); if (queryPart == null) return query_pairs; final String[] pairs = queryPart.split("&"); for (String pair : pairs) { final int idx = pair.indexOf("="); final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name()) : pair; if (!query_pairs.containsKey(key)) { query_pairs.put(key, new LinkedList()); } final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) : null; query_pairs.get(key).add(value); } return query_pairs; } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e); } } // public static void main(String[] args) { // if (args.length == 0) // throw new IllegalArgumentException("Usage: "); // Map configuration = new HashMap<>(); // configuration.put("osgi.console", "2323"); // configuration.put("org.osgi.framework.bootdelegation", // "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs"); // Framework framework = OsgiBootUtils.launch(configuration); // try { // ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext()); // Map xOr = new HashMap<>(); // xOr.put("osgi", "equinox"); // xOr.put("swt", "rap"); // FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr); // context.load(); // pm.addSource(context); // if (framework.getBundleContext().getBundles().length == 1) {// initial // pm.install(null); // } else { // pm.update(); // } // // Thread.sleep(2000); // // Bundle[] bundles = framework.getBundleContext().getBundles(); // Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName())); // for (Bundle b : bundles) // if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE) // System.out.println(b.getSymbolicName() + " " + b.getVersion()); // else // System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")"); // } catch (Exception e) { // e.printStackTrace(); // } finally { // try { // framework.stop(); // } catch (Exception e) { // e.printStackTrace(); // } // } // } }