19
19
import java .net .*;
20
20
import java .util .*;
21
21
import java .util .concurrent .CountDownLatch ;
22
+ import java .util .stream .Stream ;
22
23
import org .eclipse .equinox .internal .simpleconfigurator .utils .*;
24
+ import org .eclipse .osgi .report .resolution .ResolutionReport .Entry .Type ;
23
25
import org .osgi .framework .*;
26
+ import org .osgi .framework .hooks .resolver .ResolverHookFactory ;
24
27
import org .osgi .framework .namespace .*;
25
28
import org .osgi .framework .startlevel .BundleStartLevel ;
26
29
import org .osgi .framework .wiring .*;
27
30
import org .osgi .resource .Namespace ;
28
31
import org .osgi .resource .Requirement ;
29
32
import org .osgi .service .packageadmin .PackageAdmin ;
33
+ import org .osgi .service .resolver .ResolutionException ;
30
34
31
35
class ConfigApplier {
36
+
32
37
private static final String LAST_BUNDLES_INFO = "last.bundles.info" ; //$NON-NLS-1$
33
38
private static final String PROP_DEVMODE = "osgi.dev" ; //$NON-NLS-1$
34
39
@@ -40,8 +45,17 @@ class ConfigApplier {
40
45
41
46
private final Bundle callingBundle ;
42
47
private final URI baseLocation ;
48
+ private boolean deepRefresh ;
49
+ private int maxRefreshTry ;
43
50
44
51
ConfigApplier (BundleContext context , Bundle callingBundle ) {
52
+ deepRefresh = Boolean .parseBoolean (context .getProperty ("equinox.simpleconfigurator.deeprefresh" ));
53
+ String maxRefreshValue = context .getProperty ("equinox.simpleconfigurator.maxrefresh" );
54
+ if (maxRefreshValue != null ) {
55
+ maxRefreshTry = Integer .parseInt (maxRefreshValue );
56
+ } else {
57
+ maxRefreshTry = 10 ;
58
+ }
45
59
manipulatingContext = context ;
46
60
this .callingBundle = callingBundle ;
47
61
runningOnEquinox = "Eclipse" .equals (context .getProperty (Constants .FRAMEWORK_VENDOR )); //$NON-NLS-1$
@@ -115,11 +129,93 @@ void install(URL url, boolean exclusiveMode) throws IOException {
115
129
if (additionalRefresh .length > 0 )
116
130
refreshPackages (additionalRefresh , manipulatingContext );
117
131
}
132
+ }
133
+ if (deepRefresh ) {
134
+ // when refreshing large sets of bundles the resolver sometimes take a choice
135
+ // where a requirement can't be bound even though it is there and resolvable
136
+ // this code collects all those requirements and refresh the smaller set of
137
+ // bundles that provide these until a max retry is reached, all bundles are
138
+ // resolved, or all providers have been tried to refresh already
139
+ Set <Bundle > bundlesTried = new HashSet <>();
140
+ Collection <Bundle > provider ;
141
+ Set <Bundle > doNotRefresh = getDoNotRefresh ();
142
+ int maxtry = maxRefreshTry ;
143
+ do {
144
+ provider = getUnresolvedRequirementsProvider (doNotRefresh );
145
+ if (provider .isEmpty () || !bundlesTried .addAll (provider )) {
146
+ // nothing more we can do...
147
+ break ;
148
+ }
149
+ if (Activator .DEBUG ) {
150
+ System .out .println ("There are " + provider .size () + " bundles to refresh..." );
151
+ for (Bundle bundle : provider ) {
152
+ System .out .println ("\t " + bundle .getSymbolicName () + " " + bundle .getVersion ());
153
+ }
154
+ }
155
+ CountDownLatch latch = new CountDownLatch (1 );
156
+ frameworkWiring .refreshBundles (provider , event -> {
157
+ if (event .getType () == FrameworkEvent .PACKAGES_REFRESHED ) {
158
+ latch .countDown ();
159
+ }
160
+ });
161
+ try {
162
+ latch .await ();
163
+ } catch (InterruptedException e ) {
164
+ Thread .currentThread ().interrupt ();
165
+ }
166
+ } while (maxtry -- > 0 );
167
+ }
118
168
}
169
+ startBundles (toStart .toArray (new Bundle [toStart .size ()]));
170
+ }
119
171
172
+ /**
173
+ * This method performs a resolve on all unresolved bundles and captures
174
+ * <ol>
175
+ * <li>Missing capabilities</li>
176
+ * <li>Use constraint violations</li>
177
+ * </ol>
178
+ *
179
+ * after that each unique requirement is looked up in the wiring if there is any
180
+ * provider for it, if that is the case, the requirements is not really missing
181
+ * or part of a conflicting chain
182
+ *
183
+ * @param doNotRefresh
184
+ *
185
+ * @return a collection of bundles that potentially can provide a missing
186
+ * requirement
187
+ */
188
+ private Collection <Bundle > getUnresolvedRequirementsProvider (Set <Bundle > doNotRefresh ) {
189
+ ResolutionReportListener reportListener = new ResolutionReportListener ();
190
+ ServiceRegistration <ResolverHookFactory > hookReg = manipulatingContext
191
+ .registerService (ResolverHookFactory .class ,
192
+ reportListener , null );
193
+ try {
194
+ if (frameworkWiring .resolveBundles (null )) {
195
+ return List .of ();
196
+ }
197
+ if (Activator .DEBUG ) {
198
+ System .out .println ("There are unresolved bundles..." );
199
+ }
200
+ } finally {
201
+ hookReg .unregister ();
120
202
}
121
-
122
- startBundles (toStart .toArray (new Bundle [toStart .size ()]));
203
+ return reportListener .getReports ().stream ()
204
+ .flatMap (rr -> rr .getEntries ().values ().stream ().flatMap (Collection ::stream )).flatMap (error -> {
205
+ if (error .getType () == Type .MISSING_CAPABILITY ) {
206
+ Requirement requirement = (Requirement ) error .getData ();
207
+ if (!"optional" .equals (requirement .getDirectives ().get ("resolution" ))) {
208
+ return Stream .of (requirement );
209
+ }
210
+ }
211
+ if (error .getType () == Type .USES_CONSTRAINT_VIOLATION ) {
212
+ ResolutionException ex = (ResolutionException ) error .getData ();
213
+ return ex .getUnresolvedRequirements ().stream ();
214
+ }
215
+ return Stream .empty ();
216
+ }).distinct ().flatMap (req -> frameworkWiring .findProviders (req ).stream ())
217
+ .map (bc -> bc .getResource ().getBundle ())
218
+ .distinct ().filter (bundle -> !doNotRefresh .contains (bundle )).toList ();
123
219
}
124
220
125
221
private Bundle [] getAdditionalRefresh (Set <Bundle > previouslyResolved , Collection <Bundle > toRefresh ) {
0 commit comments