]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java
Improve init launch
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / api / a2 / ProvisioningManager.java
1 package org.argeo.api.a2;
2
3 import static java.lang.System.Logger.Level.DEBUG;
4 import static java.lang.System.Logger.Level.ERROR;
5 import static java.lang.System.Logger.Level.INFO;
6 import static java.lang.System.Logger.Level.TRACE;
7 import static org.argeo.api.a2.A2Source.SCHEME_A2;
8 import static org.argeo.api.a2.A2Source.SCHEME_A2_REFERENCE;
9
10 import java.io.File;
11 import java.io.UnsupportedEncodingException;
12 import java.lang.System.Logger;
13 import java.net.URI;
14 import java.net.URLDecoder;
15 import java.nio.charset.StandardCharsets;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18 import java.nio.file.Paths;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29
30 import org.osgi.framework.Bundle;
31 import org.osgi.framework.BundleContext;
32 import org.osgi.framework.Constants;
33 import org.osgi.framework.Version;
34 import org.osgi.framework.wiring.FrameworkWiring;
35
36 /** Loads provisioning sources into an OSGi context. */
37 public class ProvisioningManager {
38 private final static Logger logger = System.getLogger(ProvisioningManager.class.getName());
39
40 private BundleContext bc;
41 private OsgiContext osgiContext;
42 private List<ProvisioningSource> sources = Collections.synchronizedList(new ArrayList<>());
43
44 public ProvisioningManager(BundleContext bc) {
45 this.bc = bc;
46 osgiContext = new OsgiContext(bc);
47 osgiContext.load();
48 }
49
50 protected void addSource(ProvisioningSource source) {
51 sources.add(source);
52 }
53
54 void installWholeSource(ProvisioningSource source) {
55 Set<Bundle> updatedBundles = new HashSet<>();
56 for (A2Contribution contribution : source.listContributions(null)) {
57 for (A2Component component : contribution.components.values()) {
58 A2Module module = component.last().last();
59 Bundle bundle = installOrUpdate(module);
60 if (bundle != null)
61 updatedBundles.add(bundle);
62 }
63 }
64 // FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
65 // frameworkWiring.refreshBundles(updatedBundles);
66 }
67
68 public void registerSource(String uri) {
69 try {
70 URI u = new URI(uri);
71
72 // XOR
73 Map<String, List<String>> properties = queryToMap(u);
74 Map<String, String> xOr = new HashMap<>();
75 List<String> includes = null;
76 List<String> excludes = null;
77 for (String key : properties.keySet()) {
78 List<String> lst = properties.get(key);
79 if (A2Source.INCLUDE.equals(key)) {
80 includes = new ArrayList<>(lst);
81 } else if (A2Source.EXCLUDE.equals(key)) {
82 excludes = new ArrayList<>(lst);
83 } else {
84 if (lst.size() != 1)
85 throw new IllegalArgumentException("Invalid XOR definitions in " + uri);
86 xOr.put(key, lst.get(0));
87 }
88 }
89
90 if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) {
91 if (u.getHost() == null || "".equals(u.getHost())) {
92 String baseStr = u.getPath();
93 if (File.separatorChar == '\\') {// MS Windows
94 baseStr = baseStr.substring(1).replace('/', File.separatorChar);
95 }
96 Path base = Paths.get(baseStr);
97 if (Files.exists(base)) {
98 FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()),
99 includes, excludes);
100 source.load();
101 addSource(source);
102 logger.log(DEBUG, () -> "Registered " + uri + " as source");
103
104 // OS specific / native
105 String localRelPath = A2Contribution.localOsArchRelativePath();
106 Path localLibBase = base.resolve(A2Contribution.LIB).resolve(localRelPath);
107 if (Files.exists(localLibBase)) {
108 FsA2Source libSource = new FsA2Source(localLibBase, xOr,
109 SCHEME_A2_REFERENCE.equals(u.getScheme()), includes, excludes);
110 libSource.load();
111 addSource(libSource);
112 logger.log(DEBUG,
113 () -> "Registered OS-specific base " + localLibBase + " for source " + uri);
114 }
115 } else {
116 logger.log(TRACE, () -> "Source " + base + " does not exist, ignoring.");
117 }
118 } else {
119 throw new UnsupportedOperationException(
120 "Remote installation is not yet supported, cannot add source " + u);
121 }
122 } else {
123 throw new IllegalArgumentException("Unkown scheme: for source " + u);
124 }
125 } catch (Exception e) {
126 throw new A2Exception("Cannot add source " + uri, e);
127 }
128 }
129
130 public boolean registerDefaultSource() {
131 String frameworkLocation = bc.getProperty("osgi.framework");
132 try {
133 URI frameworkLocationUri = new URI(frameworkLocation);
134 if ("file".equals(frameworkLocationUri.getScheme())) {
135 Path frameworkPath = Paths.get(frameworkLocationUri);
136 if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) {
137 Path base = frameworkPath.getParent().getParent();
138 String baseStr = base.toString();
139 if (File.separatorChar == '\\')// MS Windows
140 baseStr = '/' + baseStr.replace(File.separatorChar, '/');
141 URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null);
142 registerSource(baseUri.toString());
143 logger.log(TRACE, () -> "Default source from framework location " + frameworkLocation);
144 return true;
145 }
146 }
147 } catch (Exception e) {
148 logger.log(ERROR, "Cannot register default source based on framework location " + frameworkLocation, e);
149 }
150 return false;
151 }
152
153 public void install(String spec) {
154 if (spec == null) {
155 for (ProvisioningSource source : sources) {
156 installWholeSource(source);
157 }
158 }
159 }
160
161 /** @return the new/updated bundle, or null if nothing was done. */
162 protected Bundle installOrUpdate(A2Module module) {
163 try {
164 ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource();
165 Version moduleVersion = module.getVersion();
166 A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion);
167 if (osgiBranch == null) {
168 Bundle bundle = moduleSource.install(bc, module);
169 // TODO make it more dynamic, based on OSGi APIs
170 osgiContext.registerBundle(bundle);
171 // if (OsgiBootUtils.isDebug())
172 // OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion);
173 return bundle;
174 } else {
175 A2Module lastOsgiModule = osgiBranch.last();
176 int compare = moduleVersion.compareTo(lastOsgiModule.getVersion());
177 if (compare >= 0) {// update (also if same version)
178 Bundle bundle = (Bundle) lastOsgiModule.getLocator();
179 if (bundle.getBundleId() == 0)// ignore framework bundle
180 return null;
181 moduleSource.update(bundle, module);
182 // TODO make it more dynamic, based on OSGi APIs
183 // TODO remove old module? Or keep update history?
184 osgiContext.registerBundle(bundle);
185 if (compare == 0)
186 logger.log(TRACE,
187 () -> "Updated bundle " + bundle.getLocation() + " to same version " + moduleVersion);
188 else
189 logger.log(INFO, "Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
190 return bundle;
191 } else {
192 logger.log(TRACE,
193 () -> "Did not install as bundle module " + module + " since a module with higher version "
194 + lastOsgiModule.getVersion() + " is already installed for branch " + osgiBranch);
195 }
196 }
197 } catch (Exception e) {
198 logger.log(ERROR, "Could not install module " + module + ": " + e.getMessage(), e);
199 }
200 return null;
201 }
202
203 public Collection<Bundle> update() {
204 boolean fragmentsUpdated = false;
205 Set<Bundle> updatedBundles = new HashSet<>();
206 bundles: for (Bundle bundle : bc.getBundles()) {
207 for (ProvisioningSource source : sources) {
208 String componentId = bundle.getSymbolicName();
209 Version version = bundle.getVersion();
210 A2Branch branch = source.findBranch(componentId, version);
211 if (branch == null)
212 continue bundles;
213 A2Module module = branch.last();
214 Version moduleVersion = module.getVersion();
215 int compare = moduleVersion.compareTo(version);
216 if (compare > 0) {// update
217 try {
218 source.update(bundle, module);
219 // bundle.update(in);
220 String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST);
221 if (fragmentHost != null)
222 fragmentsUpdated = true;
223 logger.log(INFO, "Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
224 updatedBundles.add(bundle);
225 } catch (Exception e) {
226 logger.log(ERROR, "Cannot update with module " + module, e);
227 }
228 }
229 }
230 }
231 FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
232 if (fragmentsUpdated)// refresh all
233 frameworkWiring.refreshBundles(null);
234 else
235 frameworkWiring.refreshBundles(updatedBundles);
236 return updatedBundles;
237 }
238
239 private static Map<String, List<String>> queryToMap(URI uri) {
240 return queryToMap(uri.getQuery());
241 }
242
243 private static Map<String, List<String>> queryToMap(String queryPart) {
244 try {
245 final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
246 if (queryPart == null)
247 return query_pairs;
248 final String[] pairs = queryPart.split("&");
249 for (String pair : pairs) {
250 final int idx = pair.indexOf("=");
251 final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
252 : pair;
253 if (!query_pairs.containsKey(key)) {
254 query_pairs.put(key, new LinkedList<String>());
255 }
256 final String value = idx > 0 && pair.length() > idx + 1
257 ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
258 : null;
259 query_pairs.get(key).add(value);
260 }
261 return query_pairs;
262 } catch (UnsupportedEncodingException e) {
263 throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
264 }
265 }
266
267 // public static void main(String[] args) {
268 // if (args.length == 0)
269 // throw new IllegalArgumentException("Usage: <path to A2 base>");
270 // Map<String, String> configuration = new HashMap<>();
271 // configuration.put("osgi.console", "2323");
272 // configuration.put("org.osgi.framework.bootdelegation",
273 // "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");
274 // Framework framework = OsgiBootUtils.launch(configuration);
275 // try {
276 // ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
277 // Map<String, String> xOr = new HashMap<>();
278 // xOr.put("osgi", "equinox");
279 // xOr.put("swt", "rap");
280 // FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
281 // context.load();
282 // pm.addSource(context);
283 // if (framework.getBundleContext().getBundles().length == 1) {// initial
284 // pm.install(null);
285 // } else {
286 // pm.update();
287 // }
288 //
289 // Thread.sleep(2000);
290 //
291 // Bundle[] bundles = framework.getBundleContext().getBundles();
292 // Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName()));
293 // for (Bundle b : bundles)
294 // if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE)
295 // System.out.println(b.getSymbolicName() + " " + b.getVersion());
296 // else
297 // System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")");
298 // } catch (Exception e) {
299 // e.printStackTrace();
300 // } finally {
301 // try {
302 // framework.stop();
303 // } catch (Exception e) {
304 // e.printStackTrace();
305 // }
306 // }
307 // }
308
309 }