/** * 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.aries.spifly; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import org.apache.aries.spifly.HeaderParser.PathElement; import org.apache.aries.util.manifest.ManifestHeaderProcessor; import org.apache.aries.util.manifest.ManifestHeaderProcessor.GenericMetadata; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.Version; public class ConsumerHeaderProcessor { private static final Dictionary<String, String> PROCESSOR_FILTER_MATCH; static { PROCESSOR_FILTER_MATCH = new Hashtable<String, String>(); PROCESSOR_FILTER_MATCH.put(SpiFlyConstants.EXTENDER_CAPABILITY_NAMESPACE, SpiFlyConstants.PROCESSOR_EXTENDER_NAME); } /** * Parses headers of the following syntax: * <ul> * <li><tt>org.acme.MyClass#myMethod</tt> - apply the weaving to all overloads of <tt>myMethod()</tt> * in <tt>MyClass</tt> * <li><tt>org.acme.MyClass#myMethod(java.lang.String, java.util.List)</tt> - apply the weaving only * to the <tt>myMethod(String, List)</tt> overload in <tt>MyClass</tt> * <li><tt>org.acme.MyClass#myMethod()</tt> - apply the weaving only to the noarg overload of * <tt>myMethod()</tt> * <li><b>true</b> - equivalent to <tt>java.util.ServiceLoader#load(java.lang.Class)</tt> * </ul> * Additionally, it registers the consumer's constraints with the consumer registry in the activator, if the * consumer is only constrained to a certain set of bundles.<p/> * * The following attributes are supported: * <ul> * <li><tt>bundle</tt> - restrict wiring to the bundle with the specifies Symbolic Name. The attribute value * is a list of bundle identifiers separated by a '|' sign. The bundle identifier starts with the Symbolic name * and can optionally contain a version suffix. E.g. bundle=impl2:version=1.2.3 or bundle=impl2|impl4. * <li><tt>bundleId</tt> - restrict wiring to the bundle with the specified bundle ID. Typically used when * the service should be forcibly picked up from the system bundle (<tt>bundleId=0</tt>). Multiple bundle IDs * can be specified separated by a '|' sign. * </ul> * * @param consumerHeaderName the name of the header (either Require-Capability or SPI-Consumer) * @param consumerHeader the <tt>SPI-Consumer</tt> header. * @return an instance of the {@link WeavingData} class. */ public static Set<WeavingData> processHeader(String consumerHeaderName, String consumerHeader) throws Exception { if (SpiFlyConstants.REQUIRE_CAPABILITY.equals(consumerHeaderName)) { return processRequireCapabilityHeader(consumerHeader); } Set<WeavingData> weavingData = new HashSet<WeavingData>(); for (PathElement element : HeaderParser.parseHeader(consumerHeader)) { List<BundleDescriptor> allowedBundles = new ArrayList<BundleDescriptor>(); String name = element.getName().trim(); String className; String methodName; MethodRestriction methodRestriction; int hashIdx = name.indexOf('#'); if (hashIdx > 0) { className = name.substring(0, hashIdx); int braceIdx = name.substring(hashIdx).indexOf('('); if (braceIdx > 0) { methodName = name.substring(hashIdx + 1, hashIdx + braceIdx); ArgRestrictions argRestrictions = new ArgRestrictions(); int closeIdx = name.substring(hashIdx).indexOf(')'); if (closeIdx > 0) { String classes = name.substring(hashIdx + braceIdx + 1, hashIdx + closeIdx).trim(); if (classes.length() > 0) { if (classes.indexOf('[') > 0) { int argNumber = 0; for (String s : classes.split(",")) { int idx = s.indexOf('['); int end = s.indexOf(']', idx); if (idx > 0 && end > idx) { argRestrictions.addRestriction(argNumber, s.substring(0, idx), s.substring(idx + 1, end)); } else { argRestrictions.addRestriction(argNumber, s); } argNumber++; } } else { String[] classNames = classes.split(","); for (int i = 0; i < classNames.length; i++) { argRestrictions.addRestriction(i, classNames[i]); } } } else { argRestrictions = null; } } methodRestriction = new MethodRestriction(methodName, argRestrictions); } else { methodName = name.substring(hashIdx + 1); methodRestriction = new MethodRestriction(methodName); } } else { if ("*".equalsIgnoreCase(name)) { className = ServiceLoader.class.getName(); methodName = "load"; ArgRestrictions argRestrictions = new ArgRestrictions(); argRestrictions.addRestriction(0, Class.class.getName()); methodRestriction = new MethodRestriction(methodName, argRestrictions); } else { throw new IllegalArgumentException("Must at least specify class name and method name: " + name); } } String bsn = element.getAttribute("bundle"); if (bsn != null) { bsn = bsn.trim(); if (bsn.length() > 0) { for (String s : bsn.split("\\|")) { int colonIdx = s.indexOf(':'); if (colonIdx > 0) { String sn = s.substring(0, colonIdx); String versionSfx = s.substring(colonIdx + 1); if (versionSfx.startsWith("version=")) { allowedBundles.add(new BundleDescriptor(sn, Version.parseVersion(versionSfx.substring("version=".length())))); } else { allowedBundles.add(new BundleDescriptor(sn)); } } else { allowedBundles.add(new BundleDescriptor(s)); } } } } String bid = element.getAttribute("bundleId"); if (bid != null) { bid = bid.trim(); if (bid.length() > 0) { for (String s : bid.split("\\|")) { allowedBundles.add(new BundleDescriptor(Long.parseLong(s))); } } } weavingData.add(createWeavingData(className, methodName, methodRestriction, allowedBundles)); } return weavingData; } private static Set<WeavingData> processRequireCapabilityHeader(String consumerHeader) throws InvalidSyntaxException { Set<WeavingData> weavingData = new HashSet<WeavingData>(); List<GenericMetadata> requirements = ManifestHeaderProcessor.parseRequirementString(consumerHeader); GenericMetadata extenderRequirement = findRequirement(requirements, SpiFlyConstants.EXTENDER_CAPABILITY_NAMESPACE, SpiFlyConstants.PROCESSOR_EXTENDER_NAME); Collection<GenericMetadata> serviceLoaderRequirements = findAllMetadata(requirements, SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE); if (extenderRequirement != null) { ArgRestrictions ar = new ArgRestrictions(); ar.addRestriction(0, Class.class.getName()); MethodRestriction mr = new MethodRestriction("load", ar); List<BundleDescriptor> allowedBundles = new ArrayList<BundleDescriptor>(); for (GenericMetadata req : serviceLoaderRequirements) { String slFilterString = req.getDirectives().get(SpiFlyConstants.FILTER_DIRECTIVE); if (slFilterString != null) { Filter slFilter = FrameworkUtil.createFilter(slFilterString); allowedBundles.add(new BundleDescriptor(slFilter)); } } weavingData.add(createWeavingData(ServiceLoader.class.getName(), "load", mr, allowedBundles)); } return weavingData; } private static WeavingData createWeavingData(String className, String methodName, MethodRestriction methodRestriction, List<BundleDescriptor> allowedBundles) { ConsumerRestriction restriction = new ConsumerRestriction(className, methodRestriction); // TODO is this correct? Why is it added to a set? Set<ConsumerRestriction> restrictions = new HashSet<ConsumerRestriction>(); restrictions.add(restriction); // TODO this can be done in the WeavingData itself? String[] argClasses = restriction.getMethodRestriction(methodName).getArgClasses(); return new WeavingData(className, methodName, argClasses, restrictions, allowedBundles.size() == 0 ? null : allowedBundles); } private static GenericMetadata findRequirement(List<GenericMetadata> requirements, String namespace, String type) throws InvalidSyntaxException { Dictionary<String, String> nsAttr = new Hashtable<String, String>(); nsAttr.put(namespace, type); for (GenericMetadata req : requirements) { if (namespace.equals(req.getNamespace())) { String filterString = req.getDirectives().get(SpiFlyConstants.FILTER_DIRECTIVE); if (filterString != null) { Filter filter = FrameworkUtil.createFilter(filterString); if (filter.match(nsAttr)) { return req; } } } } return null; } private static Collection<GenericMetadata> findAllMetadata(List<GenericMetadata> metadata, String namespace) { List<GenericMetadata> matching = new ArrayList<ManifestHeaderProcessor.GenericMetadata>(); for (GenericMetadata md : metadata) { if (namespace.equals(md.getNamespace())) { matching.add(md); } } return matching; } }