X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=blobdiff_plain;f=org.argeo.init%2Fsrc%2Forg%2Fargeo%2Fapi%2Fa2%2FProvisioningManager.java;fp=org.argeo.init%2Fsrc%2Forg%2Fargeo%2Fapi%2Fa2%2FProvisioningManager.java;h=af22787b177a6d0aac744452ecbaa9753083a9e8;hp=0000000000000000000000000000000000000000;hb=b95462873703848193e56fcbe997693630db6121;hpb=55d88fba80cec198a0f11ba7545e19878c51fc5e diff --git a/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java new file mode 100644 index 000000000..af22787b1 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java @@ -0,0 +1,309 @@ +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(); +// } +// } +// } + +}