/*
* #%L
* JBossOSGi Resolver API
* %%
* Copyright (C) 2010 - 2012 JBoss by Red Hat
* %%
* Licensed 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.
* #L%
*/
package org.jboss.osgi.resolver.spi;
import static org.jboss.osgi.resolver.ResolverMessages.MESSAGES;
import static org.osgi.framework.namespace.AbstractWiringNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE;
import static org.osgi.framework.namespace.BundleNamespace.BUNDLE_NAMESPACE;
import static org.osgi.framework.namespace.HostNamespace.HOST_NAMESPACE;
import static org.osgi.framework.namespace.PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE;
import static org.osgi.framework.namespace.PackageNamespace.PACKAGE_NAMESPACE;
import static org.osgi.framework.namespace.PackageNamespace.RESOLUTION_DYNAMIC;
import static org.osgi.resource.Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.jboss.osgi.resolver.XAttributeSupport;
import org.jboss.osgi.resolver.XCapability;
import org.jboss.osgi.resolver.XCapabilityRequirement;
import org.jboss.osgi.resolver.XDirectiveSupport;
import org.jboss.osgi.resolver.XHostRequirement;
import org.jboss.osgi.resolver.XIdentityCapability;
import org.jboss.osgi.resolver.XPackageRequirement;
import org.jboss.osgi.resolver.XRequirement;
import org.jboss.osgi.resolver.XResource;
import org.jboss.osgi.resolver.XIdentityRequirement;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.VersionRange;
import org.osgi.framework.namespace.AbstractWiringNamespace;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
/**
* The abstract implementation of a {@link XRequirement}.
*
* @author thomas.diesler@jboss.com
* @since 02-Jul-2010
*/
public class AbstractRequirement extends AbstractElement implements XHostRequirement, XPackageRequirement, XIdentityRequirement, XCapabilityRequirement {
private final XResource resource;
private final String namespace;
private XAttributeSupport attributes;
private XDirectiveSupport directives;
private String canonicalName;
private boolean optional;
private Filter filter;
private boolean valid;
public AbstractRequirement(XResource resource, String namespace, Map<String, Object> atts, Map<String, String> dirs) {
if (resource == null)
throw MESSAGES.illegalArgumentNull("resource");
if (namespace == null)
throw MESSAGES.illegalArgumentNull("namespace");
if (atts == null)
throw MESSAGES.illegalArgumentNull("attributes");
if (dirs == null)
throw MESSAGES.illegalArgumentNull("directives");
this.resource = resource;
this.namespace = namespace;
this.attributes = new AttributeSupporter(atts);
this.directives = new DirectiveSupporter(dirs);
}
@Override
public Filter getFilter() {
return filter;
}
@Override
public XResource getResource() {
return resource;
}
static String getNamespaceValue(Requirement req) {
return getNamespaceValue(req, null);
}
static String getNamespaceValue(Requirement req, StringBuffer operator) {
return getValueFromFilter(getFilterFromDirective(req), req.getNamespace(), operator);
}
@Override
public void validate() {
if (!valid) {
Map<String, Object> atts = attributes.getAttributes();
Map<String, String> dirs = directives.getDirectives();
// Attributes declared on Require-Capability will be visible in getAttributes, but attributes declared on
// other manifest entries which map to osgi.wiring.* namespace requirements will not be visible in getAttributes.
// There are instead used to form a generated filter directive which will be visible in getDirectives.
if (namespace.startsWith("osgi.wiring.")) {
if (!atts.isEmpty()) {
generateFilterDirective(namespace, atts, dirs);
}
if (!dirs.containsKey(Constants.FILTER_DIRECTIVE))
throw MESSAGES.illegalArgumentRequirementMustHaveFilterDirective(namespace, dirs);
}
filter = getFilterFromDirective(this);
String resdir = getDirective(AbstractWiringNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
optional = AbstractWiringNamespace.RESOLUTION_OPTIONAL.equals(resdir);
canonicalName = toString();
valid = true;
}
}
public static Filter getFilterFromDirective(Requirement req) {
String filterdir = req.getDirectives().get(AbstractWiringNamespace.REQUIREMENT_FILTER_DIRECTIVE);
if (filterdir != null) {
try {
return FrameworkUtil.createFilter(filterdir);
} catch (InvalidSyntaxException e) {
throw MESSAGES.illegalArgumentInvalidFilterDirective(filterdir);
}
}
return null;
}
public static String getValueFromFilter(Filter filter, String attrname, StringBuffer operator) {
String result = null;
if (filter != null) {
String filterstr = filter.toString();
int index = filterstr.indexOf("(" + attrname);
if (index >= 0) {
index += attrname.length() + 1;
char ch = filterstr.charAt(index);
while ("~<=>".indexOf(ch) >= 0) {
if (operator != null) {
operator.append(ch);
}
ch = filterstr.charAt(++index);
}
result = filterstr.substring(index);
result = result.substring(0, result.indexOf(")"));
}
}
return result;
}
@Override
public String getNamespace() {
return namespace;
}
@Override
public boolean isOptional() {
return optional;
}
@Override
public Map<String, String> getDirectives() {
return isMutable() ? directives.getDirectives() : Collections.unmodifiableMap(directives.getDirectives());
}
@Override
public String getDirective(String key) {
return directives.getDirective(key);
}
@Override
public Map<String, Object> getAttributes() {
return isMutable() ? attributes.getAttributes() : Collections.unmodifiableMap(attributes.getAttributes());
}
@Override
public Object getAttribute(String key) {
return attributes.getAttribute(key);
}
private boolean isMutable() {
return resource.isMutable();
}
private void assertImmutable() {
if (isMutable())
throw MESSAGES.illegalStateInvalidAccessToMutableResource();
}
@Override
@SuppressWarnings("unchecked")
public <T extends XRequirement> T adapt(Class<T> clazz) {
T result = null;
if (XIdentityRequirement.class == clazz && BUNDLE_NAMESPACE.equals(getNamespace())) {
result = (T) this;
} else if (XHostRequirement.class == clazz && HOST_NAMESPACE.equals(getNamespace())) {
result = (T) this;
} else if (XPackageRequirement.class == clazz && PACKAGE_NAMESPACE.equals(getNamespace())) {
result = (T) this;
}
return result;
}
@Override
public boolean matches(Capability cap) {
assertImmutable();
// The requirement matches the capability if their namespaces match and the filter is absent or matches the attributes.
boolean matches = namespace.equals(cap.getNamespace()) && matchFilter(cap);
if (matches) {
if (BUNDLE_NAMESPACE.equals(getNamespace())) {
matches = matchesResourceRequirement(cap);
} else if (HOST_NAMESPACE.equals(getNamespace())) {
matches = matchesHostRequirement(cap);
} else if (PACKAGE_NAMESPACE.equals(getNamespace())) {
matches = matchesPackageRequirement(cap);
} else {
Object reqval = getAttribute(getNamespace());
Object capval = cap.getAttributes().get(getNamespace());
matches = (reqval == null || reqval.equals(capval));
}
}
return matches;
}
private boolean matchesResourceRequirement(Capability cap) {
// cannot require itself
if (getResource() == cap.getResource())
return false;
return matchesMandatoryDirective(cap);
}
private boolean matchesHostRequirement(Capability cap) {
return matchesMandatoryDirective(cap);
}
private boolean matchesPackageRequirement(Capability cap) {
return matchesMandatoryDirective(cap);
}
private boolean matchesMandatoryDirective(Capability cap) {
// match mandatory attributes on the capability
String dirstr = ((XCapability) cap).getDirective(Constants.MANDATORY_DIRECTIVE);
if (dirstr != null) {
for (String attname : dirstr.split("[,\\s]")) {
String attval = getValueFromFilter(filter, attname, null);
if (attval == null) {
return false;
}
}
}
return true;
}
static VersionRange getVersionRange(XRequirement req, String attr) {
Object value = req.getAttribute(attr);
return (value instanceof String) ? new VersionRange((String) value) : (VersionRange) value;
}
private boolean matchFilter(Capability cap) {
Map<String, Object> capatts = cap.getAttributes();
return filter != null ? filter.matches(capatts) : true;
}
@Override
public String getVisibility() {
return getDirective(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
}
@Override
public String getSymbolicName() {
String result = null;
if (HOST_NAMESPACE.equals(getNamespace())) {
result = getNamespaceValue(this);
}
return result;
}
@Override
public String getPackageName() {
String result = null;
if (PACKAGE_NAMESPACE.equals(getNamespace())) {
result = getNamespaceValue(this);
}
return result;
}
@Override
public VersionRange getVersionRange() {
VersionRange result = null;
if (HOST_NAMESPACE.equals(getNamespace()) || BUNDLE_NAMESPACE.equals(getNamespace())) {
result = AbstractRequirement.getVersionRange(this, CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
} else if (PACKAGE_NAMESPACE.equals(getNamespace())) {
result = AbstractRequirement.getVersionRange(this, CAPABILITY_VERSION_ATTRIBUTE);
}
return result;
}
@Override
public boolean isDynamic() {
return RESOLUTION_DYNAMIC.equals(getDirective(REQUIREMENT_RESOLUTION_DIRECTIVE));
}
private void generateFilterDirective(String namespace, Map<String, Object> atts, Map<String, String> dirs) {
List<String> parts = new ArrayList<String>();
if (atts.containsKey(namespace)) {
addAttributePart(atts, namespace, parts);
addVersionRangePart(atts, Constants.BUNDLE_VERSION_ATTRIBUTE, parts);
addVersionRangePart(atts, Constants.VERSION_ATTRIBUTE, parts);
for (String key : new ArrayList<String>(atts.keySet())) {
addAttributePart(atts, key, parts);
}
StringBuffer filterSpec = new StringBuffer(parts.remove(0));
for (String part : parts) {
filterSpec.insert(0, "(&");
filterSpec.append(part + ")");
}
try {
Filter filter = FrameworkUtil.createFilter(filterSpec.toString());
dirs.put(Constants.FILTER_DIRECTIVE, filter.toString());
} catch (InvalidSyntaxException ex) {
throw new IllegalArgumentException(ex);
}
}
}
private void addAttributePart(Map<String, Object> atts, String attrname, List<String> parts) {
Object attrval = atts.remove(attrname);
if (attrval instanceof String) {
parts.add("(" + attrname + "=" + attrval + ")");
}
}
private void addVersionRangePart(Map<String, Object> atts, String attrname, List<String> parts) {
Object versionAtt = atts.remove(attrname);
if (versionAtt instanceof VersionRange) {
VersionRange versionRange = (VersionRange) versionAtt;
parts.add(versionRange.toFilterString(attrname));
} else if (versionAtt instanceof String) {
VersionRange versionRange = new VersionRange((String) versionAtt);
parts.add(versionRange.toFilterString(attrname));
}
}
@Override
public String toString() {
String result = canonicalName;
if (result == null) {
String type;
String nsval = null;
if (BUNDLE_NAMESPACE.equals(getNamespace())) {
type = XIdentityRequirement.class.getSimpleName();
} else if (HOST_NAMESPACE.equals(getNamespace())) {
type = XHostRequirement.class.getSimpleName();
} else if (PACKAGE_NAMESPACE.equals(getNamespace())) {
type = XPackageRequirement.class.getSimpleName();
} else {
type = XCapabilityRequirement.class.getSimpleName();
nsval = namespace;
}
StringBuffer buffer = new StringBuffer(type + "[");
boolean addcomma = false;
if (nsval != null) {
buffer.append(nsval);
addcomma = true;
}
if (!getAttributes().isEmpty()) {
buffer.append(addcomma ? "," : "");
buffer.append("atts=" + attributes);
addcomma = true;
}
if (!getDirectives().isEmpty()) {
buffer.append(addcomma ? "," : "");
buffer.append("dirs=" + directives);
addcomma = true;
}
XIdentityCapability icap = resource.getIdentityCapability();
if (icap != null) {
buffer.append(addcomma ? "," : "");
buffer.append("[" + icap.getName() + ":" + icap.getVersion() + "]");
addcomma = true;
} else {
buffer.append(addcomma ? "," : "");
buffer.append("[anonymous]");
addcomma = true;
}
buffer.append("]");
result = buffer.toString();
}
return result;
}
}