diff --git a/dev/com.ibm.ws.install/src/com/ibm/ws/install/internal/InstallKernelMap.java b/dev/com.ibm.ws.install/src/com/ibm/ws/install/internal/InstallKernelMap.java index e3e94ae255d..6d28caa2fc9 100755 --- a/dev/com.ibm.ws.install/src/com/ibm/ws/install/internal/InstallKernelMap.java +++ b/dev/com.ibm.ws.install/src/com/ibm/ws/install/internal/InstallKernelMap.java @@ -963,9 +963,8 @@ public Collection singleFileResolve() { if (!isInstallServerFeature) { resolveResult = resolver.resolve((Collection) data.get(InstallConstants.FEATURES_TO_RESOLVE)); } else { - resolveResult = resolver.resolveAsSet((Collection) data.get(InstallConstants.FEATURES_TO_RESOLVE)); - // TODO - After Resolver changes, also start passing platforms - // (Collection) data.get(InstallConstants.PLATFORMS)); + resolveResult = resolver.resolveAsSet((Collection) data.get(InstallConstants.FEATURES_TO_RESOLVE), + (Collection) data.get(InstallConstants.PLATFORMS)); } if (!resolveResult.isEmpty()) { diff --git a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolutionException.java b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolutionException.java index e1dd9f864f7..a1d37a2b289 100755 --- a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolutionException.java +++ b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolutionException.java @@ -1,10 +1,10 @@ /******************************************************************************* - * Copyright (c) 2014, 2019 IBM Corporation and others. + * Copyright (c) 2014, 2024 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 * * Contributors: @@ -15,8 +15,10 @@ import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.osgi.service.resolver.ResolutionException; @@ -40,14 +42,17 @@ public class RepositoryResolutionException extends RepositoryException { private final Collection missingProductInformation; private final Collection allRequirementsResourcesNotFound; private final Map> featureConflicts; + private Set resolvedPlatforms; + private Set missingPlatforms; + private List missingBasePlatforms; /** * @param cause * @param topLevelFeaturesNotResolved * @param allRequirementsNotFound - * @param missingProductInformation all the product information requirements that could not be found. Can be empty but must not be null + * @param missingProductInformation all the product information requirements that could not be found. Can be empty but must not be null * @param allRequirementsResourcesNotFound The {@link MissingRequirement} objects that were not found. Must not be null. - * @param featureConflicts the details of any feature conflicts which occurred during feature resolution, as returned from {@link Result#getConflicts()} + * @param featureConflicts the details of any feature conflicts which occurred during feature resolution, as returned from {@link Result#getConflicts()} */ public RepositoryResolutionException(ResolutionException cause, Collection topLevelFeaturesNotResolved, Collection allRequirementsNotFound, Collection missingProductInformation, Collection allRequirementsResourcesNotFound, @@ -60,6 +65,33 @@ public RepositoryResolutionException(ResolutionException cause, Collectionnull + * @param allRequirementsResourcesNotFound The {@link MissingRequirement} objects that were not found. Must not be null. + * @param featureConflicts the details of any feature conflicts which occurred during feature resolution, as returned from {@link Result#getConflicts()} + * @param resolvedPlatforms + * @param missingPlatforms Unknown platform names + * @param missingBasePlatforms unresolved versionless features needing platforms defined + */ + public RepositoryResolutionException(ResolutionException cause, Collection topLevelFeaturesNotResolved, Collection allRequirementsNotFound, + Collection missingProductInformation, Collection allRequirementsResourcesNotFound, + Map> featureConflicts, Set resolvedPlatforms, Set missingPlatforms, + List missingBasePlatforms) { + super(cause); + this.topLevelFeaturesNotResolved = topLevelFeaturesNotResolved; + this.allRequirementsNotFound = allRequirementsNotFound; + this.missingProductInformation = missingProductInformation; + this.allRequirementsResourcesNotFound = allRequirementsResourcesNotFound; + this.featureConflicts = featureConflicts; + this.resolvedPlatforms = resolvedPlatforms; + this.missingPlatforms = missingPlatforms; + this.missingBasePlatforms = missingBasePlatforms; + + } + /** * Returns a collection of top level feature names that were not resolved. * @@ -106,9 +138,9 @@ public ResolutionException getCause() { * on a {@link ProductRequirementInformation} is not in the form digit.digit.digit.digit then it will be ignored. * * @param productId The product ID to find the minimum missing version for or null to match to all products - * @param version The version to find the minimum missing version for by matching the first three parts so if you supply "9.0.0.0" and this item applies to version "8.5.5.3" - * and "9.0.0.1" then "9.0.0.1" will be returned. Supply null to match all versions - * @param edition The edition to find the minimum missing version for or null to match to all products + * @param version The version to find the minimum missing version for by matching the first three parts so if you supply "9.0.0.0" and this item applies to version "8.5.5.3" + * and "9.0.0.1" then "9.0.0.1" will be returned. Supply null to match all versions + * @param edition The edition to find the minimum missing version for or null to match to all products * @return The minimum missing version or null if there were no relevant matches */ public String getMinimumVersionForMissingProduct(String productId, String version, String edition) { @@ -152,7 +184,7 @@ private Collection filterVersions(Collection min * This method will iterate through the missingProductInformation and returned a filtered collection of all the {@link ProductRequirementInformation#versionRange}s. * * @param productId The product ID to find the version for or null to match to all products - * @param edition The edition to find the version for or null to match to all editions + * @param edition The edition to find the version for or null to match to all editions * * @return the version ranges which apply to the given product ID and edition */ @@ -185,9 +217,9 @@ private Collection filterVersionRanges(String productId, St * indicate a fairly odd repository setup.

* * @param productId The product ID to find the maximum missing version for or null to match to all products - * @param version The version to find the maximum missing version for by matching the first three parts so if you supply "8.5.5.2" and this item applies to version "8.5.5.3" - * and "9.0.0.1" then "8.5.5.3" will be returned. Supply null to match all versions - * @param edition The edition to find the maximum missing version for or null to match to all products + * @param version The version to find the maximum missing version for by matching the first three parts so if you supply "8.5.5.2" and this item applies to version "8.5.5.3" + * and "9.0.0.1" then "8.5.5.3" will be returned. Supply null to match all versions + * @param edition The edition to find the maximum missing version for or null to match to all products * @return The maximum missing version or null if there were no relevant matches or the maximum version is unbounded */ public String getMaximumVersionForMissingProduct(String productId, String version, String edition) { @@ -251,6 +283,16 @@ public Map> getFeatureConflicts() { @Override public String getMessage() { StringBuilder sb = new StringBuilder(); + + if (!getMissingPlatforms().isEmpty()) { + for (String missing : getMissingPlatforms()) { + sb.append("Platform: ").append(missing).append(" couldn't be found, no versionless features will be resolved").append("\n"); + } + } + if (getResolvedPlatforms().isEmpty() && getMissingPlatforms().isEmpty()) { + sb.append("Platform couldn't be determined, no versionless features will be resolved").append("\n"); + } + for (String missing : getTopLevelFeaturesNotResolved()) { sb.append("Top level feature not resolved: resource=").append(missing).append("\n"); } @@ -329,4 +371,28 @@ private String getResourceName(RepositoryResource resource) { } } + /** + * This states the target platforms that were used during the resolution + * @return the resolvedPlatforms + */ + public Set getResolvedPlatforms() { + return resolvedPlatforms; + } + + /** + * This describes missspelled or unknown platform names, official names are collected by the feature metadata + * @return the missingPlatforms + */ + public Set getMissingPlatforms() { + return missingPlatforms; + } + + /** + * This describes base platforms like "jakartaee" that are not derived, either by passed platform values, or by other included versioned features + * @return the missingBasePlatforms + */ + public List getMissingBasePlatforms() { + return missingBasePlatforms; + } + } diff --git a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolver.java b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolver.java index f2d6ca50aa4..8d823ad7b5a 100755 --- a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolver.java +++ b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/RepositoryResolver.java @@ -82,6 +82,11 @@ public class RepositoryResolver { */ Set requestedFeatureNames; + /** + * The platforms passed to {@link #resolve(Collection)} which will help resolve versionless features + */ + Collection requestedPlatformNames; + /** * The list of samples the user has requested to install */ @@ -113,6 +118,11 @@ public class RepositoryResolver { */ List resourcesWrongProduct; + /** + * List of platform names that were determined after resolve if versionless features requested, an empty list indicates a failed resolution if versionless features requested. + */ + Set resolvedPlatforms; + /** * List of requirements which couldn't be resolved but for which we found a solution that applied to the wrong product *

@@ -131,6 +141,14 @@ public class RepositoryResolver { * List of all the missing requirements we've found so far */ List missingRequirements; + /** + * List of all the missing platforms after resolution + */ + Set missingPlatforms; + /** + * returns if versionless features are part of the resolution - used to skip extra processing + */ + boolean includesVersionless; /** *

@@ -308,7 +326,7 @@ void indexSamples() { * @throws RepositoryResolutionException If the resource cannot be resolved */ public Collection> resolve(Collection toResolve) throws RepositoryResolutionException { - return resolve(toResolve, ResolutionMode.IGNORE_CONFLICTS); + return resolve(toResolve, null, ResolutionMode.IGNORE_CONFLICTS); } /** @@ -347,6 +365,8 @@ public Collection> resolve(String toResolve) throws Rep * For example, if {@code ejbLite-3.2} is already installed and {@code resolve(Arrays.asList("cdi-2.0"))} is called, it will not return the autofeature which would be required * for {@code cdi-2.0} and {@code ejbLite-3.2} to work together. * + * @deprecated - calling this method should be replaced by passing the platform list, required to support versionless features. + * * @param toResolve A collection of the identifiers of the resources to resolve. It should be in the form:
* {name}/{version}
*

Where the {name} can be either the symbolic name, short name or lower case short name of the resource and /{version} is @@ -369,15 +389,80 @@ public Collection> resolve(String toResolve) throws Rep * * @throws RepositoryResolutionException If the resource cannot be resolved */ + @Deprecated public Collection> resolveAsSet(Collection toResolve) throws RepositoryResolutionException { - return resolve(toResolve, ResolutionMode.DETECT_CONFLICTS); + return resolve(toResolve, null, ResolutionMode.DETECT_CONFLICTS); } - Collection> resolve(Collection toResolve, ResolutionMode resolutionMode) throws RepositoryResolutionException { + protected boolean hasRequestedVersionlessFeatures(Collection featureList, KernelResolverRepository repo) { + for (String s : featureList) { + ProvisioningFeatureDefinition feature = repo.getFeature(s); + if (feature == null) + //Can't find the feature of that name - just skip for now.... + continue; + if (feature.isVersionless()) { + return true; + } + } + return false; + } + + /** + * Takes a list of feature names that the user wants to install and returns a minimal set of the {@link RepositoryResource}s that should be installed to allow those features to + * start together in one server. + *

+ * This method uses the same resolution logic that is used by the kernel at server startup to decide which features to start. Therefore calling this method with a list of + * feature names and installing the resources returned will guarantee that a server which has the same list of feature names in its server.xml will start. + *

+ * The caller must provide the full set of features from the server.xml, including those that are already installed, so that tolerated dependencies and auto-features can be + * resolved correctly. + *

+ * This method will fail if there's no valid set of dependencies for the required features that doesn't include conflicting versions of singleton features. + *

+ * For example, {@code resolve(Arrays.asList("javaee-7.0", "javaee-8.0"))} would work but {@code resolveAsSet(Arrays.asList("javaee-7.0", "javaee-8.0"))} would fail because + * javaee-7.0 and javaee-8.0 contain features which conflict with each other (and other versions are not tolerated). + *

+ * This method guarantees that it will return all the features required to start the requested features but will not ensure that the requested features will work with features + * which were already installed but were not requested in the call to this method. + *

+ * For example, if {@code ejbLite-3.2} is already installed and {@code resolve(Arrays.asList("cdi-2.0"))} is called, it will not return the autofeature which would be required + * for {@code cdi-2.0} and {@code ejbLite-3.2} to work together. + * + * @param toResolve A collection of the identifiers of the resources to resolve. It should be in the form:
+ * {name}/{version}
+ *

Where the {name} can be either the symbolic name, short name or lower case short name of the resource and /{version} is + * optional. The collection may contain a mixture of symbolic names and short names. Must not be null or empty.

+ * @param platforms A collection of the identifiers of the platforms used for resolving versionless features + * + * @return

A collection of ordered lists of {@link RepositoryResource}s to install. Each list represents a collection of resources that must be installed together or not + * at all. They should be installed in the iteration order of the list(s). Note that if a resource is required by multiple different resources then it will appear in + * multiple lists. For instance if you have requested to install A and B and A requires N which requires M and O whereas B requires Z that requires O then the returned + * collection will be (represented in JSON):

+ * + * [[M, O, N, A],[O, Z, B]] + * + *

This will not return null although it may return an empty collection if there isn't anything to install (i.e. it resolves to resources that are + * already installed)

+ *

Every auto-feature will have it's own list in the collection, this is to stop the failure to install either an auto feature or one of it's dependencies from + * stopping everything from installing. Therefore if you have features A and B that are required to provision auto feature C and you ask to resolve A and B then this + * method will return:

+ * + * [[A],[B],[A,B,C]] + * + * + * @throws RepositoryResolutionException If the resource cannot be resolved + */ + public Collection> resolveAsSet(Collection toResolve, Collection platforms) throws RepositoryResolutionException { + return resolve(toResolve, platforms, ResolutionMode.DETECT_CONFLICTS); + } + + Collection> resolve(Collection toResolve, Collection platforms, ResolutionMode resolutionMode) throws RepositoryResolutionException { initResolve(); initializeResolverRepository(installDefinition); processNames(toResolve); + //Set platform names passed for kernel resolver resolution + requestedPlatformNames = platforms; if (resolutionMode == ResolutionMode.DETECT_CONFLICTS) { // Call the kernel resolver to determine the features needed @@ -416,6 +501,9 @@ void initResolve() { missingRequirements = new ArrayList<>(); resolverRepository = null; featureConflicts = new HashMap<>(); + resolvedPlatforms = new HashSet<>(); + missingPlatforms = new HashSet<>(); + includesVersionless = false; } /** @@ -477,9 +565,14 @@ private NameAndVersion splitRequestedNameAndVersion(String nameAndVersion) { */ void resolveFeaturesAsSet() { FeatureResolver resolver = new FeatureResolverImpl(); - Result result = resolver.resolve(resolverRepository, kernelFeatures, featureNamesToResolve, Collections. emptySet(), false, Collections. emptySet()); + Result result = resolver.resolve(resolverRepository, kernelFeatures, featureNamesToResolve, Collections. emptySet(), false, requestedPlatformNames); featureConflicts.putAll(result.getConflicts()); + if (hasRequestedVersionlessFeatures(featureNamesToResolve, resolverRepository)) { + includesVersionless = true; + resolvedPlatforms = result.getResolvedPlatforms(); + missingPlatforms = result.getMissingPlatforms(); + } for (String name : result.getResolvedFeatures()) { ProvisioningFeatureDefinition feature = resolverRepository.getFeature(name); @@ -900,11 +993,31 @@ private EsaResource getResource(ProvisioningFeatureDefinition feature) { * @throws RepositoryResolutionException if any errors occurred during resolution */ private void reportErrors() throws RepositoryResolutionException { - if (resourcesWrongProduct.isEmpty() && missingTopLevelRequirements.isEmpty() && missingRequirements.isEmpty() && featureConflicts.isEmpty()) { + if (resourcesWrongProduct.isEmpty() && missingTopLevelRequirements.isEmpty() && missingRequirements.isEmpty() && featureConflicts.isEmpty() + && (!includesVersionless || ((!resolvedPlatforms.isEmpty()) && (missingPlatforms.isEmpty())))) { // Everything went fine! return; } + List missingBasePlatforms = new ArrayList(); + + // Versionless features can't be resolved if a corresponding platform is not derived, making the choice ambiguous, and the feature won't be included in the resolved list. - will gather the associated platform unable to target. + if (includesVersionless) { + for (String name : missingTopLevelRequirements) { + ProvisioningFeatureDefinition feature = resolverRepository.getFeature(name); + if (feature != null && feature.isVersionless()) { + List featureChildren = resolverRepository.findAllPossibleVersions(feature); + if (!featureChildren.isEmpty()) { + ProvisioningFeatureDefinition firstChild = featureChildren.get(0); + String plat = firstChild.getPlatformName(); + if (plat != null) {//This will add just the platform name without version + missingBasePlatforms.add(resolverRepository.getFeatureBaseName(plat)); + } + } + } + } + } + Set missingProductInformation = new HashSet<>(); for (ApplicableToProduct esa : resourcesWrongProduct) { @@ -931,7 +1044,8 @@ private void reportErrors() throws RepositoryResolutionException { missingRequirementNames.add(req.getRequirementName()); } - throw new RepositoryResolutionException(null, missingTopLevelRequirements, missingRequirementNames, missingProductInformation, missingRequirements, featureConflicts); + throw new RepositoryResolutionException(null, missingTopLevelRequirements, missingRequirementNames, missingProductInformation, missingRequirements, featureConflicts, + resolvedPlatforms, missingPlatforms, missingBasePlatforms); } static class NameAndVersion { diff --git a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverEsa.java b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverEsa.java index 4ba46596c12..f1cd8c9c496 100644 --- a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverEsa.java +++ b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverEsa.java @@ -227,20 +227,49 @@ public boolean isSupportedFeatureVersion() { } // - + @Override public List getPlatformNames() { - return null; // TODO + + return esaResource.getPlatforms() == null ? new ArrayList() : new ArrayList(esaResource.getPlatforms()); } @Override public String getPlatformName() { - return null; // TODO + return (!getPlatformNames().isEmpty() ? getPlatformNames().get(0) : null); } - + + /** + * Tell if this is a versionless feature. + * + * Currently these are: + * + *
  • public
  • + *
  • platformless
  • + *
  • have a short name that is equal to the feature name
  • + *
  • contain ".versionless." in their symbolic name.
  • + *
  • does not contain ".internal.versionless." in their symbolic name.
  • + *
+ * + * @return True or false telling if this is a versionless feature. + */ @Override public boolean isVersionless() { - return false; // TODO + if (!getVisibility().equals(Visibility.PUBLIC) || (getPlatformName() != null)) { + return false; + } + + String shortName = getIbmShortName(); + if ((shortName == null) || !shortName.equals(getFeatureName())) { + return false; + } + + if (getSymbolicName().contains(".versionless.") + && !getSymbolicName().contains(".internal.")) { + return true; + } else { + return false; + } } @Override @@ -248,8 +277,23 @@ public boolean isConvenience() { return false; // TODO } + /** + * Tell if this is a compatibility feature. + * + *
  • private
  • + *
  • do not have a short name
  • + *
  • has a platform value
  • + *
+ * + * @return True or false telling if this is a versionless feature. + */ @Override public boolean isCompatibility() { - return false; // TODO + if (!getVisibility().equals(Visibility.PRIVATE)) { + return false; + } else if (getIbmShortName() != null) { + return false; + } + return (getPlatformName() != null); } } diff --git a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverRepository.java b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverRepository.java index 911d9d42b8b..422c245bdd3 100644 --- a/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverRepository.java +++ b/dev/com.ibm.ws.repository.resolver/src/com/ibm/ws/repository/resolver/internal/kernel/KernelResolverRepository.java @@ -24,7 +24,9 @@ import org.osgi.framework.Version; import com.ibm.ws.kernel.feature.Visibility; +import com.ibm.ws.kernel.feature.provisioning.FeatureResource; import com.ibm.ws.kernel.feature.provisioning.ProvisioningFeatureDefinition; +import com.ibm.ws.kernel.feature.provisioning.SubsystemContentType; import com.ibm.ws.kernel.feature.resolver.FeatureResolver; import com.ibm.ws.repository.common.enums.FilterableAttribute; import com.ibm.ws.repository.common.enums.ResourceType; @@ -244,6 +246,75 @@ public ProvisioningFeatureDefinition getFeature(String featureName) { return feature; } + /** + * Answer the list of public versioned features derived from the passed versionless feature or empty List if doesn't exist. + * + * @return List + */ + public List findAllPossibleVersions(ProvisioningFeatureDefinition versionlessFeature) { + ProvisioningFeatureDefinition publicFeature = null; + List result = new ArrayList<>(); + for (FeatureResource dependency : versionlessFeature.getConstituents(SubsystemContentType.FEATURE_TYPE)) { + publicFeature = getVersionedFeature(dependency.getSymbolicName()); + if (publicFeature != null) + result.add(publicFeature); + + String baseName = getFeatureBaseName(dependency.getSymbolicName()); + List tolerates = dependency.getTolerates(); + if (tolerates != null) { + for (String toleratedVersion : tolerates) { + String featureName = baseName + toleratedVersion; + publicFeature = getVersionedFeature(featureName); + if (publicFeature != null) + result.add(publicFeature); + } + } + } + return result; + } + + /** + * + * Answer the public versioned feature based on the internal versionless linking feature, or null if can't be found + * + * @param versionlessLinkingFeatureName + * @return ProvisioningFeatureDefinition + */ + private ProvisioningFeatureDefinition getVersionedFeature(String versionlessLinkingFeatureName) { + + ProvisioningFeatureDefinition feature = getFeature(versionlessLinkingFeatureName); + if (feature != null) { + //This is the versionless linking feature pointing to a public versioned feature + for (FeatureResource versionedFeature : feature.getConstituents(SubsystemContentType.FEATURE_TYPE)) { + //Find the right public feature (should only be one) - set the result + ProvisioningFeatureDefinition versionedFeatureDef = getFeature(versionedFeature.getSymbolicName()); + if (versionedFeatureDef.getVisibility() == Visibility.PUBLIC) { + return versionedFeatureDef; + } + } + } + return null; + } + + /** + * Removes the version from the end of a feature symbolic name + *

+ * The version is presumed to start after the last dash character in the name. + *

+ * E.g. {@code getFeatureBaseName("com.example.featureA-1.0")} returns {@code "com.example.featureA-"} + * + * @param nameAndVersion the feature symbolic name + * @return the feature symbolic name with any version stripped + */ + public String getFeatureBaseName(String nameAndVersion) { + int dashPosition = nameAndVersion.lastIndexOf('-'); + if (dashPosition != -1) { + return nameAndVersion.substring(0, dashPosition + 1); + } else { + return nameAndVersion; + } + } + /** * Get a feature by name, but without going and checking the remote repository if we don't know about it *