1 package org
.argeo
.api
.a2
;
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
;
11 import java
.io
.UnsupportedEncodingException
;
12 import java
.lang
.System
.Logger
;
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
;
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
;
36 /** Loads provisioning sources into an OSGi context. */
37 public class ProvisioningManager
{
38 private final static Logger logger
= System
.getLogger(ProvisioningManager
.class.getName());
40 private BundleContext bc
;
41 private OsgiContext osgiContext
;
42 private List
<ProvisioningSource
> sources
= Collections
.synchronizedList(new ArrayList
<>());
44 public ProvisioningManager(BundleContext bc
) {
46 osgiContext
= new OsgiContext(bc
);
50 protected void addSource(ProvisioningSource source
) {
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
);
61 updatedBundles
.add(bundle
);
64 // FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
65 // frameworkWiring.refreshBundles(updatedBundles);
68 public void registerSource(String uri
) {
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
);
85 throw new IllegalArgumentException("Invalid XOR definitions in " + uri
);
86 xOr
.put(key
, lst
.get(0));
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
);
96 Path base
= Paths
.get(baseStr
);
97 if (Files
.exists(base
)) {
98 FsA2Source source
= new FsA2Source(base
, xOr
, SCHEME_A2_REFERENCE
.equals(u
.getScheme()),
102 logger
.log(DEBUG
, () -> "Registered " + uri
+ " as source");
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
);
111 addSource(libSource
);
113 () -> "Registered OS-specific base " + localLibBase
+ " for source " + uri
);
116 logger
.log(TRACE
, () -> "Source " + base
+ " does not exist, ignoring.");
119 throw new UnsupportedOperationException(
120 "Remote installation is not yet supported, cannot add source " + u
);
123 throw new IllegalArgumentException("Unkown scheme: for source " + u
);
125 } catch (Exception e
) {
126 throw new A2Exception("Cannot add source " + uri
, e
);
130 public boolean registerDefaultSource() {
131 String frameworkLocation
= bc
.getProperty("osgi.framework");
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
);
147 } catch (Exception e
) {
148 logger
.log(ERROR
, "Cannot register default source based on framework location " + frameworkLocation
, e
);
153 public void install(String spec
) {
155 for (ProvisioningSource source
: sources
) {
156 installWholeSource(source
);
161 /** @return the new/updated bundle, or null if nothing was done. */
162 protected Bundle
installOrUpdate(A2Module module
) {
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);
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
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
);
187 () -> "Updated bundle " + bundle
.getLocation() + " to same version " + moduleVersion
);
189 logger
.log(INFO
, "Updated bundle " + bundle
.getLocation() + " to version " + moduleVersion
);
193 () -> "Did not install as bundle module " + module
+ " since a module with higher version "
194 + lastOsgiModule
.getVersion() + " is already installed for branch " + osgiBranch
);
197 } catch (Exception e
) {
198 logger
.log(ERROR
, "Could not install module " + module
+ ": " + e
.getMessage(), e
);
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
);
213 A2Module module
= branch
.last();
214 Version moduleVersion
= module
.getVersion();
215 int compare
= moduleVersion
.compareTo(version
);
216 if (compare
> 0) {// update
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
);
231 FrameworkWiring frameworkWiring
= bc
.getBundle(0).adapt(FrameworkWiring
.class);
232 if (fragmentsUpdated
)// refresh all
233 frameworkWiring
.refreshBundles(null);
235 frameworkWiring
.refreshBundles(updatedBundles
);
236 return updatedBundles
;
239 private static Map
<String
, List
<String
>> queryToMap(URI uri
) {
240 return queryToMap(uri
.getQuery());
243 private static Map
<String
, List
<String
>> queryToMap(String queryPart
) {
245 final Map
<String
, List
<String
>> query_pairs
= new LinkedHashMap
<String
, List
<String
>>();
246 if (queryPart
== null)
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())
253 if (!query_pairs
.containsKey(key
)) {
254 query_pairs
.put(key
, new LinkedList
<String
>());
256 final String value
= idx
> 0 && pair
.length() > idx
+ 1
257 ? URLDecoder
.decode(pair
.substring(idx
+ 1), StandardCharsets
.UTF_8
.name())
259 query_pairs
.get(key
).add(value
);
262 } catch (UnsupportedEncodingException e
) {
263 throw new IllegalArgumentException("Cannot convert " + queryPart
+ " to map", e
);
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);
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);
282 // pm.addSource(context);
283 // if (framework.getBundleContext().getBundles().length == 1) {// initial
289 // Thread.sleep(2000);
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());
297 // System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")");
298 // } catch (Exception e) {
299 // e.printStackTrace();
303 // } catch (Exception e) {
304 // e.printStackTrace();