• Intro Headers Instructions Macros
  • Fork me on GitHub
    • Release
    • Introduction
    • How to install bnd
    • Guided Tour
    • Guided Tour Workspace & Projects
    • Concepts
    • Best practices
    • Build
    • Generating JARs
    • Versioning
    • Baselining
    • Service Components
    • Metatype
    • Contracts
    • Manifest Annotations
    • Resolving Dependencies
    • Launching
    • Testing
    • Packaging Applications
    • Wrapping Libraries to OSGi Bundles
    • From the command line
    • For Developers
    • Plugins
    • Tools bound to bnd
    • File Format
    • Header Reference
    • Instruction
    • Instruction Index
    • Macro Reference
    • Command Reference
    • Plugins Reference
    • Settings
    • Errors
    • Warnings
    • Frequently Asked Questions
    Supported by OSGi enRoute
  • -contract

    	package aQute.bnd.osgi;
    
    	import java.util.*;
    	import java.util.Map.Entry;
    
    	import org.osgi.namespace.contract.*;
    
    	import aQute.bnd.header.*;
    	import aQute.bnd.osgi.Descriptors.PackageRef;
    	import aQute.bnd.version.*;
    	import aQute.lib.collections.*;
    	import aQute.service.reporter.Report.Location;
    
    	/**
    	 * OSGi Contracts are first defined in OSGi Enterprise Release 5.0.0. A Contract
    	 * is a namespace to control the versioning of a set of packages.
    	 *
    	 * @author aqute
    	 */
    	class Contracts {
    
    		private Analyzer									analyzer;
    		private final MultiMap<PackageRef,Contract>			contracted				= new MultiMap<PackageRef,Contract>(
    																							PackageRef.class,
    																							Contract.class, true);
    		private MultiMap<Collection<Contract>,PackageRef>	overlappingContracts	= new MultiMap<Collection<Contract>,PackageRef>();
    		private Instructions								instructions;
    		private final Set<Contract>							contracts				= new HashSet<Contract>();
    
    		public class Contract {
    			public String				name;
    			public Attrs				decorators;
    			public Collection<String>	uses;
    			public Version				version;
    			public String				from;
    
    			@Override
    			public String toString() {
    				return "Contract [name=" + name + ";version=" + version + ";from=" + from + "]";
    			}
    
    		}
    
    		public Contracts(Analyzer analyzer) {
    			this.analyzer = analyzer;
    		}
    
    		Instructions getFilter() {
    			if (instructions == null) {
    				String contract = analyzer.getProperty(Constants.CONTRACT);
    				this.instructions = new Instructions(contract);
    			}
    			return instructions;
    		}
    
    		public void clear() {
    			contracted.clear();
    			overlappingContracts.clear();
    			contracts.clear();
    		}
    
    		/**
    		 * Collect contracts will take a domain and find any declared contracts.
    		 * This happens early so that we have a list of contracts we can later
    		 * compare the imports against.
    		 */
    		void collectContracts(String from, Parameters pcs) {
    
    			contract: for (Entry<String,Attrs> p : pcs.entrySet()) {
    				String namespace = p.getKey();
    
    				if (namespace.equals(ContractNamespace.CONTRACT_NAMESPACE)) {
    					Attrs capabilityAttrs = p.getValue();
    
    					String name = capabilityAttrs.get(ContractNamespace.CONTRACT_NAMESPACE);
    					if (name == null) {
    						analyzer.warning("No name (attr %s) defined in bundle %s from contract namespace: %s",
    								ContractNamespace.CONTRACT_NAMESPACE, from, capabilityAttrs);
    						continue contract;
    					}
    
    					for (Entry<Instruction,Attrs> i : getFilter().entrySet()) {
    						Instruction instruction = i.getKey();
    						if (instruction.matches(name)) {
    							if (instruction.isNegated()) {
    								analyzer.trace("%s rejected due to %s", namespace, instructions);
    								continue contract;
    							}
    
    							analyzer.trace("accepted %s", p);
    
    							Contract c = new Contract();
    							c.name = name;
    
    							String list = capabilityAttrs.get(ContractNamespace.CAPABILITY_USES_DIRECTIVE + ":");
    							if (list == null || list.length() == 0) {
    								analyzer.warning("Contract %s has no uses: directive in %s.", name, from);
    								continue contract; // next contract
    							}
    
    							c.uses = Processor.split(list);
    
    							c.version = (Version) capabilityAttrs.getTyped(ContractNamespace.CAPABILITY_VERSION_ATTRIBUTE);
    							c.from = from;
    
    							if (c.version == null) {
    								c.version = Version.LOWEST;
    								analyzer.warning("%s does not declare a version, assumed 0.0.0.", c);
    							}
    							c.decorators = new Attrs(i.getValue());
    
    							//
    							// Build up the package -> contract index
    							//
    							for (String pname : c.uses) {
    								contracted.add(analyzer.getPackageRef(pname), c);
    							}
    						}
    					}
    				}
    			}
    		}
    
    		/**
    		 * Find out if a package is contracted. If there are multiple contracts for
    		 * a package we remember this so we can generate a single error.
    		 *
    		 * @param packageRef
    		 * @return
    		 */
    		boolean isContracted(PackageRef packageRef) {
    			List<Contract> list = contracted.get(packageRef);
    			if (list == null || list.isEmpty())
    				return false;
    
    			if (list.size() > 1) {
    
    				//
    				// There are multiple contracts trying to address
    				// this package. We collect those so we can report them
    				// as one error instead of one for each package
    				//
    
    				overlappingContracts.add(list, packageRef);
    			}
    			contracts.addAll(list);
    			return true;
    		}
    
    		/**
    		 * Called before we print the manifest. Should add any contracts that were
    		 * actually used to the requirements.
    		 *
    		 * @param requirements
    		 */
    		void addToRequirements(Parameters requirements) {
    			for (Contract c : contracts) {
    				Attrs attrs = new Attrs(c.decorators);
    				attrs.put(ContractNamespace.CONTRACT_NAMESPACE, c.name);
    				String range = analyzer.applyVersionPolicy(c.version.toString(), c.decorators.getVersion(), false);
    				String name = ContractNamespace.CONTRACT_NAMESPACE;
    				while (requirements.containsKey(name))
    					name += "~";
    
    				VersionRange r = new VersionRange(range);
    
    				Formatter f = new Formatter();
    				try {
    					f.format("(&(%s=%s)%s)", ContractNamespace.CONTRACT_NAMESPACE, c.name, r.toFilter());
    
    					// TODO : shall we also assert the attributes?
    
    					attrs.put("filter:", f.toString());
    
    					requirements.put(name, attrs);
    				}
    				finally {
    					f.close();
    				}
    			}
    
    			for (Entry<Collection<Contract>,List<PackageRef>> oc : overlappingContracts.entrySet()) {
    				Location location = analyzer
    						.error("Contracts %s declare the same packages in their uses: directive: %s. "
    								+ "Contracts are found in declaring bundles (see their 'from' field), it is possible to control the finding"
    								+ "with the -contract instruction", oc.getKey(), oc.getValue()).location();
    				location.header = Constants.CONTRACT;
    			}
    		}
    
    	}
    
    • Contact
    • Developers
    • More