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