1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.felix.resolver; 20 21 import java.security.*; 22 import java.util.*; 23 import java.util.Map.Entry; 24 import java.util.concurrent.*; 25 import java.util.concurrent.atomic.AtomicReference; 26 27 import org.apache.felix.resolver.reason.ReasonException; 28 import org.apache.felix.resolver.util.ArrayMap; 29 import org.apache.felix.resolver.util.CandidateSelector; 30 import org.apache.felix.resolver.util.OpenHashMap; 31 import org.osgi.framework.namespace.*; 32 import org.osgi.resource.*; 33 import org.osgi.service.resolver.*; 34 35 public class ResolverImpl implements Resolver 36 { 37 private final AccessControlContext m_acc = 38 System.getSecurityManager() != null ? 39 AccessController.getContext() : 40 null; 41 42 private final Logger m_logger; 43 44 private final int m_parallelism; 45 46 private final Executor m_executor; 47 48 enum PermutationType { 49 USES, 50 IMPORT, 51 SUBSTITUTE 52 } 53 54 // Note this class is not thread safe. 55 // Only use in the context of a single thread. 56 static class ResolveSession implements Runnable 57 { 58 // Holds the resolve context for this session 59 private final ResolveContext m_resolveContext; 60 private final Collection<Resource> m_mandatoryResources; 61 private final Collection<Resource> m_optionalResources; 62 private final Resource m_dynamicHost; 63 private final Requirement m_dynamicReq; 64 private final List<Capability> m_dynamicCandidates; 65 // keeps track of valid related resources that we have seen. 66 // a null value or TRUE indicate it is valid 67 private Map<Resource, Boolean> m_validRelatedResources = new HashMap<Resource, Boolean>(0); 68 // keeps track of related resources for each resource 69 private Map<Resource, Collection<Resource>> m_relatedResources = new HashMap<Resource, Collection<Resource>>(0); 70 // Holds candidate permutations based on permutating "uses" chains. 71 // These permutations are given higher priority. 72 private final List<Candidates> m_usesPermutations = new LinkedList<Candidates>(); 73 private int m_usesIndex = 0; 74 // Holds candidate permutations based on permutating requirement candidates. 75 // These permutations represent backtracking on previous decisions. 76 private final List<Candidates> m_importPermutations = new LinkedList<Candidates>(); 77 private int m_importIndex = 0; 78 // Holds candidate permutations based on substituted packages 79 private final List<Candidates> m_substPermutations = new LinkedList<Candidates>(); 80 private int m_substituteIndex = 0; 81 // Holds candidate permutations based on removing candidates that satisfy 82 // multiple cardinality requirements. 83 // This permutation represents a permutation that is consistent because we have 84 // removed the offending capabilities 85 private Candidates m_multipleCardCandidates = null; 86 // The delta is used to detect that we have already processed this particular permutation 87 private final Set<Object> m_processedDeltas = new HashSet<Object>(); 88 private final Executor m_executor; 89 private final Set<Requirement> m_mutated = new HashSet<Requirement>(); 90 private final Set<Requirement> m_sub_mutated = new HashSet<Requirement>(); 91 private final ConcurrentMap<String, List<String>> m_usesCache = new ConcurrentHashMap<String, List<String>>(); 92 private ResolutionError m_currentError; 93 volatile private CancellationException m_isCancelled = null; 94 createSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates)95 static ResolveSession createSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates) 96 { 97 ResolveSession session = new ResolveSession(resolveContext, executor, dynamicHost, dynamicReq, dynamicCandidates); 98 // call onCancel first 99 session.getContext().onCancel(session); 100 // now gather the mandatory and optional resources 101 session.initMandatoryAndOptionalResources(); 102 return session; 103 } 104 ResolveSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates)105 private ResolveSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates) 106 { 107 m_resolveContext = resolveContext; 108 m_executor = executor; 109 m_dynamicHost = dynamicHost; 110 m_dynamicReq = dynamicReq; 111 m_dynamicCandidates = dynamicCandidates; 112 if (m_dynamicHost != null) { 113 m_mandatoryResources = Collections.singletonList(dynamicHost); 114 m_optionalResources = Collections.emptyList(); 115 } else { 116 // Do not call resolve context yet, onCancel must be called first 117 m_mandatoryResources = new ArrayList<Resource>(); 118 m_optionalResources = new ArrayList<Resource>(); 119 } 120 } 121 initMandatoryAndOptionalResources()122 private void initMandatoryAndOptionalResources() { 123 if (!isDynamic()) { 124 m_mandatoryResources.addAll(getContext().getMandatoryResources()); 125 m_optionalResources.addAll(getContext().getOptionalResources()); 126 } 127 } getMultipleCardCandidates()128 Candidates getMultipleCardCandidates() 129 { 130 return m_multipleCardCandidates; 131 } 132 getContext()133 ResolveContext getContext() 134 { 135 return m_resolveContext; 136 } 137 getUsesCache()138 ConcurrentMap<String, List<String>> getUsesCache() { 139 return m_usesCache; 140 } 141 permutateIfNeeded(PermutationType type, Requirement req, Candidates permutation)142 void permutateIfNeeded(PermutationType type, Requirement req, Candidates permutation) { 143 List<Capability> candidates = permutation.getCandidates(req); 144 if ((candidates != null) && (candidates.size() > 1)) 145 { 146 if ((type == PermutationType.SUBSTITUTE)) { 147 if (!m_sub_mutated.add(req)) { 148 return; 149 } 150 } else if (!m_mutated.add(req)) { 151 return; 152 } 153 // If we haven't already permutated the existing 154 // import, do so now. 155 addPermutation(type, permutation.permutate(req)); 156 } 157 } 158 clearMutateIndexes()159 private void clearMutateIndexes() { 160 m_usesIndex = 0; 161 m_importIndex = 0; 162 m_substituteIndex = 0; 163 m_mutated.clear(); 164 // NOTE: m_sub_mutated is never cleared. 165 // It is unclear if even more permutations based on a substitutions will ever help. 166 // Being safe and reducing extra permutations until we get a scenario that proves 167 // more permutations would really help. 168 } 169 addPermutation(PermutationType type, Candidates permutation)170 void addPermutation(PermutationType type, Candidates permutation) { 171 if (permutation != null) 172 { 173 List<Candidates> typeToAddTo = null; 174 try { 175 switch (type) { 176 case USES : 177 typeToAddTo = m_usesPermutations; 178 m_usesPermutations.add(m_usesIndex++, permutation); 179 break; 180 case IMPORT : 181 typeToAddTo = m_importPermutations; 182 m_importPermutations.add(m_importIndex++, permutation); 183 break; 184 case SUBSTITUTE : 185 typeToAddTo = m_substPermutations; 186 m_substPermutations.add(m_substituteIndex++, permutation); 187 break; 188 default : 189 throw new IllegalArgumentException("Unknown permitation type: " + type); 190 } 191 } catch (IndexOutOfBoundsException e) { 192 // just a safeguard, this really should never happen 193 typeToAddTo.add(permutation); 194 } 195 } 196 } 197 getNextPermutation()198 Candidates getNextPermutation() { 199 Candidates next = null; 200 do { 201 if (!m_usesPermutations.isEmpty()) 202 { 203 next = m_usesPermutations.remove(0); 204 } 205 else if (!m_importPermutations.isEmpty()) 206 { 207 next = m_importPermutations.remove(0); 208 } 209 else if (!m_substPermutations.isEmpty()) 210 { 211 next = m_substPermutations.remove(0); 212 } 213 else { 214 return null; 215 } 216 } 217 while(!m_processedDeltas.add(next.getDelta())); 218 // Null out each time a new permutation is attempted. 219 // We only use this to store a valid permutation which is a 220 // delta of the current permutation. 221 m_multipleCardCandidates = null; 222 // clear mutateIndexes also so we insert new permutations 223 // based of this permutation as a higher priority 224 clearMutateIndexes(); 225 return next; 226 } 227 clearPermutations()228 void clearPermutations() { 229 m_usesPermutations.clear(); 230 m_importPermutations.clear(); 231 m_substPermutations.clear(); 232 m_multipleCardCandidates = null; 233 m_processedDeltas.clear(); 234 m_currentError = null; 235 } 236 checkMultiple( UsedBlames usedBlames, Blame usedBlame, Candidates permutation)237 boolean checkMultiple( 238 UsedBlames usedBlames, 239 Blame usedBlame, 240 Candidates permutation) 241 { 242 // Check the root requirement to see if it is a multiple cardinality 243 // requirement. 244 CandidateSelector candidates = null; 245 Requirement req = usedBlame.m_reqs.get(0); 246 if (Util.isMultiple(req)) 247 { 248 // Create a copy of the current permutation so we can remove the 249 // candidates causing the blame. 250 if (m_multipleCardCandidates == null) 251 { 252 m_multipleCardCandidates = permutation.copy(); 253 } 254 // Get the current candidate list and remove all the offending root 255 // cause candidates from a copy of the current permutation. 256 candidates = m_multipleCardCandidates.clearMultipleCardinalityCandidates(req, usedBlames.getRootCauses(req)); 257 } 258 // We only are successful if there is at least one candidate left 259 // for the requirement 260 return (candidates != null) && !candidates.isEmpty(); 261 } 262 getPermutationCount()263 long getPermutationCount() { 264 return m_usesPermutations.size() + m_importPermutations.size() + m_substPermutations.size(); 265 } 266 getExecutor()267 Executor getExecutor() { 268 return m_executor; 269 } 270 getCurrentError()271 ResolutionError getCurrentError() { 272 return m_currentError; 273 } 274 setCurrentError(ResolutionError error)275 void setCurrentError(ResolutionError error) { 276 this.m_currentError = error; 277 } 278 isDynamic()279 boolean isDynamic() { 280 return m_dynamicHost != null; 281 } 282 getMandatoryResources()283 Collection<Resource> getMandatoryResources() { 284 return m_mandatoryResources; 285 } 286 getOptionalResources()287 Collection<Resource> getOptionalResources() { 288 return m_optionalResources; 289 } 290 getDynamicHost()291 Resource getDynamicHost() { 292 return m_dynamicHost; 293 } 294 getDynamicRequirement()295 Requirement getDynamicRequirement() { 296 return m_dynamicReq; 297 } 298 getDynamicCandidates()299 List<Capability> getDynamicCandidates() { 300 return m_dynamicCandidates; 301 } 302 isValidRelatedResource(Resource resource)303 public boolean isValidRelatedResource(Resource resource) { 304 Boolean valid = m_validRelatedResources.get(resource); 305 if (valid == null) 306 { 307 // Mark this resource as a valid related resource 308 m_validRelatedResources.put(resource, Boolean.TRUE); 309 valid = Boolean.TRUE; 310 } 311 return valid; 312 } 313 invalidateRelatedResource(Resource faultyResource)314 public boolean invalidateRelatedResource(Resource faultyResource) { 315 Boolean valid = m_validRelatedResources.get(faultyResource); 316 if (valid != null && valid) 317 { 318 // This was related resource. 319 // Invalidate it and try again. 320 m_validRelatedResources.put(faultyResource, Boolean.FALSE); 321 return true; 322 } 323 return false; 324 } 325 getRelatedResources(Resource resource)326 public Collection<Resource> getRelatedResources(Resource resource) { 327 Collection<Resource> related = m_relatedResources.get(resource); 328 return related == null ? Collections.<Resource> emptyList() : related; 329 } 330 setRelatedResources(Resource resource, Collection<Resource> related)331 public void setRelatedResources(Resource resource, Collection<Resource> related) { 332 m_relatedResources.put(resource, related); 333 } 334 335 @Override run()336 public void run() { 337 m_isCancelled = new CancellationException(); 338 } 339 isCancelled()340 boolean isCancelled() { 341 return m_isCancelled != null; 342 } 343 checkForCancel()344 void checkForCancel() throws ResolutionException { 345 if (isCancelled()) { 346 throw new ResolutionException("Resolver operation has been cancelled.", m_isCancelled, null); 347 } 348 } 349 } 350 ResolverImpl(Logger logger)351 public ResolverImpl(Logger logger) 352 { 353 this(logger, Runtime.getRuntime().availableProcessors()); 354 } 355 ResolverImpl(Logger logger, int parallelism)356 public ResolverImpl(Logger logger, int parallelism) 357 { 358 this.m_logger = logger; 359 this.m_parallelism = parallelism; 360 this.m_executor = null; 361 } 362 ResolverImpl(Logger logger, Executor executor)363 public ResolverImpl(Logger logger, Executor executor) 364 { 365 this.m_logger = logger; 366 this.m_parallelism = -1; 367 this.m_executor = executor; 368 } 369 resolve(ResolveContext rc)370 public Map<Resource, List<Wire>> resolve(ResolveContext rc) throws ResolutionException 371 { 372 if (m_executor != null) 373 { 374 return resolve(rc, m_executor); 375 } 376 else if (m_parallelism > 1) 377 { 378 final ExecutorService executor = 379 System.getSecurityManager() != null ? 380 AccessController.doPrivileged( 381 new PrivilegedAction<ExecutorService>() 382 { 383 public ExecutorService run() 384 { 385 return Executors.newFixedThreadPool(m_parallelism); 386 } 387 }, m_acc) 388 : 389 Executors.newFixedThreadPool(m_parallelism); 390 try 391 { 392 return resolve(rc, executor); 393 } 394 finally 395 { 396 if (System.getSecurityManager() != null) 397 { 398 AccessController.doPrivileged(new PrivilegedAction<Void>(){ 399 public Void run() { 400 executor.shutdownNow(); 401 return null; 402 } 403 }, m_acc); 404 } 405 else 406 { 407 executor.shutdownNow(); 408 } 409 } 410 } 411 else 412 { 413 return resolve(rc, new DumbExecutor()); 414 } 415 } 416 resolve(ResolveContext rc, Executor executor)417 public Map<Resource, List<Wire>> resolve(ResolveContext rc, Executor executor) throws ResolutionException 418 { 419 ResolveSession session = ResolveSession.createSession(rc, executor, null, null, null); 420 return doResolve(session); 421 } 422 doResolve(ResolveSession session)423 private Map<Resource, List<Wire>> doResolve(ResolveSession session) throws ResolutionException { 424 Map<Resource, List<Wire>> wireMap = new HashMap<Resource, List<Wire>>(); 425 boolean retry; 426 do 427 { 428 retry = false; 429 try 430 { 431 getInitialCandidates(session); 432 if (session.getCurrentError() != null) { 433 throw session.getCurrentError().toException(); 434 } 435 436 Map<Resource, ResolutionError> faultyResources = new HashMap<Resource, ResolutionError>(); 437 Candidates allCandidates = findValidCandidates(session, faultyResources); 438 session.checkForCancel(); 439 440 // If there is a resolve exception, then determine if an 441 // optionally resolved resource is to blame (typically a fragment). 442 // If so, then remove the optionally resolved resolved and try 443 // again; otherwise, m_currentError the resolve exception. 444 if (session.getCurrentError() != null) 445 { 446 Set<Resource> resourceKeys = faultyResources.keySet(); 447 retry = (session.getOptionalResources().removeAll(resourceKeys)); 448 for (Resource faultyResource : resourceKeys) 449 { 450 if (session.invalidateRelatedResource(faultyResource)) 451 { 452 retry = true; 453 } 454 } 455 // log all the resolution exceptions for the uses constraint violations 456 for (Map.Entry<Resource, ResolutionError> usesError : faultyResources.entrySet()) 457 { 458 m_logger.logUsesConstraintViolation(usesError.getKey(), usesError.getValue()); 459 } 460 if (!retry) 461 { 462 throw session.getCurrentError().toException(); 463 } 464 } 465 // If there is no exception to m_currentError, then this was a clean 466 // resolve, so populate the wire map. 467 else 468 { 469 if (session.getMultipleCardCandidates() != null) 470 { 471 // Candidates for multiple cardinality requirements were 472 // removed in order to provide a consistent class space. 473 // Use the consistent permutation 474 allCandidates = session.getMultipleCardCandidates(); 475 } 476 if (session.isDynamic() ) 477 { 478 wireMap = populateDynamicWireMap(session, 479 wireMap, allCandidates); 480 } 481 else 482 { 483 for (Resource resource : allCandidates.getRootHosts().keySet()) 484 { 485 if (allCandidates.isPopulated(resource)) 486 { 487 wireMap = 488 populateWireMap( 489 session, allCandidates.getWrappedHost(resource), 490 wireMap, allCandidates); 491 } 492 } 493 } 494 } 495 } 496 finally 497 { 498 // Always clear the state. 499 session.clearPermutations(); 500 } 501 } 502 while (retry); 503 504 return wireMap; 505 } 506 getInitialCandidates(ResolveSession session)507 private void getInitialCandidates(ResolveSession session) { 508 // Create object to hold all candidates. 509 Candidates initialCandidates; 510 if (session.isDynamic()) { 511 // Create all candidates pre-populated with the single candidate set 512 // for the resolving dynamic import of the host. 513 initialCandidates = new Candidates(session); 514 ResolutionError prepareError = initialCandidates.populateDynamic(); 515 if (prepareError != null) { 516 session.setCurrentError(prepareError); 517 return; 518 } 519 } else { 520 List<Resource> toPopulate = new ArrayList<Resource>(); 521 522 // Populate mandatory resources; since these are mandatory 523 // resources, failure throws a resolve exception. 524 for (Resource resource : session.getMandatoryResources()) 525 { 526 if (Util.isFragment(resource) || (session.getContext().getWirings().get(resource) == null)) 527 { 528 toPopulate.add(resource); 529 } 530 } 531 // Populate optional resources; since these are optional 532 // resources, failure does not throw a resolve exception. 533 for (Resource resource : session.getOptionalResources()) 534 { 535 if (Util.isFragment(resource) || (session.getContext().getWirings().get(resource) == null)) 536 { 537 toPopulate.add(resource); 538 } 539 } 540 541 initialCandidates = new Candidates(session); 542 initialCandidates.populate(toPopulate); 543 } 544 545 // Merge any fragments into hosts. 546 ResolutionError prepareError = initialCandidates.prepare(); 547 if (prepareError != null) 548 { 549 session.setCurrentError(prepareError); 550 } 551 else 552 { 553 // Record the initial candidate permutation. 554 session.addPermutation(PermutationType.USES, initialCandidates); 555 } 556 } 557 findValidCandidates(ResolveSession session, Map<Resource, ResolutionError> faultyResources)558 private Candidates findValidCandidates(ResolveSession session, Map<Resource, ResolutionError> faultyResources) { 559 Candidates allCandidates = null; 560 boolean foundFaultyResources = false; 561 do 562 { 563 allCandidates = session.getNextPermutation(); 564 if (allCandidates == null) 565 { 566 break; 567 } 568 569 //allCandidates.dump(); 570 571 Map<Resource, ResolutionError> currentFaultyResources = new HashMap<Resource, ResolutionError>(); 572 573 session.setCurrentError( 574 checkConsistency( 575 session, 576 allCandidates, 577 currentFaultyResources 578 ) 579 ); 580 581 if (!currentFaultyResources.isEmpty()) 582 { 583 if (!foundFaultyResources) 584 { 585 foundFaultyResources = true; 586 faultyResources.putAll(currentFaultyResources); 587 } 588 else if (faultyResources.size() > currentFaultyResources.size()) 589 { 590 // save the optimal faultyResources which has less 591 faultyResources.clear(); 592 faultyResources.putAll(currentFaultyResources); 593 } 594 } 595 } 596 while (!session.isCancelled() && session.getCurrentError() != null); 597 598 return allCandidates; 599 } 600 checkConsistency( ResolveSession session, Candidates allCandidates, Map<Resource, ResolutionError> currentFaultyResources)601 private ResolutionError checkConsistency( 602 ResolveSession session, 603 Candidates allCandidates, 604 Map<Resource, ResolutionError> currentFaultyResources) 605 { 606 ResolutionError rethrow = allCandidates.checkSubstitutes(); 607 if (rethrow != null) 608 { 609 return rethrow; 610 } 611 Map<Resource, Resource> allhosts = allCandidates.getRootHosts(); 612 // Calculate package spaces 613 Map<Resource, Packages> resourcePkgMap = 614 calculatePackageSpaces(session, allCandidates, allhosts.values()); 615 ResolutionError error = null; 616 // Check package consistency 617 Map<Resource, Object> resultCache = 618 new OpenHashMap<Resource, Object>(resourcePkgMap.size()); 619 for (Entry<Resource, Resource> entry : allhosts.entrySet()) 620 { 621 rethrow = checkPackageSpaceConsistency( 622 session, entry.getValue(), 623 allCandidates, session.isDynamic(), resourcePkgMap, resultCache); 624 if (session.isCancelled()) { 625 return null; 626 } 627 if (rethrow != null) 628 { 629 Resource faultyResource = entry.getKey(); 630 // check that the faulty requirement is not from a fragment 631 for (Requirement faultyReq : rethrow.getUnresolvedRequirements()) 632 { 633 if (faultyReq instanceof WrappedRequirement) 634 { 635 faultyResource = 636 ((WrappedRequirement) faultyReq) 637 .getDeclaredRequirement().getResource(); 638 break; 639 } 640 } 641 currentFaultyResources.put(faultyResource, rethrow); 642 error = rethrow; 643 } 644 } 645 return error; 646 } 647 resolveDynamic(ResolveContext context, Wiring hostWiring, Requirement dynamicRequirement)648 public Map<Resource,List<Wire>> resolveDynamic(ResolveContext context, 649 Wiring hostWiring, Requirement dynamicRequirement) 650 throws ResolutionException 651 { 652 Resource host = hostWiring.getResource(); 653 List<Capability> matches = context.findProviders(dynamicRequirement); 654 // We can only create a dynamic import if the following 655 // conditions are met: 656 // 1. The package in question is not already imported. 657 // 2. The package in question is not accessible via require-bundle. 658 // 3. The package in question is not exported by the resource. 659 if (!matches.isEmpty()) 660 { 661 // Make sure all matching candidates are packages. 662 for (Capability cap : matches) 663 { 664 if (!cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 665 { 666 throw new IllegalArgumentException( 667 "Matching candidate does not provide a package name."); 668 } 669 } 670 ResolveSession session = ResolveSession.createSession(context, new DumbExecutor(), host, dynamicRequirement, matches); 671 return doResolve(session); 672 } 673 674 throw new Candidates.MissingRequirementError(dynamicRequirement).toException(); 675 } 676 getWireCandidates(ResolveSession session, Candidates allCandidates, Resource resource)677 private static List<WireCandidate> getWireCandidates(ResolveSession session, Candidates allCandidates, Resource resource) 678 { 679 // Create a list for requirement and proposed candidate 680 // capability or actual capability if resource is resolved or not. 681 List<WireCandidate> wireCandidates = new ArrayList<WireCandidate>(256); 682 Wiring wiring = session.getContext().getWirings().get(resource); 683 if (wiring != null) 684 { 685 // Use wires to get actual requirements and satisfying capabilities. 686 for (Wire wire : wiring.getRequiredResourceWires(null)) 687 { 688 // Wrap the requirement as a hosted requirement if it comes 689 // from a fragment, since we will need to know the host. We 690 // also need to wrap if the requirement is a dynamic import, 691 // since that requirement will be shared with any other 692 // matching dynamic imports. 693 Requirement r = wire.getRequirement(); 694 if (!r.getResource().equals(wire.getRequirer()) 695 || Util.isDynamic(r)) 696 { 697 r = new WrappedRequirement(wire.getRequirer(), r); 698 } 699 // Wrap the capability as a hosted capability if it comes 700 // from a fragment, since we will need to know the host. 701 Capability c = wire.getCapability(); 702 if (!c.getResource().equals(wire.getProvider())) 703 { 704 c = new WrappedCapability(wire.getProvider(), c); 705 } 706 wireCandidates.add(new WireCandidate(r, c)); 707 } 708 709 // Since the resource is resolved, it could be dynamically importing, 710 // so check to see if there are candidates for any of its dynamic 711 // imports. 712 // 713 // NOTE: If the resource is dynamically importing, the fact that 714 // the dynamic import is added here last to the 715 // list is used later when checking to see if the package being 716 // dynamically imported shadows an existing provider. 717 Requirement dynamicReq = session.getDynamicRequirement(); 718 if (dynamicReq != null && resource.equals(session.getDynamicHost())) 719 { 720 // Grab first (i.e., highest priority) candidate. 721 Capability cap = allCandidates.getFirstCandidate(dynamicReq); 722 wireCandidates.add(new WireCandidate(dynamicReq, cap)); 723 } 724 } 725 else 726 { 727 for (Requirement req : resource.getRequirements(null)) 728 { 729 if (!Util.isDynamic(req)) 730 { 731 // Get the candidates for the current requirement. 732 List<Capability> candCaps = allCandidates.getCandidates(req); 733 // Optional requirements may not have any candidates. 734 if (candCaps == null) 735 { 736 continue; 737 } 738 739 // For multiple cardinality requirements, we need to grab 740 // all candidates. 741 if (Util.isMultiple(req)) 742 { 743 // Use the same requirement, but list each capability separately 744 for (Capability cap : candCaps) 745 { 746 wireCandidates.add(new WireCandidate(req, cap)); 747 } 748 } 749 // Grab first (i.e., highest priority) candidate 750 else 751 { 752 Capability cap = candCaps.get(0); 753 wireCandidates.add(new WireCandidate(req, cap)); 754 } 755 } 756 } 757 } 758 return wireCandidates; 759 } 760 getPackages( ResolveSession session, Candidates allCandidates, Map<Resource, List<WireCandidate>> allWireCandidates, Map<Resource, Packages> allPackages, Resource resource, Packages resourcePkgs)761 private static Packages getPackages( 762 ResolveSession session, 763 Candidates allCandidates, 764 Map<Resource, List<WireCandidate>> allWireCandidates, 765 Map<Resource, Packages> allPackages, 766 Resource resource, 767 Packages resourcePkgs) 768 { 769 // First, all all exported packages 770 // This has been done previously 771 772 // Second, add all imported packages to the target resource's package space. 773 for (WireCandidate wire : allWireCandidates.get(resource)) 774 { 775 // If this resource is dynamically importing, then the last requirement 776 // is the dynamic import being resolved, since it is added last to the 777 // parallel lists above. For the dynamically imported package, make 778 // sure that the resource doesn't already have a provider for that 779 // package, which would be illegal and shouldn't be allowed. 780 if (Util.isDynamic(wire.requirement)) 781 { 782 String pkgName = (String) wire.capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); 783 if (resourcePkgs.m_exportedPkgs.containsKey(pkgName) 784 || resourcePkgs.m_importedPkgs.containsKey(pkgName) 785 || resourcePkgs.m_requiredPkgs.containsKey(pkgName)) 786 { 787 throw new IllegalArgumentException( 788 "Resource " 789 + resource 790 + " cannot dynamically import package '" 791 + pkgName 792 + "' since it already has access to it."); 793 } 794 } 795 796 mergeCandidatePackages( 797 session, 798 allPackages, 799 allCandidates, 800 resourcePkgs, 801 wire.requirement, 802 wire.capability, 803 new HashSet<Capability>(), 804 new HashSet<Resource>()); 805 } 806 807 return resourcePkgs; 808 } 809 computeUses( ResolveSession session, Map<Resource, List<WireCandidate>> allWireCandidates, Map<Resource, Packages> resourcePkgMap, Resource resource)810 private void computeUses( 811 ResolveSession session, 812 Map<Resource, List<WireCandidate>> allWireCandidates, 813 Map<Resource, Packages> resourcePkgMap, 814 Resource resource) 815 { 816 List<WireCandidate> wireCandidates = allWireCandidates.get(resource); 817 Packages resourcePkgs = resourcePkgMap.get(resource); 818 // Fourth, if the target resource is unresolved or is dynamically importing, 819 // then add all the uses constraints implied by its imported and required 820 // packages to its package space. 821 // NOTE: We do not need to do this for resolved resources because their 822 // package space is consistent by definition and these uses constraints 823 // are only needed to verify the consistency of a resolving resource. The 824 // only exception is if a resolved resource is dynamically importing, then 825 // we need to calculate its uses constraints again to make sure the new 826 // import is consistent with the existing package space. 827 Wiring wiring = session.getContext().getWirings().get(resource); 828 Set<Capability> usesCycleMap = new HashSet<Capability>(); 829 830 int size = wireCandidates.size(); 831 boolean isDynamicImporting = size > 0 832 && Util.isDynamic(wireCandidates.get(size - 1).requirement); 833 834 if ((wiring == null) || isDynamicImporting) 835 { 836 // Merge uses constraints from required capabilities. 837 for (WireCandidate w : wireCandidates) 838 { 839 Requirement req = w.requirement; 840 Capability cap = w.capability; 841 // Ignore bundle/package requirements, since they are 842 // considered below. 843 if (!req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE) 844 && !req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 845 { 846 List<Requirement> blameReqs = 847 Collections.singletonList(req); 848 849 mergeUses( 850 session, 851 resource, 852 resourcePkgs, 853 cap, 854 blameReqs, 855 cap, 856 resourcePkgMap, 857 usesCycleMap); 858 } 859 } 860 // Merge uses constraints from imported packages. 861 for (List<Blame> blames : resourcePkgs.m_importedPkgs.values()) 862 { 863 for (Blame blame : blames) 864 { 865 List<Requirement> blameReqs = 866 Collections.singletonList(blame.m_reqs.get(0)); 867 868 mergeUses( 869 session, 870 resource, 871 resourcePkgs, 872 blame.m_cap, 873 blameReqs, 874 null, 875 resourcePkgMap, 876 usesCycleMap); 877 } 878 } 879 // Merge uses constraints from required bundles. 880 for (List<Blame> blames : resourcePkgs.m_requiredPkgs.values()) 881 { 882 for (Blame blame : blames) 883 { 884 List<Requirement> blameReqs = 885 Collections.singletonList(blame.m_reqs.get(0)); 886 887 mergeUses( 888 session, 889 resource, 890 resourcePkgs, 891 blame.m_cap, 892 blameReqs, 893 null, 894 resourcePkgMap, 895 usesCycleMap); 896 } 897 } 898 } 899 } 900 mergeCandidatePackages( ResolveSession session, Map<Resource, Packages> resourcePkgMap, Candidates allCandidates, Packages packages, Requirement currentReq, Capability candCap, Set<Capability> capabilityCycles, Set<Resource> visitedRequiredBundles)901 private static void mergeCandidatePackages( 902 ResolveSession session, 903 Map<Resource, Packages> resourcePkgMap, 904 Candidates allCandidates, 905 Packages packages, 906 Requirement currentReq, 907 Capability candCap, 908 Set<Capability> capabilityCycles, 909 Set<Resource> visitedRequiredBundles) 910 { 911 if (!capabilityCycles.add(candCap)) 912 { 913 return; 914 } 915 916 if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 917 { 918 mergeCandidatePackage( 919 packages.m_importedPkgs, 920 currentReq, candCap); 921 } 922 else if (candCap.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)) 923 { 924 // Get the candidate's package space to determine which packages 925 // will be visible to the current resource. 926 if (visitedRequiredBundles.add(candCap.getResource())) 927 { 928 // We have to merge all exported packages from the candidate, 929 // since the current resource requires it. 930 for (Blame blame : resourcePkgMap.get(candCap.getResource()).m_exportedPkgs.values()) 931 { 932 mergeCandidatePackage( 933 packages.m_requiredPkgs, 934 currentReq, 935 blame.m_cap); 936 } 937 // now merge in substitutes 938 for (Blame blame : resourcePkgMap.get( 939 candCap.getResource()).m_substitePkgs.values()) 940 { 941 mergeCandidatePackage(packages.m_requiredPkgs, currentReq, 942 blame.m_cap); 943 } 944 } 945 946 // If the candidate requires any other bundles with reexport visibility, 947 // then we also need to merge their packages too. 948 Wiring candWiring = session.getContext().getWirings().get(candCap.getResource()); 949 if (candWiring != null) 950 { 951 for (Wire w : candWiring.getRequiredResourceWires(null)) 952 { 953 if (w.getRequirement().getNamespace() 954 .equals(BundleNamespace.BUNDLE_NAMESPACE)) 955 { 956 if (Util.isReexport(w.getRequirement())) 957 { 958 mergeCandidatePackages( 959 session, 960 resourcePkgMap, 961 allCandidates, 962 packages, 963 currentReq, 964 w.getCapability(), 965 capabilityCycles, 966 visitedRequiredBundles); 967 } 968 } 969 } 970 } 971 else 972 { 973 for (Requirement req : candCap.getResource().getRequirements(null)) 974 { 975 if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)) 976 { 977 if (Util.isReexport(req)) 978 { 979 Capability cap = allCandidates.getFirstCandidate(req); 980 if (cap != null) 981 { 982 mergeCandidatePackages( 983 session, 984 resourcePkgMap, 985 allCandidates, 986 packages, 987 currentReq, 988 cap, 989 capabilityCycles, 990 visitedRequiredBundles); 991 } 992 } 993 } 994 } 995 } 996 } 997 } 998 mergeCandidatePackage( OpenHashMap<String, List<Blame>> packages, Requirement currentReq, Capability candCap)999 private static void mergeCandidatePackage( 1000 OpenHashMap<String, List<Blame>> packages, 1001 Requirement currentReq, Capability candCap) 1002 { 1003 if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 1004 { 1005 // Merge the candidate capability into the resource's package space 1006 // for imported or required packages, appropriately. 1007 1008 String pkgName = (String) candCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); 1009 1010 List<Requirement> blameReqs = Collections.singletonList(currentReq); 1011 1012 List<Blame> blames = packages.getOrCompute(pkgName); 1013 blames.add(new Blame(candCap, blameReqs)); 1014 1015 //dumpResourcePkgs(current, currentPkgs); 1016 } 1017 } 1018 mergeUses( ResolveSession session, Resource current, Packages currentPkgs, Capability mergeCap, List<Requirement> blameReqs, Capability matchingCap, Map<Resource, Packages> resourcePkgMap, Set<Capability> cycleMap)1019 private void mergeUses( 1020 ResolveSession session, Resource current, Packages currentPkgs, 1021 Capability mergeCap, List<Requirement> blameReqs, Capability matchingCap, 1022 Map<Resource, Packages> resourcePkgMap, 1023 Set<Capability> cycleMap) 1024 { 1025 // If there are no uses, then just return. 1026 // If the candidate resource is the same as the current resource, 1027 // then we don't need to verify and merge the uses constraints 1028 // since this will happen as we build up the package space. 1029 if (current.equals(mergeCap.getResource())) 1030 { 1031 return; 1032 } 1033 1034 // Check for cycles. 1035 if (!cycleMap.add(mergeCap)) 1036 { 1037 return; 1038 } 1039 1040 for (Capability candSourceCap : getPackageSources(mergeCap, resourcePkgMap)) 1041 { 1042 List<String> uses; 1043 // TODO: RFC-112 - Need impl-specific type 1044 // if (candSourceCap instanceof FelixCapability) 1045 // { 1046 // uses = ((FelixCapability) candSourceCap).getUses(); 1047 // } 1048 // else 1049 { 1050 String s = candSourceCap.getDirectives().get(Namespace.CAPABILITY_USES_DIRECTIVE); 1051 if (s != null && s.length() > 0) 1052 { 1053 // Parse these uses directive. 1054 uses = session.getUsesCache().get(s); 1055 if (uses == null) 1056 { 1057 uses = parseUses(s); 1058 session.getUsesCache().put(s, uses); 1059 } 1060 } 1061 else 1062 { 1063 continue; 1064 } 1065 } 1066 Packages candSourcePkgs = resourcePkgMap.get(candSourceCap.getResource()); 1067 for (String usedPkgName : uses) 1068 { 1069 List<Blame> candSourceBlames; 1070 // Check to see if the used package is exported. 1071 Blame candExportedBlame = candSourcePkgs.m_exportedPkgs.get(usedPkgName); 1072 if (candExportedBlame != null) 1073 { 1074 candSourceBlames = Collections.singletonList(candExportedBlame); 1075 } 1076 else 1077 { 1078 // If the used package is not exported, check to see if it 1079 // is required. 1080 candSourceBlames = candSourcePkgs.m_requiredPkgs.get(usedPkgName); 1081 // Lastly, if the used package is not required, check to see if it 1082 // is imported. 1083 if (candSourceBlames == null) 1084 { 1085 candSourceBlames = candSourcePkgs.m_importedPkgs.get(usedPkgName); 1086 } 1087 } 1088 1089 // If the used package cannot be found, then just ignore it 1090 // since it has no impact. 1091 if (candSourceBlames == null) 1092 { 1093 continue; 1094 } 1095 1096 ArrayMap<Set<Capability>, UsedBlames> usedPkgBlames = currentPkgs.m_usedPkgs.getOrCompute(usedPkgName); 1097 List<Blame> newBlames = new ArrayList<Blame>(); 1098 for (Blame blame : candSourceBlames) 1099 { 1100 List<Requirement> newBlameReqs; 1101 if (blame.m_reqs != null) 1102 { 1103 newBlameReqs = new ArrayList<Requirement>(blameReqs.size() + 1); 1104 newBlameReqs.addAll(blameReqs); 1105 // Only add the last requirement in blame chain because 1106 // that is the requirement wired to the blamed capability 1107 newBlameReqs.add(blame.m_reqs.get(blame.m_reqs.size() - 1)); 1108 } 1109 else 1110 { 1111 newBlameReqs = blameReqs; 1112 } 1113 newBlames.add(new Blame(blame.m_cap, newBlameReqs)); 1114 } 1115 addUsedBlames(usedPkgBlames, newBlames, matchingCap, resourcePkgMap); 1116 for (Blame newBlame : newBlames) 1117 { 1118 mergeUses(session, current, currentPkgs, newBlame.m_cap, newBlame.m_reqs, matchingCap, 1119 resourcePkgMap, cycleMap); 1120 } 1121 } 1122 } 1123 } 1124 calculatePackageSpaces( final ResolveSession session, final Candidates allCandidates, Collection<Resource> hosts)1125 private Map<Resource, Packages> calculatePackageSpaces( 1126 final ResolveSession session, 1127 final Candidates allCandidates, 1128 Collection<Resource> hosts) 1129 { 1130 final EnhancedExecutor executor = new EnhancedExecutor(session.getExecutor()); 1131 1132 // Parallel compute wire candidates 1133 final Map<Resource, List<WireCandidate>> allWireCandidates = new ConcurrentHashMap<Resource, List<WireCandidate>>(); 1134 { 1135 final ConcurrentMap<Resource, Runnable> tasks = new ConcurrentHashMap<Resource, Runnable>(allCandidates.getNbResources()); 1136 class Computer implements Runnable 1137 { 1138 final Resource resource; 1139 public Computer(Resource resource) 1140 { 1141 this.resource = resource; 1142 } 1143 public void run() 1144 { 1145 List<WireCandidate> wireCandidates = getWireCandidates(session, allCandidates, resource); 1146 allWireCandidates.put(resource, wireCandidates); 1147 for (WireCandidate w : wireCandidates) 1148 { 1149 Resource u = w.capability.getResource(); 1150 if (!tasks.containsKey(u)) 1151 { 1152 Computer c = new Computer(u); 1153 if (tasks.putIfAbsent(u, c) == null) 1154 { 1155 executor.execute(c); 1156 } 1157 } 1158 } 1159 } 1160 } 1161 for (Resource resource : hosts) 1162 { 1163 executor.execute(new Computer(resource)); 1164 } 1165 executor.await(); 1166 } 1167 1168 // Parallel get all exported packages 1169 final OpenHashMap<Resource, Packages> allPackages = new OpenHashMap<Resource, Packages>(allCandidates.getNbResources()); 1170 for (final Resource resource : allWireCandidates.keySet()) 1171 { 1172 final Packages packages = new Packages(resource); 1173 allPackages.put(resource, packages); 1174 executor.execute(new Runnable() 1175 { 1176 public void run() 1177 { 1178 calculateExportedPackages(session, allCandidates, resource, 1179 packages.m_exportedPkgs, packages.m_substitePkgs); 1180 } 1181 }); 1182 } 1183 executor.await(); 1184 1185 // Parallel compute package lists 1186 for (final Resource resource : allWireCandidates.keySet()) 1187 { 1188 executor.execute(new Runnable() 1189 { 1190 public void run() 1191 { 1192 getPackages(session, allCandidates, allWireCandidates, allPackages, resource, allPackages.get(resource)); 1193 } 1194 }); 1195 } 1196 executor.await(); 1197 1198 // Compute package sources 1199 // First, sequentially compute packages for resources 1200 // that have required packages, so that all recursive 1201 // calls can be done without threading problems 1202 for (Map.Entry<Resource, Packages> entry : allPackages.fast()) 1203 { 1204 final Resource resource = entry.getKey(); 1205 final Packages packages = entry.getValue(); 1206 if (!packages.m_requiredPkgs.isEmpty()) 1207 { 1208 getPackageSourcesInternal(session, allPackages, resource, packages); 1209 } 1210 } 1211 // Next, for all remaining resources, we can compute them 1212 // in parallel, as they won't refer to other resource packages 1213 for (Map.Entry<Resource, Packages> entry : allPackages.fast()) 1214 { 1215 final Resource resource = entry.getKey(); 1216 final Packages packages = entry.getValue(); 1217 if (packages.m_sources.isEmpty()) 1218 { 1219 executor.execute(new Runnable() 1220 { 1221 public void run() 1222 { 1223 getPackageSourcesInternal(session, allPackages, resource, packages); 1224 } 1225 }); 1226 } 1227 } 1228 executor.await(); 1229 1230 // Parallel compute uses 1231 for (final Resource resource : allWireCandidates.keySet()) 1232 { 1233 executor.execute(new Runnable() 1234 { 1235 public void run() 1236 { 1237 computeUses(session, allWireCandidates, allPackages, resource); 1238 } 1239 }); 1240 } 1241 executor.await(); 1242 1243 return allPackages; 1244 } 1245 parseUses(String s)1246 private static List<String> parseUses(String s) { 1247 int nb = 1; 1248 int l = s.length(); 1249 for (int i = 0; i < l; i++) { 1250 if (s.charAt(i) == ',') { 1251 nb++; 1252 } 1253 } 1254 List<String> uses = new ArrayList<String>(nb); 1255 int start = 0; 1256 while (true) { 1257 while (start < l) { 1258 char c = s.charAt(start); 1259 if (c != ' ' && c != ',') { 1260 break; 1261 } 1262 start++; 1263 } 1264 int end = start + 1; 1265 while (end < l) { 1266 char c = s.charAt(end); 1267 if (c == ' ' || c == ',') { 1268 break; 1269 } 1270 end++; 1271 } 1272 if (start < l) { 1273 uses.add(s.substring(start, end)); 1274 start = end + 1; 1275 } else { 1276 break; 1277 } 1278 } 1279 return uses; 1280 } 1281 addUsedBlames( ArrayMap<Set<Capability>, UsedBlames> usedBlames, Collection<Blame> blames, Capability matchingCap, Map<Resource, Packages> resourcePkgMap)1282 private void addUsedBlames( 1283 ArrayMap<Set<Capability>, UsedBlames> usedBlames, Collection<Blame> blames, Capability matchingCap, Map<Resource, Packages> resourcePkgMap) 1284 { 1285 Set<Capability> usedCaps; 1286 if (blames.size() == 1) 1287 { 1288 usedCaps = getPackageSources(blames.iterator().next().m_cap, resourcePkgMap); 1289 } 1290 else 1291 { 1292 usedCaps = new HashSet<Capability>(); 1293 for (Blame blame : blames) 1294 { 1295 usedCaps.addAll(getPackageSources(blame.m_cap, resourcePkgMap)); 1296 } 1297 } 1298 if (usedCaps.isEmpty()) 1299 { 1300 // This most likely is an issue with the resolve context. 1301 // To avoid total failure we do not add blames if there is 1302 // no source capabilities 1303 m_logger.log(Logger.LOG_INFO, 1304 "Package sources are empty for used capability: " + blames); 1305 return; 1306 } 1307 // Find UsedBlame that uses the same capability as the new blame. 1308 UsedBlames addToBlame = usedBlames.getOrCompute(usedCaps); 1309 // Add the new Blames and record the matching capability cause 1310 // in case the root requirement has multiple cardinality. 1311 for (Blame blame : blames) 1312 { 1313 addToBlame.addBlame(blame, matchingCap); 1314 } 1315 } 1316 checkPackageSpaceConsistency( ResolveSession session, Resource resource, Candidates allCandidates, boolean dynamic, Map<Resource, Packages> resourcePkgMap, Map<Resource, Object> resultCache)1317 private ResolutionError checkPackageSpaceConsistency( 1318 ResolveSession session, 1319 Resource resource, 1320 Candidates allCandidates, 1321 boolean dynamic, 1322 Map<Resource, Packages> resourcePkgMap, 1323 Map<Resource, Object> resultCache) 1324 { 1325 if (!dynamic && session.getContext().getWirings().containsKey(resource)) 1326 { 1327 return null; 1328 } 1329 Object cache = resultCache.get(resource); 1330 if (cache != null) 1331 { 1332 return cache instanceof ResolutionError ? (ResolutionError) cache : null; 1333 } 1334 1335 Packages pkgs = resourcePkgMap.get(resource); 1336 1337 ResolutionError rethrow = null; 1338 1339 // Check for conflicting imports from fragments. 1340 // TODO: Is this only needed for imports or are generic and bundle requirements also needed? 1341 // I think this is only a special case for fragment imports because they can overlap 1342 // host imports, which is not allowed in normal metadata. 1343 for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.fast()) 1344 { 1345 String pkgName = entry.getKey(); 1346 List<Blame> blames = entry.getValue(); 1347 if (blames.size() > 1) 1348 { 1349 Blame sourceBlame = null; 1350 for (Blame blame : blames) 1351 { 1352 if (sourceBlame == null) 1353 { 1354 sourceBlame = blame; 1355 } 1356 else if (!sourceBlame.m_cap.getResource().equals(blame.m_cap.getResource())) 1357 { 1358 // Try to permutate the conflicting requirement. 1359 session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(blame.m_reqs.get(0))); 1360 // Try to permutate the source requirement. 1361 session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(sourceBlame.m_reqs.get(0))); 1362 // Report conflict. 1363 rethrow = new UseConstraintError( 1364 session.getContext(), allCandidates, 1365 resource, pkgName, 1366 sourceBlame, blame); 1367 if (m_logger.isDebugEnabled()) 1368 { 1369 m_logger.debug( 1370 "Candidate permutation failed due to a conflict with a " 1371 + "fragment import; will try another if possible." 1372 + " (" + rethrow.getMessage() + ")"); 1373 } 1374 return rethrow; 1375 } 1376 } 1377 } 1378 } 1379 // IMPLEMENTATION NOTE: 1380 // Below we track the mutated reqs that have been permuted 1381 // in a single candidates permutation. This permutation may contain a 1382 // delta of several reqs which conflict with a directly imported/required candidates. 1383 // When several reqs are permuted at the same time this reduces the number of solutions tried. 1384 // See the method Candidates::canRemoveCandidate for a case where substitutions must be checked 1385 // because of this code that may permute multiple reqs in on candidates permutation. 1386 AtomicReference<Candidates> permRef1 = new AtomicReference<Candidates>(); 1387 AtomicReference<Candidates> permRef2 = new AtomicReference<Candidates>(); 1388 Set<Requirement> mutated = null; 1389 1390 // Check if there are any uses conflicts with exported packages. 1391 for (Entry<String, Blame> entry : pkgs.m_exportedPkgs.fast()) 1392 { 1393 String pkgName = entry.getKey(); 1394 Blame exportBlame = entry.getValue(); 1395 ArrayMap<Set<Capability>, UsedBlames> pkgBlames = pkgs.m_usedPkgs.get(pkgName); 1396 if (pkgBlames == null) 1397 { 1398 continue; 1399 } 1400 for (UsedBlames usedBlames : pkgBlames.values()) 1401 { 1402 if (!isCompatible(exportBlame, usedBlames.m_caps, resourcePkgMap)) 1403 { 1404 mutated = (mutated != null) 1405 ? mutated 1406 : new HashSet<Requirement>(); 1407 rethrow = permuteUsedBlames(session, rethrow, allCandidates, resource, 1408 pkgName, null, usedBlames, permRef1, permRef2, mutated); 1409 } 1410 } 1411 1412 if (rethrow != null) 1413 { 1414 if (!mutated.isEmpty()) 1415 { 1416 session.addPermutation(PermutationType.USES, permRef1.get()); 1417 session.addPermutation(PermutationType.USES, permRef2.get()); 1418 } 1419 if (m_logger.isDebugEnabled()) 1420 { 1421 m_logger.debug("Candidate permutation failed due to a conflict between " 1422 + "an export and import; will try another if possible." 1423 + " (" + rethrow.getMessage() + ")"); 1424 } 1425 return rethrow; 1426 } 1427 } 1428 1429 // Check if there are any uses conflicts with imported and required packages. 1430 // We combine the imported and required packages here into one map. 1431 // Imported packages are added after required packages because they shadow or override 1432 // the packages from required bundles. 1433 OpenHashMap<String, List<Blame>> allImportRequirePkgs; 1434 if (pkgs.m_requiredPkgs.isEmpty()) 1435 { 1436 allImportRequirePkgs = pkgs.m_importedPkgs; 1437 } 1438 else 1439 { 1440 allImportRequirePkgs = new OpenHashMap<String, List<Blame>>(pkgs.m_requiredPkgs.size() + pkgs.m_importedPkgs.size()); 1441 allImportRequirePkgs.putAll(pkgs.m_requiredPkgs); 1442 allImportRequirePkgs.putAll(pkgs.m_importedPkgs); 1443 } 1444 1445 for (Entry<String, List<Blame>> entry : allImportRequirePkgs.fast()) 1446 { 1447 String pkgName = entry.getKey(); 1448 ArrayMap<Set<Capability>, UsedBlames> pkgBlames = pkgs.m_usedPkgs.get(pkgName); 1449 if (pkgBlames == null) 1450 { 1451 continue; 1452 } 1453 List<Blame> requirementBlames = entry.getValue(); 1454 1455 for (UsedBlames usedBlames : pkgBlames.values()) 1456 { 1457 if (!isCompatible(requirementBlames, usedBlames.m_caps, resourcePkgMap)) 1458 { 1459 mutated = (mutated != null) 1460 ? mutated 1461 : new HashSet<Requirement>();// Split packages, need to think how to get a good message for split packages (sigh) 1462 // For now we just use the first requirement that brings in the package that conflicts 1463 Blame requirementBlame = requirementBlames.get(0); 1464 rethrow = permuteUsedBlames(session, rethrow, allCandidates, resource, pkgName, requirementBlame, usedBlames, permRef1, permRef2, mutated); 1465 } 1466 1467 // If there was a uses conflict, then we should add a uses 1468 // permutation if we were able to permutate any candidates. 1469 // Additionally, we should try to push an import permutation 1470 // for the original import to force a backtracking on the 1471 // original candidate decision if no viable candidate is found 1472 // for the conflicting uses constraint. 1473 if (rethrow != null) 1474 { 1475 // Add uses permutation if we m_mutated any candidates. 1476 if (!mutated.isEmpty()) 1477 { 1478 session.addPermutation(PermutationType.USES, permRef1.get()); 1479 session.addPermutation(PermutationType.USES, permRef2.get()); 1480 } 1481 1482 // Try to permutate the candidate for the original 1483 // import requirement; only permutate it if we haven't 1484 // done so already. 1485 for (Blame requirementBlame : requirementBlames) 1486 { 1487 Requirement req = requirementBlame.m_reqs.get(0); 1488 if (!mutated.contains(req)) 1489 { 1490 // Since there may be lots of uses constraint violations 1491 // with existing import decisions, we may end up trying 1492 // to permutate the same import a lot of times, so we should 1493 // try to check if that the case and only permutate it once. 1494 session.permutateIfNeeded(PermutationType.IMPORT, req, allCandidates); 1495 } 1496 } 1497 1498 if (m_logger.isDebugEnabled()) 1499 { 1500 m_logger.debug("Candidate permutation failed due to a conflict between " 1501 + "imports; will try another if possible." 1502 + " (" + rethrow.getMessage() + ")" 1503 ); 1504 } 1505 return rethrow; 1506 } 1507 } 1508 } 1509 1510 resultCache.put(resource, Boolean.TRUE); 1511 1512 // Now check the consistency of all resources on which the 1513 // current resource depends. Keep track of the current number 1514 // of permutations so we know if the lower level check was 1515 // able to create a permutation or not in the case of failure. 1516 long permCount = session.getPermutationCount(); 1517 for (Requirement req : resource.getRequirements(null)) 1518 { 1519 Capability cap = allCandidates.getFirstCandidate(req); 1520 if (cap != null) 1521 { 1522 if (!resource.equals(cap.getResource())) 1523 { 1524 rethrow = checkPackageSpaceConsistency( 1525 session, cap.getResource(), 1526 allCandidates, false, resourcePkgMap, resultCache); 1527 if (session.isCancelled()) { 1528 return null; 1529 } 1530 if (rethrow != null) 1531 { 1532 // If the lower level check didn't create any permutations, 1533 // then we should create an import permutation for the 1534 // requirement with the dependency on the failing resource 1535 // to backtrack on our current candidate selection. 1536 if (permCount == session.getPermutationCount()) 1537 { 1538 session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(req)); 1539 } 1540 return rethrow; 1541 } 1542 } 1543 } 1544 } 1545 return null; 1546 } 1547 permuteUsedBlames(ResolveSession session, ResolutionError rethrow, Candidates allCandidates, Resource resource, String pkgName, Blame requirementBlame, UsedBlames usedBlames, AtomicReference<Candidates> permRef1, AtomicReference<Candidates> permRef2, Set<Requirement> mutated)1548 private ResolutionError permuteUsedBlames(ResolveSession session, 1549 ResolutionError rethrow, Candidates allCandidates, Resource resource, 1550 String pkgName, Blame requirementBlame, UsedBlames usedBlames, 1551 AtomicReference<Candidates> permRef1, AtomicReference<Candidates> permRef2, 1552 Set<Requirement> mutated) 1553 { 1554 for (Blame usedBlame : usedBlames.m_blames) 1555 { 1556 if (session.checkMultiple(usedBlames, usedBlame, allCandidates)) 1557 { 1558 // Continue to the next usedBlame, if possible we 1559 // removed the conflicting candidates. 1560 continue; 1561 } 1562 1563 if (rethrow == null) 1564 { 1565 if (requirementBlame == null) 1566 { 1567 rethrow = new UseConstraintError(session.getContext(), allCandidates, 1568 resource, pkgName, usedBlame); 1569 } 1570 else 1571 { 1572 rethrow = new UseConstraintError(session.getContext(), allCandidates, 1573 resource, pkgName, requirementBlame, usedBlame); 1574 } 1575 } 1576 1577 // Create a candidate permutation that eliminates all candidates 1578 // that conflict with existing selected candidates going from direct requirement -> root 1579 Candidates perm1 = permRef1.get(); 1580 if (perm1 == null) 1581 { 1582 perm1 = allCandidates.copy(); 1583 permRef1.set(perm1); 1584 } 1585 for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--) 1586 { 1587 Requirement req = usedBlame.m_reqs.get(reqIdx); 1588 if (permuteUsedBlameRequirement(req, mutated, perm1)) 1589 { 1590 break; 1591 } 1592 } 1593 // Create a candidate permutation that eliminates all candidates 1594 // that conflict with existing selected candidates going from root -> direct requirement 1595 Candidates perm2 = permRef2.get(); 1596 if (perm2 == null) 1597 { 1598 perm2 = allCandidates.copy(); 1599 permRef2.set(perm2); 1600 } 1601 for (int reqIdx = 0; reqIdx < usedBlame.m_reqs.size(); reqIdx++) 1602 { 1603 Requirement req = usedBlame.m_reqs.get(reqIdx); 1604 if (permuteUsedBlameRequirement(req, mutated, perm2)) 1605 { 1606 break; 1607 } 1608 } 1609 } 1610 return rethrow; 1611 } 1612 permuteUsedBlameRequirement(Requirement req, Set<Requirement> mutated, Candidates permutation)1613 private boolean permuteUsedBlameRequirement(Requirement req, Set<Requirement> mutated, Candidates permutation) 1614 { 1615 // Sanity check for multiple. 1616 if (Util.isMultiple(req)) 1617 { 1618 return false; 1619 } 1620 // If we've already permutated this requirement in another 1621 // uses constraint, don't permutate it again just continue 1622 // with the next uses constraint. 1623 if (mutated.contains(req)) 1624 { 1625 return true; 1626 } 1627 1628 // See if we can permutate the candidates for blamed 1629 // requirement; there may be no candidates if the resource 1630 // associated with the requirement is already resolved. 1631 if (permutation.canRemoveCandidate(req)) 1632 { 1633 permutation.removeFirstCandidate(req); 1634 mutated.add(req); 1635 return true; 1636 } 1637 return false; 1638 } 1639 calculateExportedPackages( ResolveSession session, Candidates allCandidates, Resource resource, OpenHashMap<String, Blame> exports, OpenHashMap<String, Blame> substitutes)1640 private static OpenHashMap<String, Blame> calculateExportedPackages( 1641 ResolveSession session, 1642 Candidates allCandidates, 1643 Resource resource, 1644 OpenHashMap<String, Blame> exports, OpenHashMap<String, Blame> substitutes) 1645 { 1646 // Get all exported packages. 1647 Wiring wiring = session.getContext().getWirings().get(resource); 1648 List<Capability> caps = (wiring != null) 1649 ? wiring.getResourceCapabilities(null) 1650 : resource.getCapabilities(null); 1651 for (Capability cap : caps) 1652 { 1653 if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 1654 { 1655 if (!cap.getResource().equals(resource)) 1656 { 1657 cap = new WrappedCapability(resource, cap); 1658 } 1659 exports.put( 1660 (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE), 1661 new Blame(cap, null)); 1662 } 1663 } 1664 // Remove substitutable exports that were imported. 1665 // For resolved resources Wiring.getCapabilities() 1666 // already excludes imported substitutable exports, but 1667 // for resolving resources we must look in the candidate 1668 // map to determine which exports are substitutable. 1669 if (wiring != null) 1670 { 1671 for (Wire wire : session.getContext().getSubstitutionWires(wiring)) 1672 { 1673 Capability cap = wire.getCapability(); 1674 if (!cap.getResource().equals(wire.getProvider())) 1675 { 1676 cap = new WrappedCapability(wire.getProvider(), cap); 1677 } 1678 substitutes.put( 1679 // Using a null on requirement instead of the wire requirement here. 1680 // It is unclear if we want to treat the substitution requirement as a permutation req here. 1681 (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE), 1682 new Blame(cap, null)); 1683 } 1684 } 1685 else 1686 { 1687 if (!exports.isEmpty()) 1688 { 1689 for (Requirement req : resource.getRequirements(null)) 1690 { 1691 if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 1692 { 1693 Capability cand = allCandidates.getFirstCandidate(req); 1694 if (cand != null) 1695 { 1696 String pkgName = (String) cand.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); 1697 Blame blame = exports.remove(pkgName); 1698 if (blame != null) 1699 { 1700 // Using a null on requirement instead of the wire requirement here. 1701 // It is unclear if we want to treat the substitution requirement as a permutation req here. 1702 substitutes.put(pkgName, new Blame(cand, null)); 1703 } 1704 } 1705 } 1706 } 1707 } 1708 } 1709 return exports; 1710 } 1711 isCompatible( Blame currentBlame, Set<Capability> candSources, Map<Resource, Packages> resourcePkgMap)1712 private static boolean isCompatible( 1713 Blame currentBlame, Set<Capability> candSources, 1714 Map<Resource, Packages> resourcePkgMap) 1715 { 1716 if (candSources.contains(currentBlame.m_cap)) 1717 { 1718 return true; 1719 } 1720 Set<Capability> currentSources = getPackageSources(currentBlame.m_cap, resourcePkgMap); 1721 return currentSources.containsAll(candSources) 1722 || candSources.containsAll(currentSources); 1723 } 1724 isCompatible( List<Blame> currentBlames, Set<Capability> candSources, Map<Resource, Packages> resourcePkgMap)1725 private static boolean isCompatible( 1726 List<Blame> currentBlames, Set<Capability> candSources, 1727 Map<Resource, Packages> resourcePkgMap) 1728 { 1729 int size = currentBlames.size(); 1730 switch (size) 1731 { 1732 case 0: 1733 return true; 1734 case 1: 1735 return isCompatible(currentBlames.get(0), candSources, resourcePkgMap); 1736 default: 1737 Set<Capability> currentSources = new HashSet<Capability>(currentBlames.size()); 1738 for (Blame currentBlame : currentBlames) 1739 { 1740 Set<Capability> blameSources = getPackageSources(currentBlame.m_cap, resourcePkgMap); 1741 currentSources.addAll(blameSources); 1742 } 1743 return currentSources.containsAll(candSources) 1744 || candSources.containsAll(currentSources); 1745 } 1746 } 1747 getPackageSources( Capability cap, Map<Resource, Packages> resourcePkgMap)1748 private static Set<Capability> getPackageSources( 1749 Capability cap, Map<Resource, Packages> resourcePkgMap) 1750 { 1751 Resource resource = cap.getResource(); 1752 if(resource == null) 1753 { 1754 return new HashSet<Capability>(); 1755 } 1756 1757 OpenHashMap<Capability, Set<Capability>> sources = resourcePkgMap.get(resource).m_sources; 1758 if(sources == null) 1759 { 1760 return new HashSet<Capability>(); 1761 } 1762 1763 Set<Capability> packageSources = sources.get(cap); 1764 if(packageSources == null) 1765 { 1766 return new HashSet<Capability>(); 1767 } 1768 1769 return packageSources; 1770 } 1771 getPackageSourcesInternal( ResolveSession session, Map<Resource, Packages> resourcePkgMap, Resource resource, Packages packages)1772 private static void getPackageSourcesInternal( 1773 ResolveSession session, Map<Resource, Packages> resourcePkgMap, 1774 Resource resource, Packages packages) 1775 { 1776 Wiring wiring = session.getContext().getWirings().get(resource); 1777 List<Capability> caps = (wiring != null) 1778 ? wiring.getResourceCapabilities(null) 1779 : resource.getCapabilities(null); 1780 @SuppressWarnings("serial") 1781 OpenHashMap<String, Set<Capability>> pkgs = new OpenHashMap<String, Set<Capability>>(caps.size()) { 1782 public Set<Capability> compute(String pkgName) { 1783 return new HashSet<Capability>(); 1784 } 1785 }; 1786 Map<Capability, Set<Capability>> sources = packages.m_sources; 1787 for (Capability sourceCap : caps) 1788 { 1789 if (sourceCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 1790 { 1791 String pkgName = (String) sourceCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); 1792 Set<Capability> pkgCaps = pkgs.getOrCompute(pkgName); 1793 // Since capabilities may come from fragments, we need to check 1794 // for that case and wrap them. 1795 if (!resource.equals(sourceCap.getResource())) 1796 { 1797 sourceCap = new WrappedCapability(resource, sourceCap); 1798 } 1799 sources.put(sourceCap, pkgCaps); 1800 pkgCaps.add(sourceCap); 1801 } 1802 else 1803 { 1804 // Otherwise, need to return generic capabilities that have 1805 // uses constraints so they are included for consistency 1806 // checking. 1807 String uses = sourceCap.getDirectives().get(Namespace.CAPABILITY_USES_DIRECTIVE); 1808 if ((uses != null) && uses.length() > 0) 1809 { 1810 sources.put(sourceCap, Collections.singleton(sourceCap)); 1811 } 1812 else 1813 { 1814 sources.put(sourceCap, Collections.<Capability>emptySet()); 1815 } 1816 } 1817 } 1818 for (Map.Entry<String, Set<Capability>> pkg : pkgs.fast()) 1819 { 1820 String pkgName = pkg.getKey(); 1821 List<Blame> required = packages.m_requiredPkgs.get(pkgName); 1822 if (required != null) 1823 { 1824 Set<Capability> srcs = pkg.getValue(); 1825 for (Blame blame : required) 1826 { 1827 Capability bcap = blame.m_cap; 1828 if (srcs.add(bcap)) 1829 { 1830 Resource capResource = bcap.getResource(); 1831 Packages capPackages = resourcePkgMap.get(capResource); 1832 Set<Capability> additional = capPackages.m_sources.get(bcap); 1833 if (additional == null) 1834 { 1835 getPackageSourcesInternal(session, resourcePkgMap, capResource, capPackages); 1836 additional = capPackages.m_sources.get(bcap); 1837 } 1838 srcs.addAll(additional); 1839 } 1840 } 1841 } 1842 } 1843 } 1844 getDeclaredResource(Resource resource)1845 private static Resource getDeclaredResource(Resource resource) 1846 { 1847 if (resource instanceof WrappedResource) 1848 { 1849 return ((WrappedResource) resource).getDeclaredResource(); 1850 } 1851 return resource; 1852 } 1853 getDeclaredCapability(Capability c)1854 private static Capability getDeclaredCapability(Capability c) 1855 { 1856 if (c instanceof HostedCapability) 1857 { 1858 return ((HostedCapability) c).getDeclaredCapability(); 1859 } 1860 return c; 1861 } 1862 getDeclaredRequirement(Requirement r)1863 private static Requirement getDeclaredRequirement(Requirement r) 1864 { 1865 if (r instanceof WrappedRequirement) 1866 { 1867 return ((WrappedRequirement) r).getDeclaredRequirement(); 1868 } 1869 return r; 1870 } 1871 populateWireMap( ResolveSession session, Resource resource, Map<Resource, List<Wire>> wireMap, Candidates allCandidates)1872 private static Map<Resource, List<Wire>> populateWireMap( 1873 ResolveSession session, Resource resource, 1874 Map<Resource, List<Wire>> wireMap, Candidates allCandidates) 1875 { 1876 Resource unwrappedResource = getDeclaredResource(resource); 1877 if (!session.getContext().getWirings().containsKey(unwrappedResource) 1878 && !wireMap.containsKey(unwrappedResource)) 1879 { 1880 wireMap.put(unwrappedResource, Collections.<Wire>emptyList()); 1881 1882 List<Wire> packageWires = new ArrayList<Wire>(); 1883 List<Wire> bundleWires = new ArrayList<Wire>(); 1884 List<Wire> capabilityWires = new ArrayList<Wire>(); 1885 1886 for (Requirement req : resource.getRequirements(null)) 1887 { 1888 List<Capability> cands = allCandidates.getCandidates(req); 1889 if ((cands != null) && (cands.size() > 0)) 1890 { 1891 for (Capability cand : cands) 1892 { 1893 // Do not create wires for the osgi.wiring.* namespaces 1894 // if the provider and requirer are the same resource; 1895 // allow such wires for non-OSGi wiring namespaces. 1896 if (!cand.getNamespace().startsWith("osgi.wiring.") 1897 || !resource.equals(cand.getResource())) 1898 { 1899 // Populate wires for the candidate 1900 populateWireMap(session, cand.getResource(), 1901 wireMap, allCandidates); 1902 1903 Resource provider; 1904 if (req.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE)) { 1905 provider = getDeclaredCapability(cand).getResource(); 1906 } else { 1907 provider = getDeclaredResource(cand.getResource()); 1908 } 1909 Wire wire = new WireImpl( 1910 unwrappedResource, 1911 getDeclaredRequirement(req), 1912 provider, 1913 getDeclaredCapability(cand)); 1914 if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 1915 { 1916 packageWires.add(wire); 1917 } 1918 else if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)) 1919 { 1920 bundleWires.add(wire); 1921 } 1922 else 1923 { 1924 capabilityWires.add(wire); 1925 } 1926 } 1927 if (!Util.isMultiple(req)) 1928 { 1929 // If not multiple just create a wire for the first candidate. 1930 break; 1931 } 1932 } 1933 } 1934 } 1935 1936 // Combine package wires with require wires last. 1937 packageWires.addAll(bundleWires); 1938 packageWires.addAll(capabilityWires); 1939 wireMap.put(unwrappedResource, packageWires); 1940 1941 // Add host wire for any fragments. 1942 if (resource instanceof WrappedResource) 1943 { 1944 List<Resource> fragments = ((WrappedResource) resource).getFragments(); 1945 for (Resource fragment : fragments) 1946 { 1947 // Get wire list for the fragment from the wire map. 1948 // If there isn't one, then create one. Note that we won't 1949 // add the wire list to the wire map until the end, so 1950 // we can determine below if this is the first time we've 1951 // seen the fragment while populating wires to avoid 1952 // creating duplicate non-payload wires if the fragment 1953 // is attached to more than one host. 1954 List<Wire> fragmentWires = wireMap.get(fragment); 1955 fragmentWires = (fragmentWires == null) 1956 ? new ArrayList<Wire>() : fragmentWires; 1957 1958 // Loop through all of the fragment's requirements and create 1959 // any necessary wires for non-payload requirements. 1960 for (Requirement req : fragment.getRequirements(null)) 1961 { 1962 // Only look at non-payload requirements. 1963 if (!isPayload(req)) 1964 { 1965 // If this is the host requirement, then always create 1966 // a wire for it to the current resource. 1967 if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE)) 1968 { 1969 fragmentWires.add( 1970 new WireImpl( 1971 getDeclaredResource(fragment), 1972 req, 1973 unwrappedResource, 1974 unwrappedResource.getCapabilities( 1975 HostNamespace.HOST_NAMESPACE).get(0))); 1976 } 1977 // Otherwise, if the fragment isn't already resolved and 1978 // this is the first time we are seeing it, then create 1979 // a wire for the non-payload requirement. 1980 else if (!session.getContext().getWirings().containsKey(fragment) 1981 && !wireMap.containsKey(fragment)) 1982 { 1983 Wire wire = createWire(req, allCandidates); 1984 if (wire != null) 1985 { 1986 fragmentWires.add(wire); 1987 } 1988 } 1989 } 1990 } 1991 1992 // Finally, add the fragment's wire list to the wire map. 1993 wireMap.put(fragment, fragmentWires); 1994 } 1995 } 1996 // now make sure any related resources are populated 1997 for (Resource related : session.getRelatedResources(unwrappedResource)) { 1998 if (allCandidates.isPopulated(related)) { 1999 populateWireMap(session, related, wireMap, allCandidates); 2000 } 2001 } 2002 } 2003 2004 return wireMap; 2005 } 2006 createWire(Requirement requirement, Candidates allCandidates)2007 private static Wire createWire(Requirement requirement, Candidates allCandidates) 2008 { 2009 Capability cand = allCandidates.getFirstCandidate(requirement); 2010 if (cand == null) { 2011 return null; 2012 } 2013 return new WireImpl( 2014 getDeclaredResource(requirement.getResource()), 2015 getDeclaredRequirement(requirement), 2016 getDeclaredResource(cand.getResource()), 2017 getDeclaredCapability(cand)); 2018 } 2019 isPayload(Requirement fragmentReq)2020 private static boolean isPayload(Requirement fragmentReq) 2021 { 2022 // this is where we would add other non-payload namespaces 2023 if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE 2024 .equals(fragmentReq.getNamespace())) 2025 { 2026 return false; 2027 } 2028 if (HostNamespace.HOST_NAMESPACE.equals(fragmentReq.getNamespace())) 2029 { 2030 return false; 2031 } 2032 return true; 2033 } 2034 populateDynamicWireMap( ResolveSession session, Map<Resource, List<Wire>> wireMap, Candidates allCandidates)2035 private static Map<Resource, List<Wire>> populateDynamicWireMap( 2036 ResolveSession session, Map<Resource, 2037 List<Wire>> wireMap, Candidates allCandidates) 2038 { 2039 wireMap.put(session.getDynamicHost(), Collections.<Wire>emptyList()); 2040 2041 List<Wire> packageWires = new ArrayList<Wire>(); 2042 2043 // Get the candidates for the current dynamic requirement. 2044 // Record the dynamic candidate. 2045 Capability dynCand = allCandidates.getFirstCandidate(session.getDynamicRequirement()); 2046 2047 if (!session.getContext().getWirings().containsKey(dynCand.getResource())) 2048 { 2049 populateWireMap(session, dynCand.getResource(), 2050 wireMap, allCandidates); 2051 } 2052 2053 packageWires.add( 2054 new WireImpl( 2055 session.getDynamicHost(), 2056 session.getDynamicRequirement(), 2057 getDeclaredResource(dynCand.getResource()), 2058 getDeclaredCapability(dynCand))); 2059 2060 wireMap.put(session.getDynamicHost(), packageWires); 2061 2062 return wireMap; 2063 } 2064 2065 @SuppressWarnings("unused") dumpResourcePkgMap( ResolveContext rc, Map<Resource, Packages> resourcePkgMap)2066 private static void dumpResourcePkgMap( 2067 ResolveContext rc, Map<Resource, Packages> resourcePkgMap) 2068 { 2069 System.out.println("+++RESOURCE PKG MAP+++"); 2070 for (Entry<Resource, Packages> entry : resourcePkgMap.entrySet()) 2071 { 2072 dumpResourcePkgs(rc, entry.getKey(), entry.getValue()); 2073 } 2074 } 2075 dumpResourcePkgs( ResolveContext rc, Resource resource, Packages packages)2076 private static void dumpResourcePkgs( 2077 ResolveContext rc, Resource resource, Packages packages) 2078 { 2079 Wiring wiring = rc.getWirings().get(resource); 2080 System.out.println(resource 2081 + " (" + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)")); 2082 System.out.println(" EXPORTED"); 2083 for (Entry<String, Blame> entry : packages.m_exportedPkgs.entrySet()) 2084 { 2085 System.out.println(" " + entry.getKey() + " - " + entry.getValue()); 2086 } 2087 System.out.println(" IMPORTED"); 2088 for (Entry<String, List<Blame>> entry : packages.m_importedPkgs.entrySet()) 2089 { 2090 System.out.println(" " + entry.getKey() + " - " + entry.getValue()); 2091 } 2092 System.out.println(" REQUIRED"); 2093 for (Entry<String, List<Blame>> entry : packages.m_requiredPkgs.entrySet()) 2094 { 2095 System.out.println(" " + entry.getKey() + " - " + entry.getValue()); 2096 } 2097 System.out.println(" USED"); 2098 for (Entry<String, ArrayMap<Set<Capability>, UsedBlames>> entry : packages.m_usedPkgs.entrySet()) 2099 { 2100 System.out.println(" " + entry.getKey() + " - " + entry.getValue().values()); 2101 } 2102 } 2103 2104 private static final class WireCandidate 2105 { 2106 public final Requirement requirement; 2107 public final Capability capability; 2108 WireCandidate(Requirement requirement, Capability capability)2109 public WireCandidate(Requirement requirement, Capability capability) 2110 { 2111 this.requirement = requirement; 2112 this.capability = capability; 2113 } 2114 } 2115 2116 public static class Packages 2117 { 2118 public final OpenHashMap<String, Blame> m_exportedPkgs; 2119 public final OpenHashMap<String, Blame> m_substitePkgs; 2120 public final OpenHashMap<String, List<Blame>> m_importedPkgs; 2121 public final OpenHashMap<String, List<Blame>> m_requiredPkgs; 2122 public final OpenHashMap<String, ArrayMap<Set<Capability>, UsedBlames>> m_usedPkgs; 2123 public final OpenHashMap<Capability, Set<Capability>> m_sources; 2124 2125 @SuppressWarnings("serial") Packages(Resource resource)2126 public Packages(Resource resource) 2127 { 2128 int nbCaps = resource.getCapabilities(null).size(); 2129 int nbReqs = resource.getRequirements(null).size(); 2130 2131 m_exportedPkgs = new OpenHashMap<String, Blame>(nbCaps); 2132 m_substitePkgs = new OpenHashMap<String, Blame>(nbCaps); 2133 m_importedPkgs = new OpenHashMap<String, List<Blame>>(nbReqs) { 2134 public List<Blame> compute(String s) { 2135 return new ArrayList<Blame>(); 2136 } 2137 }; 2138 m_requiredPkgs = new OpenHashMap<String, List<Blame>>(nbReqs) { 2139 public List<Blame> compute(String s) { 2140 return new ArrayList<Blame>(); 2141 } 2142 }; 2143 m_usedPkgs = new OpenHashMap<String, ArrayMap<Set<Capability>, UsedBlames>>(128) { 2144 @Override 2145 protected ArrayMap<Set<Capability>, UsedBlames> compute(String s) { 2146 return new ArrayMap<Set<Capability>, UsedBlames>() { 2147 @Override 2148 protected UsedBlames compute(Set<Capability> key) { 2149 return new UsedBlames(key); 2150 } 2151 }; 2152 } 2153 }; 2154 m_sources = new OpenHashMap<Capability, Set<Capability>>(nbCaps); 2155 } 2156 } 2157 2158 private static class Blame 2159 { 2160 public final Capability m_cap; 2161 public final List<Requirement> m_reqs; 2162 Blame(Capability cap, List<Requirement> reqs)2163 public Blame(Capability cap, List<Requirement> reqs) 2164 { 2165 m_cap = cap; 2166 m_reqs = reqs; 2167 } 2168 2169 @Override toString()2170 public String toString() 2171 { 2172 return m_cap.getResource() 2173 + "." + m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE) 2174 + (((m_reqs == null) || m_reqs.isEmpty()) 2175 ? " NO BLAME" 2176 : " BLAMED ON " + m_reqs); 2177 } 2178 2179 @Override equals(Object o)2180 public boolean equals(Object o) 2181 { 2182 return (o instanceof Blame) && m_reqs.equals(((Blame) o).m_reqs) 2183 && m_cap.equals(((Blame) o).m_cap); 2184 } 2185 } 2186 2187 /* 2188 * UsedBlames hold a list of Blame that have a common used capability. 2189 * The UsedBlames stores sets of capabilities (root causes) that match a 2190 * root requirement with multiple cardinality. These causes are the 2191 * capabilities that pulled in the common used capability. 2192 * It is assumed that multiple cardinality requirements can only be 2193 * root requirements of a Blame. 2194 * 2195 * This is only true because capabilities can only use a package 2196 * capability. They cannot use any other kind of capability so we 2197 * do not have to worry about transitivity of the uses directive 2198 * from other capability types. 2199 */ 2200 private static class UsedBlames 2201 { 2202 public final Set<Capability> m_caps; 2203 public final List<Blame> m_blames = new ArrayList<ResolverImpl.Blame>(); 2204 private Map<Requirement, Set<Capability>> m_rootCauses; 2205 UsedBlames(Set<Capability> caps)2206 public UsedBlames(Set<Capability> caps) 2207 { 2208 m_caps = caps; 2209 } 2210 addBlame(Blame blame, Capability matchingRootCause)2211 public void addBlame(Blame blame, Capability matchingRootCause) 2212 { 2213 if (!m_caps.contains(blame.m_cap)) 2214 { 2215 throw new IllegalArgumentException( 2216 "Attempt to add a blame with a different used capability: " 2217 + blame.m_cap); 2218 } 2219 m_blames.add(blame); 2220 if (matchingRootCause != null) 2221 { 2222 Requirement req = blame.m_reqs.get(0); 2223 // Assumption made that the root requirement of the chain is the only 2224 // possible multiple cardinality requirement and that the matching root cause 2225 // capability is passed down from the beginning of the chain creation. 2226 if (Util.isMultiple(req)) 2227 { 2228 // The root requirement is multiple. Need to store the root cause 2229 // so that we can find it later in case the used capability which the cause 2230 // capability pulled in is a conflict. 2231 if (m_rootCauses == null) 2232 { 2233 m_rootCauses = new HashMap<Requirement, Set<Capability>>(); 2234 } 2235 Set<Capability> rootCauses = m_rootCauses.get(req); 2236 if (rootCauses == null) 2237 { 2238 rootCauses = new HashSet<Capability>(); 2239 m_rootCauses.put(req, rootCauses); 2240 } 2241 rootCauses.add(matchingRootCause); 2242 } 2243 } 2244 } 2245 getRootCauses(Requirement req)2246 public Set<Capability> getRootCauses(Requirement req) 2247 { 2248 if (m_rootCauses == null) 2249 { 2250 return Collections.emptySet(); 2251 } 2252 Set<Capability> result = m_rootCauses.get(req); 2253 return result == null ? Collections.<Capability>emptySet() : result; 2254 } 2255 2256 @Override toString()2257 public String toString() 2258 { 2259 return m_blames.toString(); 2260 } 2261 } 2262 2263 private static final class UseConstraintError extends ResolutionError { 2264 2265 private final ResolveContext m_context; 2266 private final Candidates m_allCandidates; 2267 private final Resource m_resource; 2268 private final String m_pkgName; 2269 private final Blame m_blame1; 2270 private final Blame m_blame2; 2271 UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame)2272 public UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame) { 2273 this(context, allCandidates, resource, pkgName, blame, null); 2274 } 2275 UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame1, Blame blame2)2276 public UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame1, Blame blame2) { 2277 this.m_context = context; 2278 this.m_allCandidates = allCandidates; 2279 this.m_resource = resource; 2280 this.m_pkgName = pkgName; 2281 if (blame1 == null) 2282 { 2283 throw new NullPointerException("First blame cannot be null."); 2284 } 2285 this.m_blame1 = blame1; 2286 this.m_blame2 = blame2; 2287 } 2288 getMessage()2289 public String getMessage() { 2290 if (m_blame2 == null) 2291 { 2292 return "Uses constraint violation. Unable to resolve resource " 2293 + Util.getSymbolicName(m_resource) 2294 + " [" + m_resource 2295 + "] because it exports package '" 2296 + m_pkgName 2297 + "' and is also exposed to it from resource " 2298 + Util.getSymbolicName(m_blame1.m_cap.getResource()) 2299 + " [" + m_blame1.m_cap.getResource() 2300 + "] via the following dependency chain:\n\n" 2301 + toStringBlame(m_blame1); 2302 } 2303 else 2304 { 2305 return "Uses constraint violation. Unable to resolve resource " 2306 + Util.getSymbolicName(m_resource) 2307 + " [" + m_resource 2308 + "] because it is exposed to package '" 2309 + m_pkgName 2310 + "' from resources " 2311 + Util.getSymbolicName(m_blame1.m_cap.getResource()) 2312 + " [" + m_blame1.m_cap.getResource() 2313 + "] and " 2314 + Util.getSymbolicName(m_blame2.m_cap.getResource()) 2315 + " [" + m_blame2.m_cap.getResource() 2316 + "] via two dependency chains.\n\nChain 1:\n" 2317 + toStringBlame(m_blame1) 2318 + "\n\nChain 2:\n" 2319 + toStringBlame(m_blame2); 2320 } 2321 } 2322 getUnresolvedRequirements()2323 public Collection<Requirement> getUnresolvedRequirements() { 2324 if (m_blame2 == null) 2325 { 2326 // This is an export conflict so there is only the first blame; 2327 // use its requirement. 2328 return Collections.singleton(m_blame1.m_reqs.get(0)); 2329 } 2330 else 2331 { 2332 return Collections.singleton(m_blame2.m_reqs.get(0)); 2333 } 2334 } 2335 toStringBlame(Blame blame)2336 private String toStringBlame(Blame blame) 2337 { 2338 StringBuilder sb = new StringBuilder(); 2339 if ((blame.m_reqs != null) && !blame.m_reqs.isEmpty()) 2340 { 2341 for (int i = 0; i < blame.m_reqs.size(); i++) 2342 { 2343 Requirement req = blame.m_reqs.get(i); 2344 sb.append(" "); 2345 sb.append(Util.getSymbolicName(req.getResource())); 2346 sb.append(" ["); 2347 sb.append(req.getResource().toString()); 2348 sb.append("]\n"); 2349 if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 2350 { 2351 sb.append(" import: "); 2352 } 2353 else 2354 { 2355 sb.append(" require: "); 2356 } 2357 sb.append(req.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE)); 2358 sb.append("\n |"); 2359 if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 2360 { 2361 sb.append("\n export: "); 2362 } 2363 else 2364 { 2365 sb.append("\n provide: "); 2366 } 2367 if ((i + 1) < blame.m_reqs.size()) 2368 { 2369 Capability cap = getSatisfyingCapability(blame.m_reqs.get(i)); 2370 if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) 2371 { 2372 sb.append(PackageNamespace.PACKAGE_NAMESPACE); 2373 sb.append("="); 2374 sb.append(cap.getAttributes() 2375 .get(PackageNamespace.PACKAGE_NAMESPACE)); 2376 Capability usedCap = 2377 getSatisfyingCapability(blame.m_reqs.get(i + 1)); 2378 sb.append("; uses:="); 2379 sb.append(usedCap.getAttributes() 2380 .get(PackageNamespace.PACKAGE_NAMESPACE)); 2381 } 2382 else 2383 { 2384 sb.append(cap); 2385 } 2386 sb.append("\n"); 2387 } 2388 else 2389 { 2390 Capability export = getSatisfyingCapability(blame.m_reqs.get(i)); 2391 sb.append(export.getNamespace()); 2392 sb.append(": "); 2393 Object namespaceVal = export.getAttributes().get(export.getNamespace()); 2394 if (namespaceVal != null) 2395 { 2396 sb.append(namespaceVal.toString()); 2397 } 2398 else 2399 { 2400 for (Entry<String, Object> attrEntry : export.getAttributes().entrySet()) 2401 { 2402 sb.append(attrEntry.getKey()).append('=') 2403 .append(attrEntry.getValue()).append(';'); 2404 } 2405 } 2406 if (export.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE) 2407 && !export.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE) 2408 .equals(blame.m_cap.getAttributes().get( 2409 PackageNamespace.PACKAGE_NAMESPACE))) 2410 { 2411 sb.append("; uses:="); 2412 sb.append(blame.m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)); 2413 sb.append("\n export: "); 2414 sb.append(PackageNamespace.PACKAGE_NAMESPACE); 2415 sb.append("="); 2416 sb.append(blame.m_cap.getAttributes() 2417 .get(PackageNamespace.PACKAGE_NAMESPACE)); 2418 } 2419 sb.append("\n "); 2420 sb.append(Util.getSymbolicName(blame.m_cap.getResource())); 2421 sb.append(" ["); 2422 sb.append(blame.m_cap.getResource().toString()); 2423 sb.append("]"); 2424 } 2425 } 2426 } 2427 else 2428 { 2429 sb.append(blame.m_cap.getResource().toString()); 2430 } 2431 return sb.toString(); 2432 } 2433 getSatisfyingCapability(Requirement req)2434 private Capability getSatisfyingCapability(Requirement req) 2435 { 2436 // If the requiring revision is not resolved, then check in the 2437 // candidate map for its matching candidate. 2438 Capability cap = m_allCandidates.getFirstCandidate(req); 2439 // Otherwise, if the requiring revision is resolved then check 2440 // in its wires for the capability satisfying the requirement. 2441 if (cap == null && m_context.getWirings().containsKey(req.getResource())) 2442 { 2443 List<Wire> wires = 2444 m_context.getWirings().get(req.getResource()).getRequiredResourceWires(null); 2445 req = getDeclaredRequirement(req); 2446 for (Wire w : wires) 2447 { 2448 if (w.getRequirement().equals(req)) 2449 { 2450 // TODO: RESOLVER - This is not 100% correct, since requirements for 2451 // dynamic imports with wildcards will reside on many wires and 2452 // this code only finds the first one, not necessarily the correct 2453 // one. This is only used for the diagnostic message, but it still 2454 // could confuse the user. 2455 cap = w.getCapability(); 2456 break; 2457 } 2458 } 2459 } 2460 2461 return cap; 2462 } 2463 2464 @Override toException()2465 public ResolutionException toException() 2466 { 2467 return new ReasonException(ReasonException.Reason.UseConstraint, getMessage(), null, getUnresolvedRequirements()); 2468 } 2469 } 2470 2471 private static class EnhancedExecutor 2472 { 2473 private final Executor executor; 2474 private final Queue<Future<Void>> awaiting = new ConcurrentLinkedQueue<Future<Void>>(); 2475 private final AtomicReference<Throwable> throwable = new AtomicReference<Throwable>(); 2476 EnhancedExecutor(Executor executor)2477 public EnhancedExecutor(Executor executor) 2478 { 2479 this.executor = executor; 2480 } 2481 execute(final Runnable runnable)2482 public void execute(final Runnable runnable) 2483 { 2484 FutureTask<Void> task = new FutureTask<Void>(new Runnable() 2485 { 2486 public void run() 2487 { 2488 try 2489 { 2490 runnable.run(); 2491 } 2492 catch (Throwable t) 2493 { 2494 throwable.compareAndSet(null, t); 2495 } 2496 } 2497 }, (Void) null); 2498 // must have a happens-first to add the task to awaiting 2499 awaiting.add(task); 2500 try 2501 { 2502 executor.execute(task); 2503 } 2504 catch (Throwable t) 2505 { 2506 // if the task did not get added successfully to the executor we must cancel 2507 // the task so we don't await on it 2508 task.cancel(false); 2509 throwable.compareAndSet(null, t); 2510 } 2511 } 2512 await()2513 public void await() 2514 { 2515 Future<Void> awaitTask; 2516 while (throwable.get() == null && (awaitTask = awaiting.poll()) != null) 2517 { 2518 if (!awaitTask.isDone() && !awaitTask.isCancelled()) 2519 { 2520 try 2521 { 2522 awaitTask.get(); 2523 } 2524 catch (CancellationException e) 2525 { 2526 // ignore; will have throwable set 2527 } 2528 catch (InterruptedException e) 2529 { 2530 throw new IllegalStateException(e); 2531 } 2532 catch (ExecutionException e) 2533 { 2534 throw new RuntimeException(e.getCause()); 2535 } 2536 } 2537 } 2538 Throwable t = throwable.get(); 2539 if (t != null) 2540 { 2541 if (t instanceof Runnable) 2542 { 2543 throw (RuntimeException) t; 2544 } 2545 else if (t instanceof Error) 2546 { 2547 throw (Error) t; 2548 } 2549 throw new RuntimeException(t); 2550 } 2551 } 2552 } 2553 2554 static class DumbExecutor implements Executor 2555 { execute(Runnable command)2556 public void execute(Runnable command) 2557 { 2558 command.run(); 2559 } 2560 } 2561 2562 } 2563