http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java new file mode 100644 index 0000000..ad4cc85 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CandidateComparator.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.util.Comparator; + +import org.osgi.framework.Version; +import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.resource.Capability; + +public class CandidateComparator implements Comparator<Capability> +{ + public int compare(Capability cap1, Capability cap2) + { + int c = 0; + // Always prefer system bundle + if (cap1 instanceof BundleCapability && !(cap2 instanceof BundleCapability)) { + c = -1; + } else if (!(cap1 instanceof BundleCapability) && cap2 instanceof BundleCapability) { + c = 1; + } + // Compare revision capabilities. + if ((c == 0) && cap1.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)) + { + c = ((Comparable) cap1.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE)) + .compareTo(cap2.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE)); + if (c == 0) + { + Version v1 = (!cap1.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap1.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); + Version v2 = (!cap2.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap2.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); + // Compare these in reverse order, since we want + // highest version to have priority. + c = compareVersions(v2, v1); + } + } + // Compare package capabilities. + else if ((c == 0) && cap1.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)) + { + c = ((Comparable) cap1.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)) + .compareTo(cap2.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)); + if (c == 0) + { + Version v1 = (!cap1.getAttributes().containsKey(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap1.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); + Version v2 = (!cap2.getAttributes().containsKey(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap2.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); + // Compare these in reverse order, since we want + // highest version to have priority. + c = compareVersions(v2, v1); + // if same version, rather compare on the bundle version + if (c == 0) + { + v1 = (!cap1.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap1.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); + v2 = (!cap2.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap2.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); + // Compare these in reverse order, since we want + // highest version to have priority. + c = compareVersions(v2, v1); + } + } + } + // Compare feature capabilities + else if ((c == 0) && cap1.getNamespace().equals(FeatureNamespace.FEATURE_NAMESPACE)) + { + c = ((Comparable) cap1.getAttributes().get(FeatureNamespace.FEATURE_NAMESPACE)) + .compareTo(cap2.getAttributes().get(FeatureNamespace.FEATURE_NAMESPACE)); + if (c == 0) + { + Version v1 = (!cap1.getAttributes().containsKey(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap1.getAttributes().get(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE); + Version v2 = (!cap2.getAttributes().containsKey(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE)) + ? Version.emptyVersion + : (Version) cap2.getAttributes().get(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE); + // Compare these in reverse order, since we want + // highest version to have priority. + c = compareVersions(v2, v1); + } + } + return c; + } + + private int compareVersions(Version v1, Version v2) { + int c = v1.getMajor() - v2.getMajor(); + if (c != 0) { + return c; + } + c = v1.getMinor() - v2.getMinor(); + if (c != 0) { + return c; + } + c = v1.getMicro() - v2.getMicro(); + if (c != 0) { + return c; + } + String q1 = cleanQualifierForComparison(v1.getQualifier()); + String q2 = cleanQualifierForComparison(v2.getQualifier()); + return q1.compareTo(q2); + } + + private String cleanQualifierForComparison(String qualifier) { + return qualifier.replaceAll("(redhat-[0-9]{3})([0-9]{3})", "$1-$2"); + } +}
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java new file mode 100644 index 0000000..bfe9b40 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.osgi.framework.Constants; +import org.osgi.resource.Capability; +import org.osgi.resource.Resource; + +public class CapabilityImpl extends BaseClause implements Capability { + + private final Resource m_resource; + private final String m_namespace; + private final Map<String, String> m_dirs; + private final Map<String, Object> m_attrs; + private final List<String> m_uses; + private final List<List<String>> m_includeFilter; + private final List<List<String>> m_excludeFilter; + private final Set<String> m_mandatory; + + public CapabilityImpl(Capability capability) { + this(null, capability.getNamespace(), capability.getDirectives(), capability.getAttributes()); + } + + public CapabilityImpl(Resource resource, String namespace, + Map<String, String> dirs, Map<String, Object> attrs) { + m_namespace = namespace; + m_resource = resource; + m_dirs = dirs; + m_attrs = attrs; + + // Find all export directives: uses, mandatory, include, and exclude. + + List<String> uses = Collections.emptyList(); + String value = m_dirs.get(Constants.USES_DIRECTIVE); + if (value != null) { + // Parse these uses directive. + StringTokenizer tok = new StringTokenizer(value, ","); + uses = new ArrayList<String>(tok.countTokens()); + while (tok.hasMoreTokens()) { + uses.add(tok.nextToken().trim()); + } + } + m_uses = uses; + + value = m_dirs.get(Constants.INCLUDE_DIRECTIVE); + if (value != null) { + List<String> filters = ResourceBuilder.parseDelimitedString(value, ","); + m_includeFilter = new ArrayList<List<String>>(filters.size()); + for (String filter : filters) { + List<String> substrings = SimpleFilter.parseSubstring(filter); + m_includeFilter.add(substrings); + } + } else { + m_includeFilter = null; + } + + value = m_dirs.get(Constants.EXCLUDE_DIRECTIVE); + if (value != null) { + List<String> filters = ResourceBuilder.parseDelimitedString(value, ","); + m_excludeFilter = new ArrayList<List<String>>(filters.size()); + for (String filter : filters) { + List<String> substrings = SimpleFilter.parseSubstring(filter); + m_excludeFilter.add(substrings); + } + } else { + m_excludeFilter = null; + } + + Set<String> mandatory = Collections.emptySet(); + value = m_dirs.get(Constants.MANDATORY_DIRECTIVE); + if (value != null) { + List<String> names = ResourceBuilder.parseDelimitedString(value, ","); + mandatory = new HashSet<String>(names.size()); + for (String name : names) { + // If attribute exists, then record it as mandatory. + if (m_attrs.containsKey(name)) { + mandatory.add(name); + } + // Otherwise, report an error. + else { + throw new IllegalArgumentException("Mandatory attribute '" + name + "' does not exist."); + } + } + } + m_mandatory = mandatory; + } + + public Resource getResource() { + return m_resource; + } + + public String getNamespace() { + return m_namespace; + } + + public Map<String, String> getDirectives() { + return m_dirs; + } + + public Map<String, Object> getAttributes() { + return m_attrs; + } + + public boolean isAttributeMandatory(String name) { + return !m_mandatory.isEmpty() && m_mandatory.contains(name); + } + + public List<String> getUses() { + return m_uses; + } + + public boolean isIncluded(String name) { + if ((m_includeFilter == null) && (m_excludeFilter == null)) { + return true; + } + + // Get the class name portion of the target class. + String className = getClassName(name); + + // If there are no include filters then all classes are included + // by default, otherwise try to find one match. + boolean included = (m_includeFilter == null); + for (int i = 0; !included && m_includeFilter != null && i < m_includeFilter.size(); i++) { + included = SimpleFilter.compareSubstring(m_includeFilter.get(i), className); + } + + // If there are no exclude filters then no classes are excluded + // by default, otherwise try to find one match. + boolean excluded = false; + for (int i = 0; (!excluded) && (m_excludeFilter != null) && (i < m_excludeFilter.size()); i++) { + excluded = SimpleFilter.compareSubstring(m_excludeFilter.get(i), className); + } + return included && !excluded; + } + + private static String getClassName(String className) { + if (className == null) { + className = ""; + } + return (className.lastIndexOf('.') < 0) ? "" : className.substring(className.lastIndexOf('.') + 1); + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java new file mode 100644 index 0000000..4c5656d --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilitySet.java @@ -0,0 +1,612 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import org.osgi.framework.Constants; +import org.osgi.resource.Capability; + +public class CapabilitySet +{ + private final Map<String, Map<Object, Set<Capability>>> m_indices; + private final Set<Capability> m_capSet = new HashSet<Capability>(); + +public void dump() +{ + for (Entry<String, Map<Object, Set<Capability>>> entry : m_indices.entrySet()) + { + boolean header1 = false; + for (Entry<Object, Set<Capability>> entry2 : entry.getValue().entrySet()) + { + boolean header2 = false; + for (Capability cap : entry2.getValue()) + { + if (!header1) + { + System.out.println(entry.getKey() + ":"); + header1 = true; + } + if (!header2) + { + System.out.println(" " + entry2.getKey()); + header2 = true; + } + System.out.println(" " + cap); + } + } + } +} + + public CapabilitySet(List<String> indexProps) + { + m_indices = new TreeMap<String, Map<Object, Set<Capability>>>(); + for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++) + { + m_indices.put( + indexProps.get(i), new HashMap<Object, Set<Capability>>()); + } + } + + public void addCapability(Capability cap) + { + m_capSet.add(cap); + + // Index capability. + for (Entry<String, Map<Object, Set<Capability>>> entry : m_indices.entrySet()) + { + Object value = cap.getAttributes().get(entry.getKey()); + if (value != null) + { + if (value.getClass().isArray()) + { + value = convertArrayToList(value); + } + + Map<Object, Set<Capability>> index = entry.getValue(); + + if (value instanceof Collection) + { + Collection c = (Collection) value; + for (Object o : c) + { + indexCapability(index, cap, o); + } + } + else + { + indexCapability(index, cap, value); + } + } + } + } + + private void indexCapability( + Map<Object, Set<Capability>> index, Capability cap, Object capValue) + { + Set<Capability> caps = index.get(capValue); + if (caps == null) + { + caps = new HashSet<Capability>(); + index.put(capValue, caps); + } + caps.add(cap); + } + + public void removeCapability(Capability cap) + { + if (m_capSet.remove(cap)) + { + for (Entry<String, Map<Object, Set<Capability>>> entry : m_indices.entrySet()) + { + Object value = cap.getAttributes().get(entry.getKey()); + if (value != null) + { + if (value.getClass().isArray()) + { + value = convertArrayToList(value); + } + + Map<Object, Set<Capability>> index = entry.getValue(); + + if (value instanceof Collection) + { + Collection c = (Collection) value; + for (Object o : c) + { + deindexCapability(index, cap, o); + } + } + else + { + deindexCapability(index, cap, value); + } + } + } + } + } + + private void deindexCapability( + Map<Object, Set<Capability>> index, Capability cap, Object value) + { + Set<Capability> caps = index.get(value); + if (caps != null) + { + caps.remove(cap); + if (caps.isEmpty()) + { + index.remove(value); + } + } + } + + public Set<Capability> match(SimpleFilter sf, boolean obeyMandatory) + { + Set<Capability> matches = match(m_capSet, sf); + return (obeyMandatory) + ? matchMandatory(matches, sf) + : matches; + } + + private Set<Capability> match(Set<Capability> caps, SimpleFilter sf) + { + Set<Capability> matches = new HashSet<Capability>(); + + if (sf.getOperation() == SimpleFilter.MATCH_ALL) + { + matches.addAll(caps); + } + else if (sf.getOperation() == SimpleFilter.AND) + { + // Evaluate each subfilter against the remaining capabilities. + // For AND we calculate the intersection of each subfilter. + // We can short-circuit the AND operation if there are no + // remaining capabilities. + List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); + for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++) + { + matches = match(caps, sfs.get(i)); + caps = matches; + } + } + else if (sf.getOperation() == SimpleFilter.OR) + { + // Evaluate each subfilter against the remaining capabilities. + // For OR we calculate the union of each subfilter. + List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); + for (int i = 0; i < sfs.size(); i++) + { + matches.addAll(match(caps, sfs.get(i))); + } + } + else if (sf.getOperation() == SimpleFilter.NOT) + { + // Evaluate each subfilter against the remaining capabilities. + // For OR we calculate the union of each subfilter. + matches.addAll(caps); + List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); + for (int i = 0; i < sfs.size(); i++) + { + matches.removeAll(match(caps, sfs.get(i))); + } + } + else + { + Map<Object, Set<Capability>> index = m_indices.get(sf.getName()); + if ((sf.getOperation() == SimpleFilter.EQ) && (index != null)) + { + Set<Capability> existingCaps = index.get(sf.getValue()); + if (existingCaps != null) + { + matches.addAll(existingCaps); + matches.retainAll(caps); + } + } + else + { + for (Iterator<Capability> it = caps.iterator(); it.hasNext(); ) + { + Capability cap = it.next(); + Object lhs = cap.getAttributes().get(sf.getName()); + if (lhs != null) + { + if (compare(lhs, sf.getValue(), sf.getOperation())) + { + matches.add(cap); + } + } + } + } + } + + return matches; + } + + public static boolean matches(Capability cap, SimpleFilter sf) + { + return matchesInternal(cap, sf) && matchMandatory(cap, sf); + } + + private static boolean matchesInternal(Capability cap, SimpleFilter sf) + { + boolean matched = true; + + if (sf.getOperation() == SimpleFilter.MATCH_ALL) + { + matched = true; + } + else if (sf.getOperation() == SimpleFilter.AND) + { + // Evaluate each subfilter against the remaining capabilities. + // For AND we calculate the intersection of each subfilter. + // We can short-circuit the AND operation if there are no + // remaining capabilities. + List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); + for (int i = 0; matched && (i < sfs.size()); i++) + { + matched = matchesInternal(cap, sfs.get(i)); + } + } + else if (sf.getOperation() == SimpleFilter.OR) + { + // Evaluate each subfilter against the remaining capabilities. + // For OR we calculate the union of each subfilter. + matched = false; + List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); + for (int i = 0; !matched && (i < sfs.size()); i++) + { + matched = matchesInternal(cap, sfs.get(i)); + } + } + else if (sf.getOperation() == SimpleFilter.NOT) + { + // Evaluate each subfilter against the remaining capabilities. + // For OR we calculate the union of each subfilter. + List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue(); + for (int i = 0; i < sfs.size(); i++) + { + matched = !(matchesInternal(cap, sfs.get(i))); + } + } + else + { + matched = false; + Object lhs = cap.getAttributes().get(sf.getName()); + if (lhs != null) + { + matched = compare(lhs, sf.getValue(), sf.getOperation()); + } + } + + return matched; + } + + private static Set<Capability> matchMandatory( + Set<Capability> caps, SimpleFilter sf) + { + for (Iterator<Capability> it = caps.iterator(); it.hasNext(); ) + { + Capability cap = it.next(); + if (!matchMandatory(cap, sf)) + { + it.remove(); + } + } + return caps; + } + + private static boolean matchMandatory(Capability cap, SimpleFilter sf) + { + if (cap instanceof CapabilityImpl) { + for (Entry<String, Object> entry : cap.getAttributes().entrySet()) + { + if (((CapabilityImpl) cap).isAttributeMandatory(entry.getKey()) + && !matchMandatoryAttribute(entry.getKey(), sf)) + { + return false; + } + } + } else { + String value = cap.getDirectives().get(Constants.MANDATORY_DIRECTIVE); + if (value != null) { + List<String> names = ResourceBuilder.parseDelimitedString(value, ","); + for (Entry<String, Object> entry : cap.getAttributes().entrySet()) + { + if (names.contains(entry.getKey()) + && !matchMandatoryAttribute(entry.getKey(), sf)) + { + return false; + } + } + } + + } + return true; + } + + private static boolean matchMandatoryAttribute(String attrName, SimpleFilter sf) + { + if ((sf.getName() != null) && sf.getName().equals(attrName)) + { + return true; + } + else if (sf.getOperation() == SimpleFilter.AND) + { + List list = (List) sf.getValue(); + for (int i = 0; i < list.size(); i++) + { + SimpleFilter sf2 = (SimpleFilter) list.get(i); + if ((sf2.getName() != null) + && sf2.getName().equals(attrName)) + { + return true; + } + } + } + return false; + } + + private static final Class<?>[] STRING_CLASS = new Class[] { String.class }; + + private static boolean compare(Object lhs, Object rhsUnknown, int op) + { + if (lhs == null) + { + return false; + } + + // If this is a PRESENT operation, then just return true immediately + // since we wouldn't be here if the attribute wasn't present. + if (op == SimpleFilter.PRESENT) + { + return true; + } + + // If the type is comparable, then we can just return the + // result immediately. + if (lhs instanceof Comparable) + { + // Spec says SUBSTRING is false for all types other than string. + if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String)) + { + return false; + } + + Object rhs; + if (op == SimpleFilter.SUBSTRING) + { + rhs = rhsUnknown; + } + else + { + try + { + rhs = coerceType(lhs, (String) rhsUnknown); + } + catch (Exception ex) + { + return false; + } + } + + switch (op) + { + case SimpleFilter.EQ : + try + { + return (((Comparable) lhs).compareTo(rhs) == 0); + } + catch (Exception ex) + { + return false; + } + case SimpleFilter.GTE : + try + { + return (((Comparable) lhs).compareTo(rhs) >= 0); + } + catch (Exception ex) + { + return false; + } + case SimpleFilter.LTE : + try + { + return (((Comparable) lhs).compareTo(rhs) <= 0); + } + catch (Exception ex) + { + return false; + } + case SimpleFilter.APPROX : + return compareApproximate(((Comparable) lhs), rhs); + case SimpleFilter.SUBSTRING : + return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs); + default: + throw new RuntimeException( + "Unknown comparison operator: " + op); + } + } + // Booleans do not implement comparable, so special case them. + else if (lhs instanceof Boolean) + { + Object rhs; + try + { + rhs = coerceType(lhs, (String) rhsUnknown); + } + catch (Exception ex) + { + return false; + } + + switch (op) + { + case SimpleFilter.EQ : + case SimpleFilter.GTE : + case SimpleFilter.LTE : + case SimpleFilter.APPROX : + return (lhs.equals(rhs)); + default: + throw new RuntimeException( + "Unknown comparison operator: " + op); + } + } + + // If the LHS is not a comparable or boolean, check if it is an + // array. If so, convert it to a list so we can treat it as a + // collection. + if (lhs.getClass().isArray()) + { + lhs = convertArrayToList(lhs); + } + + // If LHS is a collection, then call compare() on each element + // of the collection until a match is found. + if (lhs instanceof Collection) + { + for (Iterator iter = ((Collection) lhs).iterator(); iter.hasNext(); ) + { + if (compare(iter.next(), rhsUnknown, op)) + { + return true; + } + } + + return false; + } + + // Spec says SUBSTRING is false for all types other than string. + if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String)) + { + return false; + } + + // Since we cannot identify the LHS type, then we can only perform + // equality comparison. + try + { + return lhs.equals(coerceType(lhs, (String) rhsUnknown)); + } + catch (Exception ex) + { + return false; + } + } + + private static boolean compareApproximate(Object lhs, Object rhs) + { + if (rhs instanceof String) + { + return removeWhitespace((String) lhs) + .equalsIgnoreCase(removeWhitespace((String) rhs)); + } + else if (rhs instanceof Character) + { + return Character.toLowerCase(((Character) lhs)) + == Character.toLowerCase(((Character) rhs)); + } + return lhs.equals(rhs); + } + + private static String removeWhitespace(String s) + { + StringBuffer sb = new StringBuffer(s.length()); + for (int i = 0; i < s.length(); i++) + { + if (!Character.isWhitespace(s.charAt(i))) + { + sb.append(s.charAt(i)); + } + } + return sb.toString(); + } + + private static Object coerceType(Object lhs, String rhsString) throws Exception + { + // If the LHS expects a string, then we can just return + // the RHS since it is a string. + if (lhs.getClass() == rhsString.getClass()) + { + return rhsString; + } + + // Try to convert the RHS type to the LHS type by using + // the string constructor of the LHS class, if it has one. + Object rhs = null; + try + { + // The Character class is a special case, since its constructor + // does not take a string, so handle it separately. + if (lhs instanceof Character) + { + rhs = new Character(rhsString.charAt(0)); + } + else + { + // Spec says we should trim number types. + if ((lhs instanceof Number) || (lhs instanceof Boolean)) + { + rhsString = rhsString.trim(); + } + Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS); + ctor.setAccessible(true); + rhs = ctor.newInstance(new Object[] { rhsString }); + } + } + catch (Exception ex) + { + throw new Exception( + "Could not instantiate class " + + lhs.getClass().getName() + + " from string constructor with argument '" + + rhsString + "' because " + ex); + } + + return rhs; + } + + /** + * This is an ugly utility method to convert an array of primitives + * to an array of primitive wrapper objects. This method simplifies + * processing LDAP filters since the special case of primitive arrays + * can be ignored. + * @param array An array of primitive types. + * @return An corresponding array using pritive wrapper objects. + **/ + private static List convertArrayToList(Object array) + { + int len = Array.getLength(array); + List list = new ArrayList(len); + for (int i = 0; i < len; i++) + { + list.add(Array.get(array, i)); + } + return list; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java new file mode 100644 index 0000000..e211618 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureNamespace.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.util.List; + +import org.osgi.framework.Version; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; +import org.osgi.resource.Resource; + +/** + */ +public final class FeatureNamespace extends Namespace { + + public static final String FEATURE_NAMESPACE = "karaf.feature"; + + public static final String CAPABILITY_VERSION_ATTRIBUTE = "version"; + + /** + * The attribute value identifying the resource + * {@link org.osgi.framework.namespace.IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE type} as an OSGi bundle. + * + * @see org.osgi.framework.namespace.IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE + */ + public static final String TYPE_FEATURE = "karaf.feature"; + + public static String getName(Resource resource) + { + List<Capability> caps = resource.getCapabilities(null); + for (Capability cap : caps) + { + if (cap.getNamespace().equals(FEATURE_NAMESPACE)) + { + return cap.getAttributes().get(FEATURE_NAMESPACE).toString(); + } + } + return null; + } + + public static Version getVersion(Resource resource) + { + List<Capability> caps = resource.getCapabilities(null); + for (Capability cap : caps) + { + if (cap.getNamespace().equals(FEATURE_NAMESPACE)) + { + return (Version) + cap.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE); + } + } + return null; + } + + + private FeatureNamespace() { + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java new file mode 100644 index 0000000..e3b0101 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.felix.utils.version.VersionRange; +import org.apache.felix.utils.version.VersionTable; +import org.apache.karaf.features.BundleInfo; +import org.apache.karaf.features.Conditional; +import org.apache.karaf.features.Dependency; +import org.apache.karaf.features.Feature; +import org.apache.karaf.features.internal.util.Macro; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +/** +*/ +public class FeatureResource extends ResourceImpl { + + private final Feature feature; + + public static Resource build(Feature feature, Conditional conditional, String featureRange, Map<String, Resource> locToRes) throws BundleException { + Feature fcond = conditional.asFeature(feature.getName(), feature.getVersion()); + FeatureResource resource = (FeatureResource) build(fcond, featureRange, locToRes); + for (String cond : conditional.getCondition()) { + if (cond.startsWith("req:")) { + cond = cond.substring("req:".length()); + List<Requirement> reqs = ResourceBuilder.parseRequirement(resource, cond); + resource.addRequirements(reqs); + } else { + org.apache.karaf.features.internal.model.Dependency dep = new org.apache.karaf.features.internal.model.Dependency(); + String[] p = cond.split("/"); + dep.setName(p[0]); + if (p.length > 1) { + dep.setVersion(p[1]); + } + addDependency(resource, dep, featureRange); + } + } + org.apache.karaf.features.internal.model.Dependency dep = new org.apache.karaf.features.internal.model.Dependency(); + dep.setName(feature.getName()); + dep.setVersion(feature.getVersion()); + addDependency(resource, dep, featureRange); + return resource; + } + + public static Resource build(Feature feature, String featureRange, Map<String, Resource> locToRes) throws BundleException { + FeatureResource resource = new FeatureResource(feature); + Map<String, String> dirs = new HashMap<String, String>(); + Map<String, Object> attrs = new HashMap<String, Object>(); + attrs.put(FeatureNamespace.FEATURE_NAMESPACE, feature.getName()); + attrs.put(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE, VersionTable.getVersion(feature.getVersion())); + resource.addCapability(new CapabilityImpl(resource, FeatureNamespace.FEATURE_NAMESPACE, dirs, attrs)); + for (BundleInfo info : feature.getBundles()) { + if (!info.isDependency()) { + Resource res = locToRes.get(info.getLocation()); + if (res == null) { + throw new IllegalStateException("Resource not found for url " + info.getLocation()); + } + List<Capability> caps = res.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE); + if (caps.size() != 1) { + throw new IllegalStateException("Resource does not have a single " + IdentityNamespace.IDENTITY_NAMESPACE + " capability"); + } + dirs = new HashMap<String, String>(); + attrs = new HashMap<String, Object>(); + attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, caps.get(0).getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE)); + attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, caps.get(0).getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE)); + attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((Version) caps.get(0).getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE), true)); + resource.addRequirement(new RequirementImpl(resource, IdentityNamespace.IDENTITY_NAMESPACE, dirs, attrs)); + } + } + for (Dependency dep : feature.getDependencies()) { + addDependency(resource, dep, featureRange); + } + for (org.apache.karaf.features.Capability cap : feature.getCapabilities()) { + resource.addCapabilities(ResourceBuilder.parseCapability(resource, cap.getValue())); + } + for (org.apache.karaf.features.Requirement req : feature.getRequirements()) { + resource.addRequirements(ResourceBuilder.parseRequirement(resource, req.getValue())); + } + return resource; + } + + protected static void addDependency(FeatureResource resource, Dependency dep, String featureRange) { + Map<String, String> dirs; + Map<String, Object> attrs; + String name = dep.getName(); + String version = dep.getVersion(); + if (version.equals("0.0.0")) { + version = null; + } else if (!version.startsWith("[") && !version.startsWith("(")) { + version = Macro.transform(featureRange, version); + } + dirs = new HashMap<String, String>(); + attrs = new HashMap<String, Object>(); + attrs.put(FeatureNamespace.FEATURE_NAMESPACE, name); + if (version != null) { + attrs.put(FeatureNamespace.CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(version)); + } + resource.addRequirement(new RequirementImpl(resource, FeatureNamespace.FEATURE_NAMESPACE, dirs, attrs)); + } + + public FeatureResource(Feature feature) { + super(feature.getName(), FeatureNamespace.TYPE_FEATURE, VersionTable.getVersion(feature.getVersion())); + this.feature = feature; + } + + public Feature getFeature() { + return feature; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java new file mode 100644 index 0000000..cdc00d1 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/IdentityCapability.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.util.HashMap; +import java.util.Map; + +import org.osgi.framework.Version; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Resource; + +class IdentityCapability extends BaseClause implements Capability +{ + private final Resource m_resource; + private final Map<String, String> m_dirs; + private final Map<String, Object> m_attrs; + + public IdentityCapability(Resource resource, String name, String type, Version version) + { + m_resource = resource; + m_dirs = new HashMap<String, String>(); + m_attrs = new HashMap<String, Object>(); + m_attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, name); + m_attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, type); + m_attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, version); + } + + public String getNamespace() + { + return IdentityNamespace.IDENTITY_NAMESPACE; + } + + public Map<String, String> getDirectives() + { + return m_dirs; + } + + public Map<String, Object> getAttributes() + { + return m_attrs; + } + + public Resource getResource() + { + return m_resource; + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java new file mode 100644 index 0000000..a4ef775 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.util.Map; + +import org.osgi.framework.Constants; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +public class RequirementImpl extends BaseClause implements Requirement { + private final Resource m_resource; + private final String m_namespace; + private final SimpleFilter m_filter; + private final boolean m_optional; + private final Map<String, String> m_dirs; + private final Map<String, Object> m_attrs; + + public RequirementImpl( + Resource resource, String namespace, + Map<String, String> dirs, Map<String, Object> attrs, SimpleFilter filter) { + m_resource = resource; + m_namespace = namespace; + m_dirs = dirs; + m_attrs = attrs; + m_filter = filter; + // Find resolution import directives. + m_optional = Constants.RESOLUTION_OPTIONAL.equals(m_dirs.get(Constants.RESOLUTION_DIRECTIVE)); + } + + public RequirementImpl( + Resource resource, String namespace, + Map<String, String> dirs, Map<String, Object> attrs) { + this(resource, namespace, dirs, attrs, SimpleFilter.convert(attrs)); + } + + public String getNamespace() { + return m_namespace; + } + + public Map<String, String> getDirectives() { + return m_dirs; + } + + public Map<String, Object> getAttributes() { + return m_attrs; + } + + public Resource getResource() { + return m_resource; + } + + public boolean matches(Capability cap) { + return CapabilitySet.matches(cap, getFilter()); + } + + public boolean isOptional() { + return m_optional; + } + + public SimpleFilter getFilter() { + return m_filter; + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java new file mode 100644 index 0000000..e2ff793 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResolveContextImpl.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.features.internal.resolver; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.osgi.framework.Constants; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; +import org.osgi.resource.Wiring; +import org.osgi.service.repository.Repository; +import org.osgi.service.resolver.HostedCapability; +import org.osgi.service.resolver.ResolveContext; + +/** +*/ +public class ResolveContextImpl extends ResolveContext { + + private final Set<Resource> mandatory; + private final Set<Resource> optional; + private final Repository repository; + private final Map<Resource, Wiring> wirings; + private final boolean resolveOptional; + + private final CandidateComparator candidateComparator = new CandidateComparator(); + + public ResolveContextImpl(Set<Resource> mandatory, + Set<Resource> optional, + Repository repository, + boolean resolveOptional) { + this.mandatory = mandatory; + this.optional = optional; + this.repository = repository; + this.wirings = new HashMap<Resource, Wiring>(); + this.resolveOptional = resolveOptional; + } + + @Override + public Collection<Resource> getMandatoryResources() { + return mandatory; + } + + @Override + public Collection<Resource> getOptionalResources() { + return optional; + } + + @Override + public List<Capability> findProviders(Requirement requirement) { + List<Capability> caps = new ArrayList<Capability>(); + Map<Requirement, Collection<Capability>> resMap = + repository.findProviders(Collections.singleton(requirement)); + Collection<Capability> res = resMap != null ? resMap.get(requirement) : null; + if (res != null) { + caps.addAll(res); + } + Collections.sort(caps, candidateComparator); + return caps; + } + @Override + public int insertHostedCapability(List capabilities, HostedCapability hostedCapability) { + for (int i=0; i < capabilities.size(); i++) { + Capability cap = (Capability) capabilities.get(i); + if (candidateComparator.compare(hostedCapability, cap) <= 0) { + capabilities.add(i, hostedCapability); + return i; + } + } + capabilities.add(hostedCapability); + return capabilities.size() - 1; + } + @Override + public boolean isEffective(Requirement requirement) { + return resolveOptional || + !Constants.RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(Constants.RESOLUTION_DIRECTIVE)); + } + @Override + public Map<Resource, Wiring> getWirings() { + return wirings; + } +}