Temporarily include bndlib 1.47 for testing purposes (not yet on central)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1185095 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/BsnToMavenPath.java b/bundleplugin/src/main/java/aQute/bnd/maven/BsnToMavenPath.java
new file mode 100644
index 0000000..6dc716c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/BsnToMavenPath.java
@@ -0,0 +1,5 @@
+package aQute.bnd.maven;
+
+public interface BsnToMavenPath {
+    String[] getGroupAndArtifact(String bsn);
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
new file mode 100644
index 0000000..c274ca5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenCommand.java
@@ -0,0 +1,615 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.bnd.maven.support.*;
+import aQute.bnd.maven.support.Pom.*;
+import aQute.bnd.settings.*;
+import aQute.lib.collections.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.header.*;
+
+public class MavenCommand extends Processor {
+	final Settings	settings	= new Settings();
+	File			temp;
+
+	public MavenCommand() {
+	}
+
+	public MavenCommand(Processor p) {
+		super(p);
+	}
+
+	/**
+	 * maven deploy [-url repo] [-passphrase passphrase] [-homedir homedir]
+	 * [-keyname keyname] bundle ...
+	 * 
+	 * @param args
+	 * @param i
+	 * @throws Exception
+	 */
+	public void run(String args[], int i) throws Exception {
+		temp = new File("maven-bundle");
+
+		if (i >= args.length) {
+			help();
+			return;
+		}
+
+		while (i < args.length && args[i].startsWith("-")) {
+			String option = args[i];
+			trace("option " + option);
+			if (option.equals("-temp"))
+				temp = getFile(args[++i]);
+			else {
+				help();
+				error("Invalid option " + option);
+			}
+			i++;
+		}
+
+		String cmd = args[i++];
+
+		trace("temp dir " + temp);
+		IO.delete(temp);
+		temp.mkdirs();
+		if (!temp.isDirectory())
+			throw new IOException("Cannot create temp directory");
+
+		if (cmd.equals("settings"))
+			settings();
+		else if (cmd.equals("help"))
+			help();
+		else if (cmd.equals("bundle"))
+			bundle(args, i);
+		else if (cmd.equals("view"))
+			view(args, i);
+		else
+			error("No such command %s, type help", cmd);
+	}
+
+	private void help() {
+		System.err.println("Usage:\n");
+		System.err
+				.println("  maven \n" //
+						+ "  [-temp <dir>]            use as temp directory\n" //
+						+ "  settings                 show maven settings\n" //
+						+ "  bundle                   turn a bundle into a maven bundle\n" //
+						+ "    [-properties <file>]   provide properties, properties starting with javadoc are options for javadoc, like javadoc-tag=...\n"
+						+ "    [-javadoc <file|url>]  where to find the javadoc (zip/dir), otherwise generated\n" //
+						+ "    [-source <file|url>]   where to find the source (zip/dir), otherwise from OSGI-OPT/src\n" //
+						+ "    [-scm <url>]           required scm in pom, otherwise from Bundle-SCM\n" //
+						+ "    [-url <url>]           required project url in pom\n" //
+						+ "    [-bsn bsn]             overrides bsn\n" //
+						+ "    [-version <version>]   overrides version\n" //
+						+ "    [-developer <email>]   developer email\n" //
+						+ "    [-nodelete]            do not delete temp files\n" //
+						+ "    [-passphrase <gpgp passphrase>] signer password\n"//
+						+ "        <file|url> ");
+	}
+
+	/**
+	 * Show the maven settings
+	 * 
+	 * @throws FileNotFoundException
+	 * @throws Exception
+	 */
+	private void settings() throws FileNotFoundException, Exception {
+		File userHome = new File(System.getProperty("user.home"));
+		File m2 = new File(userHome, ".m2");
+		if (!m2.isDirectory()) {
+			error("There is no m2 directory at %s", userHome);
+			return;
+		}
+		File settings = new File(m2, "settings.xml");
+		if (!settings.isFile()) {
+			error("There is no settings file at '%s'", settings.getAbsolutePath());
+			return;
+		}
+
+		FileReader rdr = new FileReader(settings);
+
+		LineCollection lc = new LineCollection(new BufferedReader(rdr));
+		while (lc.hasNext()) {
+			System.out.println(lc.next());
+		}
+	}
+
+	/**
+	 * Create a maven bundle.
+	 * 
+	 * @param args
+	 * @param i
+	 * @throws Exception
+	 */
+	private void bundle(String args[], int i) throws Exception {
+		List<String> developers = new ArrayList<String>();
+		Properties properties = new Properties();
+
+		String scm = null;
+		String passphrase = null;
+		String javadoc = null;
+		String source = null;
+		String output = "bundle.jar";
+		String url = null;
+		String artifact = null;
+		String group = null;
+		String version = null;
+		boolean nodelete = false;
+
+		while (i < args.length && args[i].startsWith("-")) {
+			String option = args[i++];
+			trace("bundle option %s", option);
+			if (option.equals("-scm"))
+				scm = args[i++];
+			else if (option.equals("-group"))
+				group = args[i++];
+			else if (option.equals("-artifact"))
+				artifact = args[i++];
+			else if (option.equals("-version"))
+				version = args[i++];
+			else if (option.equals("-developer"))
+				developers.add(args[i++]);
+			else if (option.equals("-passphrase")) {
+				passphrase = args[i++];
+			} else if (option.equals("-url")) {
+				url = args[i++];
+			} else if (option.equals("-javadoc"))
+				javadoc = args[i++];
+			else if (option.equals("-source"))
+				source = args[i++];
+			else if (option.equals("-output"))
+				output = args[i++];
+			else if (option.equals("-nodelete"))
+				nodelete=true;
+			else if (option.startsWith("-properties")) {
+				InputStream in = new FileInputStream(args[i++]);
+				try {
+					properties.load(in);
+				} catch (Exception e) {
+					in.close();
+				}
+			}
+		}
+
+		if (developers.isEmpty()) {
+			String email = settings.globalGet(Settings.EMAIL, null);
+			if (email == null)
+				error("No developer email set, you can set global default email with: bnd global email Peter.Kriens@aQute.biz");
+			else
+				developers.add(email);
+		}
+
+		if (i == args.length) {
+			error("too few arguments, no bundle specified");
+			return;
+		}
+
+		if (i != args.length - 1) {
+			error("too many arguments, only one bundle allowed");
+			return;
+		}
+
+		String input = args[i++];
+
+		Jar binaryJar = getJarFromFileOrURL(input);
+		trace("got %s", binaryJar);
+		if (binaryJar == null) {
+			error("JAR does not exist: %s", input);
+			return;
+		}
+
+		File original = getFile(temp, "original");
+		original.mkdirs();
+		binaryJar.expand(original);
+		binaryJar.calcChecksums(null);
+
+		Manifest manifest = binaryJar.getManifest();
+		trace("got manifest");
+
+		PomFromManifest pom = new PomFromManifest(manifest);
+
+		if (scm != null)
+			pom.setSCM(scm);
+		if (url != null)
+			pom.setURL(url);
+		if (artifact != null)
+			pom.setArtifact(artifact);
+		if (artifact != null)
+			pom.setGroup(group);
+		if (version != null)
+			pom.setVersion(version);
+		trace(url);
+		for (String d : developers)
+			pom.addDeveloper(d);
+
+		Set<String> exports = OSGiHeader.parseHeader(
+				manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE)).keySet();
+
+		Jar sourceJar;
+		if (source == null) {
+			trace("Splitting source code");
+			sourceJar = new Jar("source");
+			for (Map.Entry<String, Resource> entry : binaryJar.getResources().entrySet()) {
+				if (entry.getKey().startsWith("OSGI-OPT/src")) {
+					sourceJar.putResource(entry.getKey().substring("OSGI-OPT/src/".length()),
+							entry.getValue());
+				}
+			}
+			copyInfo(binaryJar, sourceJar, "source");
+		} else {
+			sourceJar = getJarFromFileOrURL(source);
+		}
+		sourceJar.calcChecksums(null);
+		
+		Jar javadocJar;
+		if (javadoc == null) {
+			trace("creating javadoc because -javadoc not used");
+			javadocJar = javadoc(getFile(original, "OSGI-OPT/src"), exports, manifest, properties);
+			if (javadocJar == null) {
+				error("Cannot find source code in OSGI-OPT/src to generate Javadoc");
+				return;
+			}
+			copyInfo(binaryJar, javadocJar, "javadoc");
+		} else {
+			trace("Loading javadoc externally %s", javadoc);
+			javadocJar = getJarFromFileOrURL(javadoc);
+		}
+		javadocJar.calcChecksums(null);
+		
+		addClose(binaryJar);
+		addClose(sourceJar);
+		addClose(javadocJar);
+
+		trace("creating bundle dir");
+		File bundle = new File(temp, "bundle");
+		bundle.mkdirs();
+
+		String prefix = pom.getArtifactId() + "-" + pom.getVersion();
+		File binaryFile = new File(bundle, prefix + ".jar");
+		File sourceFile = new File(bundle, prefix + "-sources.jar");
+		File javadocFile = new File(bundle, prefix + "-javadoc.jar");
+		File pomFile = new File(bundle, "pom.xml").getAbsoluteFile();
+		trace("creating output files %s, %s,%s, and %s", binaryFile, sourceFile, javadocFile,
+				pomFile);
+
+		IO.copy(pom.openInputStream(), pomFile);
+		trace("copied pom");
+
+		trace("writing binary %s", binaryFile);
+		binaryJar.write(binaryFile);
+
+		trace("writing source %s", sourceFile);
+		sourceJar.write(sourceFile);
+
+		trace("writing javadoc %s", javadocFile);
+		javadocJar.write(javadocFile);
+
+		sign(binaryFile, passphrase);
+		sign(sourceFile, passphrase);
+		sign(javadocFile, passphrase);
+		sign(pomFile, passphrase);
+
+		trace("create bundle");
+		Jar bundleJar = new Jar(bundle);
+		addClose(bundleJar);
+		File outputFile = getFile(output);
+		bundleJar.write(outputFile);
+		trace("created bundle %s", outputFile);
+
+		binaryJar.close();
+		sourceJar.close();
+		javadocJar.close();
+		bundleJar.close();
+		if (!nodelete)
+			IO.delete(temp);
+	}
+
+	private void copyInfo(Jar source, Jar dest, String type) throws Exception {
+		source.ensureManifest();
+		dest.ensureManifest();
+		copyInfoResource( source, dest, "LICENSE");
+		copyInfoResource( source, dest, "LICENSE.html");
+		copyInfoResource( source, dest, "about.html");
+		
+		Manifest sm = source.getManifest();
+		Manifest dm = dest.getManifest();
+		copyInfoHeader( sm, dm, "Bundle-Description","");
+		copyInfoHeader( sm, dm, "Bundle-Vendor","");
+		copyInfoHeader( sm, dm, "Bundle-Copyright","");
+		copyInfoHeader( sm, dm, "Bundle-DocURL","");
+		copyInfoHeader( sm, dm, "Bundle-License","");
+		copyInfoHeader( sm, dm, "Bundle-Name", " " + type);
+		copyInfoHeader( sm, dm, "Bundle-SymbolicName", "." + type);
+		copyInfoHeader( sm, dm, "Bundle-Version", "");
+	}
+
+	private void copyInfoHeader(Manifest sm, Manifest dm, String key, String value) {
+		String v = sm.getMainAttributes().getValue(key);
+		if ( v == null) {
+			trace("no source for " + key);
+			return;
+		}
+		
+		if ( dm.getMainAttributes().getValue(key) != null) {
+			trace("already have " + key );
+			return;
+		}
+		
+		dm.getMainAttributes().putValue(key, v + value);
+	}
+
+	private void copyInfoResource(Jar source, Jar dest, String type) {
+		if ( source.getResources().containsKey(type) && !dest.getResources().containsKey(type))
+			dest.putResource(type, source.getResource(type));
+	}
+
+	/**
+	 * @return
+	 * @throws IOException
+	 * @throws MalformedURLException
+	 */
+	protected Jar getJarFromFileOrURL(String spec) throws IOException, MalformedURLException {
+		Jar jar;
+		File jarFile = getFile(spec);
+		if (jarFile.exists()) {
+			jar = new Jar(jarFile);
+		} else {
+			URL url = new URL(spec);
+			InputStream in = url.openStream();
+			try {
+				jar = new Jar(url.getFile(), in);
+			} finally {
+				in.close();
+			}
+		}
+		addClose(jar);
+		return jar;
+	}
+
+	private void sign(File file, String passphrase) throws Exception {
+		trace("signing %s", file);
+		File asc = new File(file.getParentFile(), file.getName() + ".asc");
+		asc.delete();
+
+		Command command = new Command();
+		command.setTrace();
+
+		command.add(getProperty("gpgp", "gpg"));
+		if (passphrase != null)
+			command.add("--passphrase", passphrase);
+		command.add("-ab", "--sign"); // not the -b!!
+		command.add(file.getAbsolutePath());
+		System.out.println(command);
+		StringBuffer stdout = new StringBuffer();
+		StringBuffer stderr = new StringBuffer();
+		int result = command.execute(stdout, stderr);
+		if (result != 0) {
+			error("gpg signing %s failed because %s", file, "" + stdout + stderr);
+		}
+	}
+
+	private Jar javadoc(File source, Set<String> exports, Manifest m, Properties p)
+			throws Exception {
+		File tmp = new File(temp, "javadoc");
+		tmp.mkdirs();
+
+		Command command = new Command();
+		command.add(getProperty("javadoc", "javadoc"));
+		command.add("-quiet");
+		command.add("-protected");
+		// command.add("-classpath");
+		// command.add(binary.getAbsolutePath());
+		command.add("-d");
+		command.add(tmp.getAbsolutePath());
+		command.add("-charset");
+		command.add("UTF-8");
+		command.add("-sourcepath");
+		command.add(source.getAbsolutePath());
+
+		Attributes attr = m.getMainAttributes();
+		Properties pp = new Properties(p);
+		set(pp, "-doctitle", description(attr));
+		set(pp, "-header", description(attr));
+		set(pp, "-windowtitle", name(attr));
+		set(pp, "-bottom", copyright(attr));
+		set(pp, "-footer", license(attr));
+
+		command.add("-tag");
+		command.add("Immutable:t:Immutable");
+		command.add("-tag");
+		command.add("ThreadSafe:t:ThreadSafe");
+		command.add("-tag");
+		command.add("NotThreadSafe:t:NotThreadSafe");
+		command.add("-tag");
+		command.add("GuardedBy:mf:Guarded By:");
+		command.add("-tag");
+		command.add("security:m:Required Permissions");
+		command.add("-tag");
+		command.add("noimplement:t:Consumers of this API must not implement this interface");
+
+		for (Enumeration<?> e = pp.propertyNames(); e.hasMoreElements();) {
+			String key = (String) e.nextElement();
+			String value = pp.getProperty(key);
+
+			if (key.startsWith("javadoc")) {
+				key = key.substring("javadoc".length());
+				removeDuplicateMarker(key);
+				command.add(key);
+				command.add(value);
+			}
+		}
+		for (String packageName : exports) {
+			command.add(packageName);
+		}
+
+		StringBuffer out = new StringBuffer();
+		StringBuffer err = new StringBuffer();
+
+		System.out.println(command);
+
+		int result = command.execute(out, err);
+		if (result != 0) {
+			warning("Error during execution of javadoc command: %s\n******************\n%s", out,
+					err);
+		}
+		Jar jar = new Jar(tmp);
+		addClose(jar);
+		return jar;
+	}
+
+	/**
+	 * Generate a license string
+	 * 
+	 * @param attr
+	 * @return
+	 */
+	private String license(Attributes attr) {
+		Map<String, Map<String, String>> map = Processor.parseHeader(
+				attr.getValue(Constants.BUNDLE_LICENSE), null);
+		if (map.isEmpty())
+			return null;
+
+		StringBuilder sb = new StringBuilder();
+		String sep = "Licensed under ";
+		for (Map.Entry<String, Map<String, String>> entry : map.entrySet()) {
+			sb.append(sep);
+			String key = entry.getKey();
+			String link = entry.getValue().get("link");
+			String description = entry.getValue().get("description");
+
+			if (description == null)
+				description = key;
+
+			if (link != null) {
+				sb.append("<a href='");
+				sb.append(link);
+				sb.append("'>");
+			}
+			sb.append(description);
+			if (link != null) {
+				sb.append("</a>");
+			}
+			sep = ",<br/>";
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * Generate the copyright statement.
+	 * 
+	 * @param attr
+	 * @return
+	 */
+	private String copyright(Attributes attr) {
+		return attr.getValue(Constants.BUNDLE_COPYRIGHT);
+	}
+
+	private String name(Attributes attr) {
+		String name = attr.getValue(Constants.BUNDLE_NAME);
+		if (name == null)
+			name = attr.getValue(Constants.BUNDLE_SYMBOLICNAME);
+		return name;
+	}
+
+	private String description(Attributes attr) {
+		String descr = attr.getValue(Constants.BUNDLE_DESCRIPTION);
+		if (descr == null)
+			descr = attr.getValue(Constants.BUNDLE_NAME);
+		if (descr == null)
+			descr = attr.getValue(Constants.BUNDLE_SYMBOLICNAME);
+		return descr;
+	}
+
+	private void set(Properties pp, String option, String defaultValue) {
+		String key = "javadoc" + option;
+		String existingValue = pp.getProperty(key);
+		if (existingValue != null)
+			return;
+
+		pp.setProperty(key, defaultValue);
+	}
+
+	
+	/**
+	 * View - Show the dependency details of an artifact
+	 */
+	
+
+	static Executor executor = Executors.newCachedThreadPool();
+	static Pattern GROUP_ARTIFACT_VERSION = Pattern.compile("([^+]+)\\+([^+]+)\\+([^+]+)");
+	void view( String args[], int i) throws Exception {
+		Maven maven = new Maven(executor);
+		OutputStream out = System.out;
+		
+		List<URI>	urls = new ArrayList<URI>();
+		
+		while ( i < args.length && args[i].startsWith("-")) {
+			if( "-r".equals(args[i])) {
+				URI uri = new URI(args[++i]);
+				urls.add( uri );
+				System.out.println("URI for repo " + uri);
+			}
+			else
+				if ( "-o".equals(args[i])) {
+					out = new FileOutputStream(args[++i]);
+				}
+				else
+					throw new IllegalArgumentException("Unknown option: " + args[i]);
+			
+			i++;
+		}
+		
+		URI[] urls2 = urls.toArray(new URI[urls.size()]);
+		PrintWriter pw = new PrintWriter(out);
+		
+		while ( i < args.length) {
+			String ref = args[i++];
+			pw.println("Ref " + ref);
+			
+			Matcher matcher = GROUP_ARTIFACT_VERSION.matcher(ref);
+			if (matcher.matches()) {
+				
+				String group = matcher.group(1);
+				String artifact = matcher.group(2);
+				String version = matcher.group(3);
+				CachedPom pom = maven.getPom(group, artifact, version, urls2);
+				
+				Builder a = new Builder();
+				a.setProperty("Private-Package", "*");
+				Set<Pom> dependencies = pom.getDependencies(Scope.compile, urls2);
+				for ( Pom dep : dependencies ) {
+					System.out.printf( "%20s %-20s %10s\n", dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
+					a.addClasspath(dep.getArtifact());
+				}
+				pw.println(a.getClasspath());
+				a.build();
+
+				TreeSet<String> sorted = new TreeSet<String>( a.getImports().keySet());
+				for ( String p :sorted) {
+					pw.printf("%-40s\n",p);
+				}
+//				for ( Map.Entry<String, Set<String>> entry : a.getUses().entrySet()) {
+//					String from = entry.getKey();
+//					for ( String uses : entry.getValue()) {
+//						System.out.printf("%40s %s\n", from, uses);
+//						from = "";
+//					}
+//				}
+				a.close();
+			} else
+				System.err.println("Wrong, must look like group+artifact+version, is " + ref);
+			
+		}
+	}
+	
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenDependencyGraph.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDependencyGraph.java
new file mode 100644
index 0000000..6278be2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDependencyGraph.java
@@ -0,0 +1,139 @@
+package aQute.bnd.maven;
+
+import java.net.*;
+import java.util.*;
+
+import javax.xml.parsers.*;
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+public class MavenDependencyGraph {
+	final static DocumentBuilderFactory	docFactory		= DocumentBuilderFactory.newInstance();
+	final static XPathFactory			xpathFactory	= XPathFactory.newInstance();
+	final List<Artifact>				dependencies	= new ArrayList<Artifact>();
+	final List<URL>						repositories	= new ArrayList<URL>();
+	final XPath							xpath			= xpathFactory.newXPath();
+	final Map<URL, Artifact>			cache			= new HashMap<URL, Artifact>();
+	Artifact						root;
+
+	enum Scope {
+		COMPILE, RUNTIME, TEST, PROVIDED, SYSTEM, IMPORT,
+	};
+
+	
+	public class Artifact {
+
+		String			groupId;
+		String			artifactId;
+		String			version;
+		Scope			scope			= Scope.COMPILE;
+		boolean			optional;
+		String			type;
+		URL				url;
+		List<Artifact>	dependencies	= new ArrayList<Artifact>();
+
+		public Artifact(URL url) throws Exception {
+			if (url != null) {
+				this.url = url;
+				DocumentBuilder db = docFactory.newDocumentBuilder();
+				Document doc = db.parse(url.toString());
+				Node node = (Node) xpath.evaluate("/project", doc, XPathConstants.NODE);
+
+				groupId = xpath.evaluate("groupId", node);
+				artifactId = xpath.evaluate("artifactId", node);
+				version = xpath.evaluate("version", node);
+				type = xpath.evaluate("type", node);
+				optional = (Boolean) xpath.evaluate("optinal", node, XPathConstants.BOOLEAN);
+				String scope = xpath.evaluate("scope", node);
+				if (scope != null && scope.length() > 0) {
+					this.scope = Scope.valueOf(scope.toUpperCase());
+				}
+				NodeList evaluate = (NodeList) xpath.evaluate("//dependencies/dependency", doc,
+						XPathConstants.NODESET);
+
+				for (int i = 0; i < evaluate.getLength(); i++) {
+					Node childNode = evaluate.item(i);
+					Artifact artifact = getArtifact(xpath.evaluate("groupId", childNode), xpath
+							.evaluate("artifactId", childNode), xpath.evaluate("version", childNode));
+					add(artifact);
+				}
+			}
+		}
+		
+		
+
+		public void add(Artifact artifact) {
+			dependencies.add(artifact);
+		}
+
+
+
+		public String toString() {
+			return groupId + "." + artifactId + "-" + version + "[" + scope + "," + optional + "]";
+		}
+
+		public String getPath() throws URISyntaxException {
+			return groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + artifactId
+					+ "-" + version;
+		}
+
+	}
+
+	public void addRepository(URL repository) {
+		repositories.add(repository);
+	}
+
+	/**
+	 * @param xp
+	 * @param node
+	 * @param d
+	 * @throws XPathExpressionException
+	 */
+
+	public Artifact getArtifact(String groupId, String artifactId, String version) {
+		for (URL repository : repositories) {
+			String path = getPath(repository.toString(), groupId, artifactId, version);
+
+			try {
+				URL url = new URL(path + ".pom");
+				if (cache.containsKey(url)) {
+					return cache.get(url);
+				} else {
+					return new Artifact(url);
+				}
+			} catch (Exception e) {
+				System.err.println("Failed to get " + artifactId + " from " + repository);
+			}
+		}
+		return null;
+	}
+
+	private String getPath(String path, String groupId, String artifactId, String version) {
+		StringBuilder sb = new StringBuilder();
+		sb.append(path);
+		if (!path.endsWith("/"))
+			sb.append("/");
+
+		sb.append(groupId.replace('.', '/'));
+		sb.append('/');
+		sb.append(artifactId);
+		sb.append('/');
+		sb.append(version);
+		sb.append('/');
+		sb.append(artifactId);
+		sb.append('-');
+		sb.append(version);
+		return null;
+	}
+
+	
+	
+	public void addArtifact( Artifact artifact ) throws Exception {
+		if ( root == null)
+			root = new Artifact(null);
+		root.add(artifact);
+	}
+	
+	
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeploy.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeploy.java
new file mode 100644
index 0000000..24981ca
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeploy.java
@@ -0,0 +1,186 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.build.*;
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.reporter.*;
+
+public class MavenDeploy implements Deploy, Plugin {
+
+	String		repository;
+	String		url;
+	String		homedir;
+	String		keyname;
+
+	String		passphrase;
+	Reporter	reporter;
+
+	public void setProperties(Map<String, String> map) {
+		repository = map.get("repository");
+		url = map.get("url");
+		passphrase = map.get("passphrase");
+		homedir = map.get("homedir");
+		keyname = map.get("keyname");
+
+		if (url == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository URL");
+		if (repository == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository name");
+	}
+
+	public void setReporter(Reporter processor) {
+		this.reporter = processor;
+	}
+
+	/**
+	 */
+	public boolean deploy(Project project, Jar original) throws Exception {
+		Map<String, Map<String, String>> deploy = project.parseHeader(project
+				.getProperty(Constants.DEPLOY));
+
+		Map<String, String> maven = deploy.get(repository);
+		if (maven == null)
+			return false; // we're not playing for this bundle
+
+		project.progress("deploying %s to Maven repo: %s", original, repository);
+		File target = project.getTarget();
+		File tmp = Processor.getFile(target, repository);
+		tmp.mkdirs();
+
+		Manifest manifest = original.getManifest();
+		if (manifest == null)
+			project.error("Jar has no manifest: %s", original);
+		else {
+			project.progress("Writing pom.xml");
+			PomResource pom = new PomResource(manifest);
+			pom.setProperties(maven);
+			File pomFile = write(tmp, pom, "pom.xml");
+
+			Jar main = new Jar("main");
+			Jar src = new Jar("src");
+			try {
+				split(original, main, src);
+				Map<String, Map<String, String>> exports = project.parseHeader(manifest
+						.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+				File jdoc = new File(tmp, "jdoc");
+				jdoc.mkdirs();
+				project.progress("Generating Javadoc for: " + exports.keySet());
+				Jar javadoc = javadoc(jdoc, project, exports.keySet());
+				project.progress("Writing javadoc jar");
+				File javadocFile = write(tmp, new JarResource(javadoc), "javadoc.jar");
+				project.progress("Writing main file");
+				File mainFile = write(tmp, new JarResource(main), "main.jar");
+				project.progress("Writing sources file");
+				File srcFile = write(tmp, new JarResource(main), "src.jar");
+
+				project.progress("Deploying main file");
+				maven_gpg_sign_and_deploy(project, mainFile, null, pomFile);
+				project.progress("Deploying main sources file");
+				maven_gpg_sign_and_deploy(project, srcFile, "sources", null);
+				project.progress("Deploying main javadoc file");
+				maven_gpg_sign_and_deploy(project, javadocFile, "javadoc", null);
+
+			} finally {
+				main.close();
+				src.close();
+			}
+		}
+		return true;
+	}
+
+	private void split(Jar original, Jar main, Jar src) {
+		for (Map.Entry<String, Resource> e : original.getResources().entrySet()) {
+			String path = e.getKey();
+			if (path.startsWith("OSGI-OPT/src/")) {
+				src.putResource(path.substring("OSGI-OPT/src/".length()), e.getValue());
+			} else {
+				main.putResource(path, e.getValue());
+			}
+		}
+	}
+
+	// gpg:sign-and-deploy-file \
+	// -Durl=http://oss.sonatype.org/service/local/staging/deploy/maven2
+	// \
+	// -DrepositoryId=sonatype-nexus-staging \
+	// -DupdateReleaseInfo=true \
+	// -DpomFile=pom.xml \
+	// -Dfile=/Ws/bnd/biz.aQute.bndlib/tmp/biz.aQute.bndlib.jar \
+	// -Dpassphrase=a1k3v3t5x3
+
+	private void maven_gpg_sign_and_deploy(Project b, File file, String classifier, File pomFile)
+			throws Exception {
+		Command command = new Command();
+		command.setTrace();
+		command.add(b.getProperty("mvn", "mvn"));
+		command.add("gpg:sign-and-deploy-file", "-DreleaseInfo=true", "-DpomFile=pom.xml");
+		command.add("-Dfile=" + file.getAbsolutePath());
+		command.add("-DrepositoryId=" + repository);
+		command.add("-Durl=" + url);
+		optional(command, "passphrase", passphrase);
+		optional(command, "keyname", keyname);
+		optional(command, "homedir", homedir);
+		optional(command, "classifier", classifier);
+		optional(command, "pomFile", pomFile == null ? null : pomFile.getAbsolutePath());
+
+		StringBuffer stdout = new StringBuffer();
+		StringBuffer stderr = new StringBuffer();
+
+		int result = command.execute(stdout, stderr);
+		if (result != 0) {
+			b.error("Maven deploy to %s failed to sign and transfer %s because %s", repository,
+					file, "" + stdout + stderr);
+		}
+	}
+
+	private void optional(Command command, String key, String value) {
+		if (value == null)
+			return;
+
+		command.add("-D=" + value);
+	}
+
+	private Jar javadoc(File tmp, Project b, Set<String> exports) throws Exception {
+		Command command = new Command();
+		
+		command.add(b.getProperty("javadoc", "javadoc"));
+		command.add("-d");
+		command.add(tmp.getAbsolutePath());
+		command.add("-sourcepath");
+		command.add( Processor.join(b.getSourcePath(),File.pathSeparator));
+
+		for (String packageName : exports) {
+			command.add(packageName);
+		}
+
+		StringBuffer out = new StringBuffer();
+		StringBuffer err = new StringBuffer();
+		Command c = new Command();
+		c.setTrace();
+		int result = c.execute(out, err);
+		if (result == 0) {
+			Jar jar = new Jar(tmp);
+			b.addClose(jar);
+			return jar;
+		}
+		b.error("Error during execution of javadoc command: %s / %s", out, err);
+		return null;
+	}
+
+	private File write(File base, Resource r, String fileName) throws Exception {
+		File f = Processor.getFile(base, fileName);
+		OutputStream out = new FileOutputStream(f);
+		try {
+			r.write(out);
+		} finally {
+			out.close();
+		}
+		return f;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeployCmd.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeployCmd.java
new file mode 100644
index 0000000..aaa23ff
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenDeployCmd.java
@@ -0,0 +1,221 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.build.*;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.reporter.*;
+
+public class MavenDeployCmd extends Processor {
+
+	String		repository	= "nexus";
+	String		url			= "http://oss.sonatype.org/service/local/staging/deploy/maven2";
+	String		homedir;
+	String		keyname;
+
+	String		passphrase;
+	Reporter	reporter;
+
+	/**
+	 * maven deploy [-url repo] [-passphrase passphrase] [-homedir homedir]
+	 * [-keyname keyname] bundle ...
+	 * 
+	 * @param args
+	 * @param i
+	 * @throws Exception
+	 */
+	void run(String args[], int i) throws Exception {
+		if (i >= args.length) {
+			System.err
+					.println("Usage:\n");
+			System.err.println("  deploy [-url repo] [-passphrase passphrase] [-homedir homedir] [-keyname keyname] bundle ...");
+			System.err.println("  settings");
+			return;
+		}
+
+		@SuppressWarnings("unused") String cmd = args[i++];
+		
+		while (i < args.length && args[i].startsWith("-")) {
+			String option = args[i];
+			if (option.equals("-url"))
+				repository = args[++i];
+			else if (option.equals("-passphrase"))
+				passphrase = args[++i];
+			else if (option.equals("-url"))
+				homedir = args[++i];
+			else if (option.equals("-keyname"))
+				keyname = args[++i];
+			else
+				error("Invalid command ");
+		}
+
+		
+	}
+
+	public void setProperties(Map<String, String> map) {
+		repository = map.get("repository");
+		url = map.get("url");
+		passphrase = map.get("passphrase");
+		homedir = map.get("homedir");
+		keyname = map.get("keyname");
+
+		if (url == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository URL");
+		if (repository == null)
+			throw new IllegalArgumentException("MavenDeploy plugin must get a repository name");
+	}
+
+	public void setReporter(Reporter processor) {
+		this.reporter = processor;
+	}
+
+	/**
+	 */
+	public boolean deploy(Project project, Jar original) throws Exception {
+		Map<String, Map<String, String>> deploy = project.parseHeader(project
+				.getProperty(Constants.DEPLOY));
+
+		Map<String, String> maven = deploy.get(repository);
+		if (maven == null)
+			return false; // we're not playing for this bundle
+
+		project.progress("deploying %s to Maven repo: %s", original, repository);
+		File target = project.getTarget();
+		File tmp = Processor.getFile(target, repository);
+		tmp.mkdirs();
+
+		Manifest manifest = original.getManifest();
+		if (manifest == null)
+			project.error("Jar has no manifest: %s", original);
+		else {
+			project.progress("Writing pom.xml");
+			PomResource pom = new PomResource(manifest);
+			pom.setProperties(maven);
+			File pomFile = write(tmp, pom, "pom.xml");
+
+			Jar main = new Jar("main");
+			Jar src = new Jar("src");
+			try {
+				split(original, main, src);
+				Map<String, Map<String, String>> exports = project.parseHeader(manifest
+						.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+				File jdoc = new File(tmp, "jdoc");
+				jdoc.mkdirs();
+				project.progress("Generating Javadoc for: " + exports.keySet());
+				Jar javadoc = javadoc(jdoc, project, exports.keySet());
+				project.progress("Writing javadoc jar");
+				File javadocFile = write(tmp, new JarResource(javadoc), "javadoc.jar");
+				project.progress("Writing main file");
+				File mainFile = write(tmp, new JarResource(main), "main.jar");
+				project.progress("Writing sources file");
+				File srcFile = write(tmp, new JarResource(main), "src.jar");
+
+				project.progress("Deploying main file");
+				maven_gpg_sign_and_deploy(project, mainFile, null, pomFile);
+				project.progress("Deploying main sources file");
+				maven_gpg_sign_and_deploy(project, srcFile, "sources", null);
+				project.progress("Deploying main javadoc file");
+				maven_gpg_sign_and_deploy(project, javadocFile, "javadoc", null);
+
+			} finally {
+				main.close();
+				src.close();
+			}
+		}
+		return true;
+	}
+
+	private void split(Jar original, Jar main, Jar src) {
+		for (Map.Entry<String, Resource> e : original.getResources().entrySet()) {
+			String path = e.getKey();
+			if (path.startsWith("OSGI-OPT/src/")) {
+				src.putResource(path.substring("OSGI-OPT/src/".length()), e.getValue());
+			} else {
+				main.putResource(path, e.getValue());
+			}
+		}
+	}
+
+	// gpg:sign-and-deploy-file \
+	// -Durl=http://oss.sonatype.org/service/local/staging/deploy/maven2
+	// \
+	// -DrepositoryId=sonatype-nexus-staging \
+	// -DupdateReleaseInfo=true \
+	// -DpomFile=pom.xml \
+	// -Dfile=/Ws/bnd/biz.aQute.bndlib/tmp/biz.aQute.bndlib.jar \
+	// -Dpassphrase=a1k3v3t5x3
+
+	private void maven_gpg_sign_and_deploy(Project b, File file, String classifier, File pomFile)
+			throws Exception {
+		Command command = new Command();
+		command.setTrace();
+		command.add(b.getProperty("mvn", "mvn"));
+		command.add("gpg:sign-and-deploy-file", "-DreleaseInfo=true", "-DpomFile=pom.xml");
+		command.add("-Dfile=" + file.getAbsolutePath());
+		command.add("-DrepositoryId=" + repository);
+		command.add("-Durl=" + url);
+		optional(command, "passphrase", passphrase);
+		optional(command, "keyname", keyname);
+		optional(command, "homedir", homedir);
+		optional(command, "classifier", classifier);
+		optional(command, "pomFile", pomFile == null ? null : pomFile.getAbsolutePath());
+
+		StringBuffer stdout = new StringBuffer();
+		StringBuffer stderr = new StringBuffer();
+
+		int result = command.execute(stdout, stderr);
+		if (result != 0) {
+			b.error("Maven deploy to %s failed to sign and transfer %s because %s", repository,
+					file, "" + stdout + stderr);
+		}
+	}
+
+	private void optional(Command command, String key, String value) {
+		if (value == null)
+			return;
+
+		command.add("-D=" + value);
+	}
+
+	private Jar javadoc(File tmp, Project b, Set<String> exports) throws Exception {
+		Command command = new Command();
+
+		command.add(b.getProperty("javadoc", "javadoc"));
+		command.add("-d");
+		command.add(tmp.getAbsolutePath());
+		command.add("-sourcepath");
+		command.add(Processor.join(b.getSourcePath(), File.pathSeparator));
+
+		for (String packageName : exports) {
+			command.add(packageName);
+		}
+
+		StringBuffer out = new StringBuffer();
+		StringBuffer err = new StringBuffer();
+		Command c = new Command();
+		c.setTrace();
+		int result = c.execute(out, err);
+		if (result == 0) {
+			Jar jar = new Jar(tmp);
+			b.addClose(jar);
+			return jar;
+		}
+		b.error("Error during execution of javadoc command: %s / %s", out, err);
+		return null;
+	}
+
+	private File write(File base, Resource r, String fileName) throws Exception {
+		File f = Processor.getFile(base, fileName);
+		OutputStream out = new FileOutputStream(f);
+		try {
+			r.write(out);
+		} finally {
+			out.close();
+		}
+		return f;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenGroup.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenGroup.java
new file mode 100644
index 0000000..2d49be6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenGroup.java
@@ -0,0 +1,27 @@
+package aQute.bnd.maven;
+
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.libg.reporter.*;
+
+public class MavenGroup implements BsnToMavenPath, Plugin {
+    String    groupId = "";
+
+    public String[] getGroupAndArtifact(String bsn) {
+        String[] result = new String[2];
+        result[0] = groupId;
+        result[1] = bsn;
+        return result;
+    }
+
+    public void setProperties(Map<String, String> map) {
+        if (map.containsKey("groupId")) {
+            groupId = map.get("groupId");
+        }
+    }
+
+    public void setReporter(Reporter processor) {
+    }
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java b/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
new file mode 100644
index 0000000..506ea53
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/MavenRepository.java
@@ -0,0 +1,194 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class MavenRepository implements RepositoryPlugin, Plugin, BsnToMavenPath {
+
+	public static String	NAME	= "name";
+
+	File					root;
+	Reporter				reporter;
+	String					name;
+
+	public String toString() {
+		return "maven:" + root;
+	}
+
+	public boolean canWrite() {
+		return false;
+	}
+
+	public File[] get(String bsn, String version) throws Exception {
+		VersionRange range = new VersionRange("0");
+		if (version != null)
+			range = new VersionRange(version);
+
+		List<BsnToMavenPath> plugins = ((Processor) reporter).getPlugins(BsnToMavenPath.class);
+		if ( plugins.isEmpty())
+			plugins.add(this);
+		
+		for (BsnToMavenPath cvr : plugins) {
+			String[] paths = cvr.getGroupAndArtifact(bsn);
+			if (paths != null) {
+				File[] files = find(paths[0], paths[1], range);
+				if (files != null)
+					return files;
+			}
+		}
+		reporter.trace("Cannot find in maven: %s-%s", bsn, version);
+		return null;
+	}
+
+	File[] find(String groupId, String artifactId, VersionRange range) {
+		String path = groupId.replace(".", "/");
+		File vsdir = Processor.getFile(root, path);
+		if (!vsdir.isDirectory())
+			return null;
+
+		vsdir = Processor.getFile(vsdir, artifactId);
+
+		List<File> result = new ArrayList<File>();
+		if (vsdir.isDirectory()) {
+			String versions[] = vsdir.list();
+			for (String v : versions) {
+				String vv = Analyzer.cleanupVersion(v);
+				if (Verifier.isVersion(vv)) {
+					Version vvv = new Version(vv);
+					if (range.includes(vvv)) {
+						File file = Processor.getFile(vsdir, v + "/" + artifactId + "-" + v
+								+ ".jar");
+						if (file.isFile())
+							result.add(file);
+						else
+							reporter.warning("Expected maven entry was not a valid file %s ", file);
+					}
+				} else {
+					reporter
+							.warning(
+									"Expected a version directory in maven: dir=%s raw-version=%s cleaned-up-version=%s",
+									vsdir, vv, v);
+				}
+			}
+		} else
+			return null;
+
+		return result.toArray(new File[result.size()]);
+	}
+
+	public List<String> list(String regex) {
+		List<String> bsns = new ArrayList<String>();
+		Pattern match = Pattern.compile(".*");
+		if (regex != null)
+			match = Pattern.compile(regex);
+		find(bsns, match, root, "");
+		return bsns;
+	}
+
+	void find(List<String> bsns, Pattern pattern, File base, String name) {
+		if (base.isDirectory()) {
+			String list[] = base.list();
+			boolean found = false;
+			for (String entry : list) {
+				char c = entry.charAt(0);
+				if (c >= '0' && c <= '9') {
+					if (pattern.matcher(name).matches())
+						found = true;
+				} else {
+					String nextName = entry;
+					if (name.length() != 0)
+						nextName = name + "." + entry;
+
+					File next = Processor.getFile(base, entry);
+					find(bsns, pattern, next, nextName);
+				}
+			}
+			if (found)
+				bsns.add(name);
+		}
+	}
+
+	public File put(Jar jar) throws Exception {
+		throw new IllegalStateException("Maven does not support the put command");
+	}
+
+	public List<Version> versions(String bsn) throws Exception {
+		
+		File files[] = get( bsn, null);
+		List<Version> versions = new ArrayList<Version>();
+		for ( File f : files ) {
+			Version v = new Version( f.getParentFile().getName());
+			versions.add(v);
+		}
+		return versions;
+	}
+
+	public void setProperties(Map<String, String> map) {
+		File home = new File("");
+		String root = map.get("root");
+		if (root == null) {
+			home = new File( System.getProperty("user.home") );
+			this.root = Processor.getFile(home , ".m2/repository").getAbsoluteFile();
+		} else
+			this.root = Processor.getFile(home, root).getAbsoluteFile();
+		
+		if (!this.root.isDirectory()) {
+			reporter.error("Maven repository did not get a proper URL to the repository %s", root);
+		}
+		name = (String) map.get(NAME);
+
+	}
+
+	public void setReporter(Reporter processor) {
+		this.reporter = processor;
+	}
+
+	public String[] getGroupAndArtifact(String bsn) {
+		String groupId;
+		String artifactId;
+		int n = bsn.indexOf('.');
+		
+		while ( n > 0 ) {
+			artifactId = bsn.substring(n+1);
+			groupId = bsn.substring(0,n);
+			
+			File gdir = new File(root, groupId.replace('.',File.separatorChar)).getAbsoluteFile();
+			File adir = new File( gdir, artifactId).getAbsoluteFile();
+			if ( adir.isDirectory() )
+				return new String[] {groupId, artifactId};
+			
+			n = bsn.indexOf('.',n+1);
+		}
+		return null;
+	}
+	
+	public String getName() {
+		if (name == null) {
+			return toString();
+		}
+		return name;
+	}
+
+	public File get(String bsn, String range, Strategy strategy, Map<String,String> properties) throws Exception {
+		File[] files = get(bsn, range);
+		if (files.length >= 0) {
+			switch (strategy) {
+			case LOWEST:
+				return files[0];
+			case HIGHEST:
+				return files[files.length - 1];
+			}
+		}
+		return null;
+	}
+
+	public void setRoot( File f  ) {
+		root = f;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/PomFromManifest.java b/bundleplugin/src/main/java/aQute/bnd/maven/PomFromManifest.java
new file mode 100644
index 0000000..3ed560d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomFromManifest.java
@@ -0,0 +1,247 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+public class PomFromManifest extends WriteResource {
+	final Manifest			manifest;
+	private List<String>	scm			= new ArrayList<String>();
+	private List<String>	developers	= new ArrayList<String>();
+	final static Pattern	NAME_URL	= Pattern.compile("(.*)(http://.*)");
+	String					xbsn;
+	String					xversion;
+	String					xgroupId;
+	String					xartifactId;
+	private String			projectURL;
+
+	public String getBsn() {
+		if (xbsn == null)
+			xbsn = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+		if (xbsn == null)
+			throw new RuntimeException("Cannot create POM unless bsn is set");
+
+		xbsn = xbsn.trim();
+		int n = xbsn.lastIndexOf('.');
+		if (n < 0) {
+			n = xbsn.length();
+			xbsn = xbsn + "." + xbsn;
+		}
+
+		if (xgroupId == null)
+			xgroupId = xbsn.substring(0, n);
+		if (xartifactId == null) {
+			xartifactId = xbsn.substring(n + 1);
+			n = xartifactId.indexOf(';');
+			if (n > 0)
+				xartifactId = xartifactId.substring(0, n).trim();
+		}
+
+		return xbsn;
+	}
+
+	public String getGroupId() {
+		getBsn();
+		return xgroupId;
+	}
+
+	public String getArtifactId() {
+		getBsn();
+		return xartifactId;
+	}
+
+	public Version getVersion() {
+		if (xversion != null)
+			return new Version(xversion);
+		String version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+		Version v = new Version(version);
+		return new Version(v.getMajor(), v.getMinor(), v.getMicro());
+	}
+
+	public PomFromManifest(Manifest manifest) {
+		this.manifest = manifest;
+	}
+
+	@Override public long lastModified() {
+		return 0;
+	}
+
+	@Override public void write(OutputStream out) throws IOException {
+		PrintWriter ps = new PrintWriter(out);
+
+		String name = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_NAME);
+
+		String description = manifest.getMainAttributes().getValue(Constants.BUNDLE_DESCRIPTION);
+		String docUrl = manifest.getMainAttributes().getValue(Constants.BUNDLE_DOCURL);
+		String bundleVendor = manifest.getMainAttributes().getValue(Constants.BUNDLE_VENDOR);
+
+		String licenses = manifest.getMainAttributes().getValue(Constants.BUNDLE_LICENSE);
+
+		Tag project = new Tag("project");
+		project.addAttribute("xmlns", "http://maven.apache.org/POM/4.0.0");
+		project.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+		project.addAttribute("xsi:schemaLocation",
+				"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd");
+
+		project.addContent(new Tag("modelVersion").addContent("4.0.0"));
+		project.addContent(new Tag("groupId").addContent(getGroupId()));
+		project.addContent(new Tag("artifactId").addContent(getArtifactId()));
+		project.addContent(new Tag("version").addContent(getVersion().toString()));
+
+		if (description != null) {
+			new Tag(project, "description").addContent(description);
+		}
+		if (name != null) {
+			new Tag(project, "name").addContent(name);
+		}
+
+		if (projectURL != null)
+			new Tag(project, "url").addContent(projectURL);
+		else if (docUrl != null) {
+			new Tag(project, "url").addContent(docUrl);
+		} else
+			new Tag(project, "url").addContent("http://no-url");
+
+		String scmheader = manifest.getMainAttributes().getValue("Bundle-SCM");
+		if (scmheader != null)
+			scm.add(scmheader);
+
+		Tag scmtag = new Tag(project, "scm");
+		if (scm != null && !scm.isEmpty()) {
+			for (String cm : this.scm) {
+				new Tag(scmtag, "url").addContent(cm);
+				new Tag(scmtag, "connection").addContent(cm);
+				new Tag(scmtag, "developerConnection").addContent(cm);
+			}
+		} else {
+			new Tag(scmtag, "url").addContent("private");
+			new Tag(scmtag, "connection").addContent("private");
+			new Tag(scmtag, "developerConnection").addContent("private");
+		}
+
+		if (bundleVendor != null) {
+			Matcher m = NAME_URL.matcher(bundleVendor);
+			String namePart = bundleVendor;
+			String urlPart = this.projectURL;
+			if (m.matches()) {
+				namePart = m.group(1);
+				urlPart = m.group(2);
+			}
+			Tag organization = new Tag(project, "organization");
+			new Tag(organization, "name").addContent(namePart.trim());
+			if (urlPart != null) {
+				new Tag(organization, "url").addContent(urlPart.trim());
+			}
+		}
+		if (!developers.isEmpty()) {
+			Tag d = new Tag(project, "developers");
+			for (String email : developers) {
+				String id = email;
+				String xname = email;
+				String organization = null;
+
+				Matcher m = Pattern.compile("([^@]+)@([\\d\\w\\-_\\.]+)\\.([\\d\\w\\-_\\.]+)")
+						.matcher(email);
+				if (m.matches()) {
+					xname = m.group(1);
+					organization = m.group(2);
+				}
+
+				Tag developer = new Tag(d, "developer");
+				new Tag(developer, "id").addContent(id);
+				new Tag(developer, "name").addContent(xname);
+				new Tag(developer, "email").addContent(email);
+				if (organization != null)
+					new Tag(developer, "organization").addContent(organization);
+			}
+		}
+		if (licenses != null) {
+			Tag ls = new Tag(project, "licenses");
+
+			Map<String, Map<String, String>> map = Processor.parseHeader(licenses, null);
+			for (Iterator<Map.Entry<String, Map<String, String>>> e = map.entrySet().iterator(); e
+					.hasNext();) {
+
+				// Bundle-License:
+				// http://www.opensource.org/licenses/apache2.0.php; \
+				// description="${Bundle-Copyright}"; \
+				// link=LICENSE
+				//
+				//  <license>
+				//    <name>This material is licensed under the Apache
+				// Software License, Version 2.0</name>
+				//    <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+				//    <distribution>repo</distribution>
+				//    </license>
+
+				Map.Entry<String, Map<String, String>> entry = e.next();
+				Tag l = new Tag(ls, "license");
+				Map<String, String> values = entry.getValue();
+				String url = entry.getKey();
+
+				if (values.containsKey("description"))
+					tagFromMap(l, values, "description", "name", url);
+				else
+					tagFromMap(l, values, "name", "name", url);
+
+				tagFromMap(l, values, "url", "url", url);
+				tagFromMap(l, values, "distribution", "distribution", "repo");
+			}
+		}
+		project.print(0, ps);
+		ps.flush();
+	}
+
+	/**
+	 * Utility function to print a tag from a map
+	 * 
+	 * @param ps
+	 * @param values
+	 * @param string
+	 * @param tag
+	 * @param object
+	 */
+	private Tag tagFromMap(Tag parent, Map<String, String> values, String string, String tag,
+			String object) {
+		String value = (String) values.get(string);
+		if (value == null)
+			value = object;
+		if (value == null)
+			return parent;
+		new Tag(parent, tag).addContent(value.trim());
+		return parent;
+	}
+
+	public void setSCM(String scm) {
+		this.scm.add(scm);
+	}
+
+	public void setURL(String url) {
+		this.projectURL = url;
+	}
+
+	public void setBsn(String bsn) {
+		this.xbsn = bsn;
+	}
+
+	public void addDeveloper(String email) {
+		this.developers.add(email);
+	}
+
+	public void setVersion(String version) {
+		this.xversion = version;
+	}
+
+	public void setArtifact(String artifact) {
+		this.xartifactId = artifact;
+	}
+
+	public void setGroup(String group) {
+		this.xgroupId = group;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/PomParser.java b/bundleplugin/src/main/java/aQute/bnd/maven/PomParser.java
new file mode 100644
index 0000000..e529141
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomParser.java
@@ -0,0 +1,144 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+
+import javax.xml.parsers.*;
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+
+/**
+ * Provides a way to parse a maven pom as properties.
+ * 
+ * This provides most of the maven elements as properties. It also
+ * provides pom.scope.[compile|test|runtime|provided|system] properties
+ * that can be appended to the build and run path. That is, they are
+ * in the correct format for this.
+ */
+public class PomParser extends Processor {
+	static DocumentBuilderFactory	dbf			= DocumentBuilderFactory.newInstance();
+	static XPathFactory				xpathf		= XPathFactory.newInstance();
+	static Set<String>				multiple	= new HashSet<String>();
+	static Set<String>				skip	= new HashSet<String>();
+
+	static {
+		dbf.setNamespaceAware(false);
+		
+		// Set all elements that need enumeration of their elements
+		// these will not use the name of the subelement but instead
+		// they use an index from 0..n
+		multiple.add("mailingLists");
+		multiple.add("pluginRepositories");
+		multiple.add("repositories");
+		multiple.add("resources");
+		multiple.add("executions");
+		multiple.add("goals");
+		multiple.add("includes");
+		multiple.add("excludes");
+
+		// These properties are not very useful and
+		// pollute the property space.
+		skip.add("plugins");
+		skip.add("dependencies");
+		skip.add("reporting");
+		skip.add("extensions");
+		
+	}
+
+	public Properties getProperties(File pom) throws Exception {
+		DocumentBuilder db = dbf.newDocumentBuilder();
+		XPath xpath = xpathf.newXPath();
+		pom = pom.getAbsoluteFile();
+		Document doc = db.parse(pom);
+		Properties p = new Properties();
+
+		// Check if there is a parent pom
+		String relativePath = xpath.evaluate("project/parent/relativePath", doc);
+		if (relativePath != null && relativePath.length()!=0) {
+			File parentPom = IO.getFile(pom.getParentFile(), relativePath);
+			if (parentPom.isFile()) {
+				Properties parentProps = getProperties(parentPom);
+				p.putAll(parentProps);
+			} else {
+				error("Parent pom for %s is not an existing file (could be directory): %s", pom, parentPom);
+			}
+		}
+
+		Element e = doc.getDocumentElement();
+		traverse("pom", e, p);
+
+		String scopes[] = { "provided", "runtime", "test", "system" };
+		NodeList set = (NodeList) xpath.evaluate("//dependency[not(scope) or scope='compile']", doc,
+				XPathConstants.NODESET);
+		if (set.getLength() != 0)
+			p.put("pom.scope.compile", toBsn(set));
+
+		for (String scope : scopes) {
+			set = (NodeList) xpath.evaluate("//dependency[scope='" + scope + "']", doc,
+					XPathConstants.NODESET);
+			if (set.getLength() != 0)
+				p.put("pom.scope." + scope, toBsn(set));
+		}
+
+		return p;
+	}
+
+	private Object toBsn(NodeList set) throws XPathExpressionException {
+		XPath xpath = xpathf.newXPath();
+		StringBuilder sb = new StringBuilder();
+		String del = "";
+		for (int i = 0; i < set.getLength(); i++) {
+			Node child = set.item(i);
+			String version = xpath.evaluate("version", child);
+			sb.append(del);
+			sb.append(xpath.evaluate("groupId", child));
+			sb.append(".");
+			sb.append(xpath.evaluate("artifactId", child));
+			if (version != null && version.trim().length()!=0) {
+				sb.append(";version=");
+				sb.append( Analyzer.cleanupVersion(version));
+			}
+			del = ", ";
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * The maven POM is quite straightforward, it is basically a structured property file.
+	 * @param name
+	 * @param parent
+	 * @param p
+	 */
+	static void traverse(String name, Node parent, Properties p) {
+		if ( skip.contains(parent.getNodeName()))
+			return;
+		
+		NodeList children = parent.getChildNodes();
+		if (multiple.contains(parent.getNodeName())) {
+			int n = 0;
+			for (int i = 0; i < children.getLength(); i++) {
+				Node child = children.item(i);
+				if (!(child instanceof Text)) {
+
+					traverse(name + "." + n++, child, p);
+				}
+			}
+		} else {
+			for (int i = 0; i < children.getLength(); i++) {
+				Node child = children.item(i);
+				if (child instanceof Text) {
+					String value = child.getNodeValue().trim();
+					if (value.length()!=0) {
+						p.put(name, value);
+					}
+				} else {
+					traverse(name + "." + child.getNodeName(), child, p);
+				}
+			}
+		}
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/PomResource.java b/bundleplugin/src/main/java/aQute/bnd/maven/PomResource.java
new file mode 100644
index 0000000..e5e6b8d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/PomResource.java
@@ -0,0 +1,159 @@
+package aQute.bnd.maven;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+
+public class PomResource extends WriteResource {
+	final Manifest				manifest;
+	private Map<String, String>	scm;
+	final static Pattern		NAME_URL	= Pattern.compile("(.*)(http://.*)");
+
+	public PomResource(Manifest manifest) {
+		this.manifest = manifest;
+	}
+
+	@Override public long lastModified() {
+		return 0;
+	}
+
+	@Override public void write(OutputStream out) throws IOException {
+		PrintWriter ps = new PrintWriter(out);
+
+		String name = manifest.getMainAttributes().getValue(Analyzer.BUNDLE_NAME);
+
+		String description = manifest.getMainAttributes().getValue(Constants.BUNDLE_DESCRIPTION);
+		String docUrl = manifest.getMainAttributes().getValue(Constants.BUNDLE_DOCURL);
+		String version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+		String bundleVendor = manifest.getMainAttributes().getValue(Constants.BUNDLE_VENDOR);
+
+		String bsn = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+		String licenses = manifest.getMainAttributes().getValue(Constants.BUNDLE_LICENSE);
+
+		if (bsn == null) {
+			throw new RuntimeException("Cannot create POM unless bsn is set");
+		}
+
+		bsn = bsn.trim();
+		int n = bsn.lastIndexOf('.');
+		if (n <= 0)
+			throw new RuntimeException(
+					"Can not create POM unless Bundle-SymbolicName contains a . to separate group and  artifact id");
+
+		if (version == null)
+			version = "0";
+
+		String groupId = bsn.substring(0, n);
+		String artifactId = bsn.substring(n + 1);
+		n = artifactId.indexOf(';');
+		if (n > 0)
+			artifactId = artifactId.substring(0, n).trim();
+
+		Tag project = new Tag("project");
+		project.addAttribute("xmlns", "http://maven.apache.org/POM/4.0.0");
+		project.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+		project.addAttribute("xmlns:xsi", "");
+		project.addAttribute("xsi:schemaLocation",
+				"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd");
+
+		project.addContent(new Tag("modelVersion").addContent("4.0.0"));
+		project.addContent(new Tag("groupId").addContent(groupId));
+		project.addContent(new Tag("artifactId").addContent(artifactId));
+		project.addContent(new Tag("version").addContent(version));
+
+		if (description != null) {
+			new Tag(project, "description").addContent(description);
+		}
+		if (name != null) {
+			new Tag(project, "name").addContent(name);
+		}
+		if (docUrl != null) {
+			new Tag(project, "url").addContent(docUrl);
+		}
+
+		if (scm != null) {
+			Tag scm = new Tag(project, "scm");
+			for (Map.Entry<String, String> e : this.scm.entrySet()) {
+				new Tag(scm, e.getKey()).addContent(e.getValue());
+			}
+		}
+
+		if (bundleVendor != null) {
+			Matcher m = NAME_URL.matcher(bundleVendor);
+			String namePart = bundleVendor;
+			String urlPart = null;
+			if (m.matches()) {
+				namePart = m.group(1);
+				urlPart = m.group(2);
+			}
+			Tag organization = new Tag(project, "organization");
+			new Tag(organization, "name").addContent(namePart.trim());
+			if (urlPart != null) {
+				new Tag(organization, "url").addContent(urlPart.trim());
+			}
+		}
+		if (licenses != null) {
+			Tag ls = new Tag(project, "licenses");
+
+			Map<String, Map<String, String>> map = Processor.parseHeader(licenses, null);
+			for (Iterator<Map.Entry<String, Map<String, String>>> e = map.entrySet().iterator(); e
+					.hasNext();) {
+
+				// Bundle-License:
+				// http://www.opensource.org/licenses/apache2.0.php; \
+				// description="${Bundle-Copyright}"; \
+				// link=LICENSE
+				//
+				//  <license>
+				//    <name>This material is licensed under the Apache
+				// Software License, Version 2.0</name>
+				//    <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+				//    <distribution>repo</distribution>
+				//    </license>
+
+				Map.Entry<String, Map<String, String>> entry = e.next();
+				Tag l = new Tag(ls, "license");
+				Map<String, String> values = entry.getValue();
+				String url = entry.getKey();
+
+				if (values.containsKey("description"))
+					tagFromMap(l, values, "description", "name", url);
+				else
+					tagFromMap(l, values, "name", "name", url);
+
+				tagFromMap(l, values, "url", "url", url);
+				tagFromMap(l, values, "distribution", "distribution", "repo");
+			}
+		}
+		project.print(0, ps);
+		ps.flush();
+	}
+
+	/**
+	 * Utility function to print a tag from a map
+	 * 
+	 * @param ps
+	 * @param values
+	 * @param string
+	 * @param tag
+	 * @param object
+	 */
+	private Tag tagFromMap(Tag parent, Map<String, String> values, String string, String tag,
+			String object) {
+		String value = (String) values.get(string);
+		if (value == null)
+			value = object;
+		if (value == null)
+			return parent;
+		new Tag(parent, tag).addContent(value.trim());
+		return parent;
+	}
+
+	public void setProperties(Map<String, String> scm) {
+		this.scm = scm;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/CachedPom.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/CachedPom.java
new file mode 100644
index 0000000..02449fa
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/CachedPom.java
@@ -0,0 +1,18 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+
+public class CachedPom extends Pom {
+	final MavenEntry				maven;
+
+	CachedPom(MavenEntry mavenEntry, URI repo) throws Exception {
+		super(mavenEntry.maven, mavenEntry.getPomFile(), repo);
+		this.maven = mavenEntry;
+	}
+
+	public File getArtifact() throws Exception {
+		return maven.getArtifact();
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/Maven.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/Maven.java
new file mode 100644
index 0000000..2124a05
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/Maven.java
@@ -0,0 +1,88 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.regex.*;
+
+/*
+ http://repository.springsource.com/maven/bundles/external/org/apache/coyote/com.springsource.org.apache.coyote/6.0.24/com.springsource.org.apache.coyote-6.0.24.pom
+ http://repository.springsource.com/maven/bundles/external/org/apache/coyote/com.springsource.org.apache.coyote/6.0.24/com.springsource.org.apache.coyote-6.0.24.pom
+ */
+public class Maven {
+	final File						userHome	= new File(System.getProperty("user.home"));
+	final Map<String, MavenEntry>	entries		= new ConcurrentHashMap<String, MavenEntry>();
+	final static String[]			ALGORITHMS	= { "md5", "sha1" };
+	boolean							usecache	= false;
+	final Executor					executor;
+	File							m2			= new File(userHome, ".m2");
+	File							repository	= new File(m2, "repository");
+
+	public Maven(Executor executor) {
+		if ( executor == null)
+			this.executor = Executors.newCachedThreadPool();
+		else
+			this.executor = executor;
+	}
+	
+	//http://repo1.maven.org/maven2/junit/junit/maven-metadata.xml
+	
+	static Pattern MAVEN_RANGE = Pattern.compile("(\\[|\\()(.+)(,(.+))(\\]|\\))");
+	public CachedPom getPom(String groupId, String artifactId, String version, URI... extra)
+			throws Exception {		
+		MavenEntry entry = getEntry(groupId, artifactId, version);
+		return entry.getPom(extra);
+	}
+
+	/**
+	 * @param groupId
+	 * @param artifactId
+	 * @param version
+	 * @param extra
+	 * @return
+	 * @throws Exception
+	 */
+	public MavenEntry getEntry(String groupId, String artifactId, String version) throws Exception {
+		String path = path(groupId, artifactId, version);
+
+		MavenEntry entry;
+		synchronized (entries) {
+			entry = entries.get(path);
+			if (entry != null)
+				return entry;
+
+			entry = new MavenEntry(this, path);
+			entries.put(path, entry);
+		}
+		return entry;
+	}
+
+	private String path(String groupId, String artifactId, String version) {
+		return groupId.replace('.', '/') + '/' + artifactId + '/' + version + "/" + artifactId
+				+ "-" + version;
+	}
+
+	public void schedule(Runnable runnable) {
+		if (executor == null)
+			runnable.run();
+		else
+			executor.execute(runnable);
+	}
+
+	public ProjectPom createProjectModel(File file) throws Exception {
+		ProjectPom pom = new ProjectPom(this, file);
+		pom.parse();
+		return pom;
+	}
+
+	public MavenEntry getEntry(Pom pom) throws Exception {
+		return getEntry(pom.getGroupId(), pom.getArtifactId(), pom.getVersion());
+	}
+
+	public void setM2(File dir) {
+		this.m2 = dir;
+		this.repository = new File(dir,"repository");
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenEntry.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenEntry.java
new file mode 100644
index 0000000..f23ba82
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenEntry.java
@@ -0,0 +1,338 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import aQute.lib.hex.*;
+import aQute.lib.io.*;
+import aQute.libg.filelock.*;
+
+/**
+ * An entry (a group/artifact) in the maven cache in the .m2/repository
+ * directory. It provides methods to get the pom and the artifact.
+ * 
+ */
+public class MavenEntry implements Closeable {
+	final Maven					maven;
+	final File					root;
+	final File					dir;
+	final String				path;
+	final DirectoryLock			lock;
+	final Map<URI, CachedPom>	poms	= new HashMap<URI, CachedPom>();
+	final File					pomFile;
+	final File					artifactFile;
+	final String				pomPath;
+	final File					propertiesFile;
+	Properties					properties;
+	private boolean				propertiesChanged;
+	FutureTask<File>			artifact;
+	private String				artifactPath;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param maven
+	 * @param path
+	 */
+	MavenEntry(Maven maven, String path) {
+		this.root = maven.repository;
+		this.maven = maven;
+		this.path = path;
+		this.pomPath = path + ".pom";
+		this.artifactPath = path + ".jar";
+		this.dir = IO.getFile(maven.repository, path).getParentFile();
+		this.dir.mkdirs();
+		this.pomFile = new File(maven.repository, pomPath);
+		this.artifactFile = new File(maven.repository, artifactPath);
+		this.propertiesFile = new File(dir, "bnd.properties");
+		this.lock = new DirectoryLock(dir, 5 * 60000); // 5 mins
+	}
+
+	/**
+	 * This is the method to get the POM for a cached entry.
+	 * 
+	 * @param urls
+	 *            The allowed URLs
+	 * @return a CachedPom for this maven entry
+	 * 
+	 * @throws Exception
+	 *             If something goes haywire
+	 */
+	public CachedPom getPom(URI[] urls) throws Exception {
+
+		// First check if we have the pom cached in memory
+		synchronized (this) {
+			// Try to find it in the in-memory cache
+			for (URI url : urls) {
+				CachedPom pom = poms.get(url);
+				if (pom != null)
+					return pom;
+			}
+		}
+
+		// Ok, we need to see if it exists on disk
+
+		// lock.lock();
+		try {
+
+			if (isValid()) {
+				// Check if one of our repos had the correct file.
+				for (URI url : urls) {
+					String valid = getProperty(url.toASCIIString());
+					if (valid != null)
+						return createPom(url);
+				}
+
+				// we have the info, but have to verify that it
+				// exists in one of our repos but we do not have
+				// to download it as our cache is already ok.
+				for (URI url : urls) {
+					if (verify(url, pomPath)) {
+						return createPom(url);
+					}
+				}
+
+				// It does not exist in out repo
+				// so we have to fail even though we do have
+				// the file.
+
+			} else {
+				dir.mkdirs();
+				// We really do not have the file
+				// so we have to find out who has it.
+				for (final URI url : urls) {
+
+					if (download(url, pomPath)) {
+						if (verify(url, pomPath)) {
+							artifact = new FutureTask<File>(new Callable<File>() {
+
+								public File call() throws Exception {
+									if (download(url, artifactPath)) {
+										verify(url, artifactPath);
+									}
+									return artifactFile;
+								}
+
+							});
+							maven.executor.execute(artifact);
+							return createPom(url);
+						}
+					}
+				}
+			}
+			return null;
+		} finally {
+			saveProperties();
+			// lock.release();
+		}
+	}
+
+	/**
+	 * Download a resource from the given repo.
+	 * 
+	 * @param url
+	 *            The base url for the repo
+	 * @param path
+	 *            The path part
+	 * @return
+	 * @throws MalformedURLException
+	 */
+	private boolean download(URI repo, String path) throws MalformedURLException {
+		try {
+			URL url = toURL(repo, path);
+			System.out.println("Downloading "  + repo + " path " + path + " url " + url);
+			File file = new File(root, path);
+			IO.copy(url.openStream(), file);
+			System.out.println("Downloaded "  + url);
+			return true;
+		} catch (Exception e) {
+			System.err.println("debug: " + e);
+			return false;
+		}
+	}
+
+	/**
+	 * Converts a repo + path to a URL..
+	 * 
+	 * @param base
+	 *            The base repo
+	 * @param path
+	 *            The path in the directory + url
+	 * @return a URL that points to the file in the repo
+	 * 
+	 * @throws MalformedURLException
+	 */
+	URL toURL(URI base, String path) throws MalformedURLException {
+		StringBuilder r = new StringBuilder();
+		r.append(base.toString());
+		if (r.charAt(r.length() - 1) != '/')
+			r.append('/');
+		r.append(path);
+		return new URL(r.toString());
+	}
+
+	/**
+	 * Check if this is a valid cache directory, might probably need some more
+	 * stuff.
+	 * 
+	 * @return true if valid
+	 */
+	private boolean isValid() {
+		return pomFile.isFile() && pomFile.length() > 100 && artifactFile.isFile()
+				&& artifactFile.length() > 100;
+	}
+
+	/**
+	 * We maintain a set of bnd properties in the cache directory.
+	 * 
+	 * @param key
+	 *            The key for the property
+	 * @param value
+	 *            The value for the property
+	 */
+	private void setProperty(String key, String value) {
+		Properties properties = getProperties();
+		properties.setProperty(key, value);
+		propertiesChanged = true;
+	}
+
+	/**
+	 * Answer the properties, loading if needed.
+	 */
+	protected Properties getProperties() {
+		if (properties == null) {
+			properties = new Properties();
+			File props = new File(dir, "bnd.properties");
+			if (props.exists()) {
+				try {
+					FileInputStream in = new FileInputStream(props);
+					properties.load(in);
+				} catch (Exception e) {
+					// we ignore for now, will handle it on safe
+				}
+			}
+		}
+		return properties;
+	}
+
+	/**
+	 * Answer a property.
+	 * 
+	 * @param key
+	 *            The key
+	 * @return The value
+	 */
+	private String getProperty(String key) {
+		Properties properties = getProperties();
+		return properties.getProperty(key);
+	}
+
+	private void saveProperties() throws IOException {
+		if (propertiesChanged) {
+			FileOutputStream fout = new FileOutputStream(propertiesFile);
+			try {
+				properties.store(fout, "");
+			} finally {
+				properties = null;
+				propertiesChanged = false;
+				fout.close();
+			}
+		}
+	}
+
+	/**
+	 * Help function to create the POM and record its source.
+	 * 
+	 * @param url
+	 *            the repo from which it was constructed
+	 * @return the new pom
+	 * @throws Exception
+	 */
+	private CachedPom createPom(URI url) throws Exception {
+		CachedPom pom = new CachedPom(this, url);
+		pom.parse();
+		poms.put(url, pom);
+		setProperty(url.toASCIIString(), "true");
+		return pom;
+	}
+
+	/**
+	 * Verify that the repo has a checksum file for the given path and that this
+	 * checksum matchs.
+	 * 
+	 * @param repo
+	 *            The repo
+	 * @param path
+	 *            The file id
+	 * @return true if there is a digest and it matches one of the algorithms
+	 * @throws Exception
+	 */
+	boolean verify(URI repo, String path) throws Exception {
+		for (String algorithm : Maven.ALGORITHMS) {
+			if (verify(repo, path, algorithm))
+				return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Verify the path against its digest for the given algorithm.
+	 * 
+	 * @param repo
+	 * @param path
+	 * @param algorithm
+	 * @return
+	 * @throws Exception
+	 */
+	private boolean verify(URI repo, String path, String algorithm) throws Exception {
+		String digestPath = path + "." + algorithm;
+		File actualFile = new File(root, path);
+
+		if (download(repo, digestPath)) {
+			File digestFile = new File(root, digestPath);
+			final MessageDigest md = MessageDigest.getInstance(algorithm);
+			IO.copy(actualFile, new OutputStream() {
+				@Override public void write(int c) throws IOException {
+					md.update((byte) c);
+				}
+
+				@Override public void write(byte[] buffer, int offset, int length) {
+					md.update(buffer, offset, length);
+				}
+			});
+			byte[] digest = md.digest();
+			String source = IO.collect(digestFile).toUpperCase();
+			String hex = Hex.toHexString(digest).toUpperCase();
+			if (source.startsWith(hex)) {
+				System.out.println("Verified ok " + actualFile + " digest " + algorithm);
+				return true;
+			}
+		}
+		System.out.println("Failed to verify " + actualFile + " for digest " + algorithm);
+		return false;
+	}
+
+	public File getArtifact() throws Exception {
+		if (artifact == null )
+			return artifactFile;
+		return artifact.get();
+	}
+
+	public File getPomFile() {
+		return pomFile;
+	}
+
+	public void close() throws IOException {
+
+	}
+
+	public void remove() {
+		if (dir.getParentFile() != null) {
+			IO.delete(dir);
+		}
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
new file mode 100644
index 0000000..bbe2411
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/MavenRemoteRepository.java
@@ -0,0 +1,130 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+public class MavenRemoteRepository implements RepositoryPlugin, RegistryPlugin, Plugin {
+	Reporter	reporter;
+	URI[]		repositories;
+	Registry	registry;
+	Maven		maven;
+
+	public File[] get(String bsn, String range) throws Exception {
+		File f = get(bsn, range, Strategy.HIGHEST, null);
+		if (f == null)
+			return null;
+
+		return new File[] { f };
+	}
+
+	public File get(String bsn, String version, Strategy strategy, Map<String, String> properties)
+			throws Exception {
+		String groupId = null;
+		
+		if (properties != null)
+			groupId = properties.get("groupId");
+		
+		if (groupId == null) {
+			int n = bsn.indexOf('+');
+			if ( n < 0)
+				return null;
+			
+			groupId = bsn.substring(0,n);
+			bsn = bsn.substring(n+1);
+		}
+
+		String artifactId = bsn;
+
+		if (version == null) {
+			if (reporter != null)
+				reporter.error("Maven dependency version not set for %s - %s", groupId, artifactId);
+			return null;
+		}
+
+		CachedPom pom = getMaven().getPom(groupId, artifactId, version, repositories);
+
+		String value = properties == null ? null : properties.get("scope");
+		if (value == null)
+			return pom.getArtifact();
+
+		Pom.Scope action = null;
+
+		try {
+			action = Pom.Scope.valueOf(value);
+			return pom.getLibrary(action, repositories);
+		} catch (Exception e) {
+			return pom.getArtifact();
+		}
+	}
+
+	public Maven getMaven() {
+		if ( maven != null)
+			return maven;
+		
+		maven = registry.getPlugin(Maven.class);
+		return maven;
+	}
+
+	public boolean canWrite() {
+		return false;
+	}
+
+	public File put(Jar jar) throws Exception {
+		throw new UnsupportedOperationException("cannot do put");
+	}
+
+	public List<String> list(String regex) throws Exception {
+		throw new UnsupportedOperationException("cannot do list");
+	}
+
+	public List<Version> versions(String bsn) throws Exception {
+		throw new UnsupportedOperationException("cannot do versions");
+	}
+
+	public String getName() {
+		return "maven";
+	}
+
+	public void setRepositories(URI... urls) {
+		repositories = urls;
+	}
+
+	public void setProperties(Map<String, String> map) {
+		String repoString = map.get("repositories");
+		if (repoString != null) {
+			String[] repos = repoString.split("\\s*,\\s*");
+			repositories = new URI[repos.length];
+			int n = 0;
+			for (String repo : repos) {
+				try {
+					URI uri = new URI(repo);
+					if ( !uri.isAbsolute())
+						uri = IO.getFile( new File(""),repo).toURI();
+					repositories[n++] = uri;
+				} catch (Exception e) {
+					if (reporter != null)
+						reporter.error("Invalid repository %s for maven plugin, %s", repo, e);
+				}
+			}
+		}
+	}
+
+	public void setReporter(Reporter reporter) {
+		this.reporter = reporter;
+	}
+
+	public void setRegistry(Registry registry) {
+		this.registry = registry;
+	}
+
+	public void setMaven(Maven maven) {
+		this.maven = maven;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/Pom.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/Pom.java
new file mode 100644
index 0000000..2b7ab46
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/Pom.java
@@ -0,0 +1,350 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import javax.xml.parsers.*;
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+public abstract class Pom {
+	static DocumentBuilderFactory	dbf	= DocumentBuilderFactory.newInstance();
+	static XPathFactory				xpf	= XPathFactory.newInstance();
+
+	static {
+		dbf.setNamespaceAware(false);
+	}
+	
+	public enum Scope {
+		compile, runtime, system, import_, provided, test, ;
+		
+		private boolean includes(Scope other) {
+			if (other == this) return true;
+			switch (this) {
+			case compile:
+				return other == provided || other == test;
+			default:
+				return false;
+			}
+		}
+	};
+
+	final Maven			maven;
+	final URI			home;
+
+	String				groupId;
+	String				artifactId;
+	String				version;
+	List<Dependency>	dependencies	= new ArrayList<Dependency>();
+	Exception			exception;
+	File				pomFile;
+	String				description="";
+	String				name;
+
+	public String getDescription() {
+		return description;
+	}
+
+	public class Dependency {
+		Scope		scope;
+		String		type;
+		boolean		optional;
+		String		groupId;
+		String		artifactId;
+		String		version;
+		Set<String>	exclusions	= new HashSet<String>();
+
+		public Scope getScope() {
+			return scope;
+		}
+
+		public String getType() {
+			return type;
+		}
+
+		public boolean isOptional() {
+			return optional;
+		}
+
+		public String getGroupId() {
+			return replace(groupId);
+		}
+
+		public String getArtifactId() {
+			return replace(artifactId);
+		}
+
+		public String getVersion() {
+			return replace(version);
+		}
+
+		public Set<String> getExclusions() {
+			return exclusions;
+		}
+
+		public Pom getPom() throws Exception {
+			return maven.getPom(groupId, artifactId, version);
+		}
+		@Override
+		public String toString() {
+			StringBuilder builder = new StringBuilder();
+			builder.append("Dependency [");
+			if (groupId != null)
+				builder.append("groupId=").append(groupId).append(", ");
+			if (artifactId != null)
+				builder.append("artifactId=").append(artifactId).append(", ");
+			if (version != null)
+				builder.append("version=").append(version).append(", ");
+			if (type != null)
+				builder.append("type=").append(type).append(", ");
+			if (scope != null)
+				builder.append("scope=").append(scope).append(", ");
+			builder.append("optional=").append(optional).append(", ");
+			if (exclusions != null)
+				builder.append("exclusions=").append(exclusions);
+			builder.append("]");
+			return builder.toString();
+		}
+	}
+
+	public Pom(Maven maven, File pomFile, URI home) throws Exception {
+		this.maven = maven;
+		this.home = home;
+		this.pomFile = pomFile;
+	}
+
+	void parse() throws Exception {
+		DocumentBuilder db = dbf.newDocumentBuilder();
+		System.out.println("Parsing " + pomFile.getAbsolutePath());
+		Document doc = db.parse(pomFile);
+		XPath xp = xpf.newXPath();
+		parse(doc, xp);
+	}
+
+	protected void parse(Document doc, XPath xp) throws XPathExpressionException, Exception {
+
+		this.artifactId = replace(xp.evaluate("project/artifactId", doc).trim(), this.artifactId);
+		this.groupId = replace(xp.evaluate("project/groupId", doc).trim(), this.groupId);
+		this.version = replace(xp.evaluate("project/version", doc).trim(), this.version);
+		
+		String nextDescription = xp.evaluate("project/description", doc).trim();
+		if ( this.description.length() != 0 && nextDescription.length() != 0)
+			this.description += "\n";
+		this.description += replace(nextDescription);
+		
+		this.name = replace(xp.evaluate("project/name", doc).trim(), this.name);
+
+		NodeList list = (NodeList) xp.evaluate("project/dependencies/dependency", doc,
+				XPathConstants.NODESET);
+		for (int i = 0; i < list.getLength(); i++) {
+			Node node = list.item(i);
+			Dependency dep = new Dependency();
+			String scope = xp.evaluate("scope", node).trim();
+			if (scope.length() == 0)
+				dep.scope = Scope.compile;
+			else
+				dep.scope = Scope.valueOf(scope);
+			dep.type = xp.evaluate("type", node).trim();
+
+			String opt = xp.evaluate("optional", node).trim();
+			dep.optional = opt != null && opt.equalsIgnoreCase("true");
+			dep.groupId = replace(xp.evaluate("groupId", node));
+			dep.artifactId = replace(xp.evaluate("artifactId", node).trim());
+
+			dep.version = replace(xp.evaluate("version", node).trim());
+			dependencies.add(dep);
+
+			NodeList exclusions = (NodeList) xp
+					.evaluate("exclusions", node, XPathConstants.NODESET);
+			for (int e = 0; e < exclusions.getLength(); e++) {
+				Node exc = exclusions.item(e);
+				String exclGroupId = xp.evaluate("groupId", exc).trim();
+				String exclArtifactId = xp.evaluate("artifactId", exc).trim();
+				dep.exclusions.add(exclGroupId + "+" + exclArtifactId);
+			}
+		}
+
+	}
+
+	private String replace(String key, String dflt) {
+		if ( key == null || key.length() == 0)
+			return dflt;
+		
+		return replace(key);
+	}
+
+	public String getArtifactId() throws Exception {
+		return replace(artifactId);
+	}
+
+	public String getGroupId() throws Exception {
+		return replace(groupId);
+	}
+
+	public String getVersion() throws Exception {
+		if ( version == null)
+			return "<not set>";
+		return replace(version);
+	}
+
+	public List<Dependency> getDependencies() throws Exception {
+		return dependencies;
+	}
+
+	class Rover {
+
+		public Rover(Rover rover, Dependency d) {
+			this.previous = rover;
+			this.dependency = d;
+		}
+
+		final Rover			previous;
+		final Dependency	dependency;
+
+		public boolean excludes(String name) {
+			return dependency.exclusions.contains(name) && previous != null
+					&& previous.excludes(name);
+		}
+	}
+
+	public Set<Pom> getDependencies(Scope scope, URI... urls) throws Exception {
+		Set<Pom> result = new LinkedHashSet<Pom>();
+
+		List<Rover> queue = new ArrayList<Rover>();
+		for (Dependency d : dependencies) {
+			queue.add(new Rover(null, d));
+		}
+
+		while (!queue.isEmpty()) {
+			Rover rover = queue.remove(0);
+			Dependency dep = rover.dependency;
+			String groupId = replace(dep.groupId);
+			String artifactId = replace(dep.artifactId);
+			String version = replace(dep.version);
+
+			String name = groupId + "+" + artifactId;
+
+			if (rover.excludes(name) || dep.optional)
+				continue;
+			
+			if (dep.scope == scope && !dep.optional) {
+				try {
+					Pom sub = maven.getPom(groupId, artifactId, version, urls);
+					if (sub != null) {
+						if (!result.contains(sub)) {
+							result.add(sub);
+							for (Dependency subd : sub.dependencies) {
+								queue.add(new Rover(rover, subd));
+							}
+						}
+					} else
+						if (rover.previous != null)
+							System.out.println("Cannot find " + dep + " from "
+									+ rover.previous.dependency);
+						else
+							System.out.println("Cannot find " + dep + " from top");
+				} catch (Exception e) {
+					if (rover.previous != null)
+						System.out.println("Cannot find " + dep + " from "
+								+ rover.previous.dependency);
+					else
+						System.out.println("Cannot find " + dep + " from top");
+
+//			boolean include = false;
+//			if (dep.scope == Scope.compile) {
+//				include = true;
+//			} else if (dep.scope == Scope.test) {
+//				include = rover.previous == null && (action == Action.compile || action == Action.test);
+//			} else if (dep.scope == Scope.runtime) {
+//				include = action == Action.run;
+//			}
+//			if (include) {
+//				Pom sub = maven.getPom(groupId, artifactId, version, urls);
+//				if (!result.contains(sub)) {
+//					result.add(sub);
+//					for (Dependency subd : sub.dependencies) {
+//						queue.add(new Rover(rover, subd));
+//					}
+					
+				}
+			}
+		}
+		return result;
+	}
+
+	protected String replace(String in) {
+		System.out.println("replace: " + in);
+		if (in == null)
+			return "null";
+
+		in = in.trim();
+		if ("${pom.version}".equals(in) || "${version}".equals(in)
+				|| "${project.version}".equals(in))
+			return version;
+
+		if ("${basedir}".equals(in))
+			return pomFile.getParentFile().getAbsolutePath();
+
+		if ("${pom.name}".equals(in) || "${project.name}".equals(in))
+			return name;
+
+		if ("${pom.artifactId}".equals(in) || "${project.artifactId}".equals(in))
+			return artifactId;
+		if ("${pom.groupId}".equals(in) || "${project.groupId}".equals(in))
+			return groupId;
+
+		return in;
+	}
+
+	public String toString() {
+		return groupId + "+" + artifactId + "-" + version;
+	}
+
+	public File getLibrary(Scope action, URI... repositories) throws Exception {
+		MavenEntry entry = maven.getEntry(this);
+		File file = new File(entry.dir, action + ".lib");
+
+		if (file.isFile() && file.lastModified() >= getPomFile().lastModified())
+			return file;
+
+		file.delete();
+
+		Writer writer = new FileWriter(file);
+		doEntry(writer, this);
+		try {
+			for (Pom dep : getDependencies(action, repositories)) {
+				doEntry(writer, dep);
+			}
+		} finally {
+			writer.close();
+		}
+		return file;
+	}
+
+	/**
+	 * @param writer
+	 * @param dep
+	 * @throws IOException
+	 * @throws Exception
+	 */
+	private void doEntry(Writer writer, Pom dep) throws IOException, Exception {
+		writer.append(dep.getGroupId());
+		writer.append("+");
+		writer.append(dep.getArtifactId());
+		writer.append(";version=\"");
+		writer.append(dep.getVersion());
+		writer.append("\"\n");
+	}
+
+	public File getPomFile() {
+		return pomFile;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public abstract java.io.File getArtifact() throws Exception;
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/ProjectPom.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/ProjectPom.java
new file mode 100644
index 0000000..8187954
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/ProjectPom.java
@@ -0,0 +1,204 @@
+package aQute.bnd.maven.support;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.xml.xpath.*;
+
+import org.w3c.dom.*;
+
+import aQute.lib.io.*;
+
+public class ProjectPom extends Pom {
+
+	final List<URI>	repositories	= new ArrayList<URI>();
+	final Properties	properties		= new Properties();
+	String 		packaging;
+	String 		url;
+	
+	ProjectPom(Maven maven, File pomFile) throws Exception {
+		super(maven, pomFile, pomFile.toURI());
+	}
+
+	@Override protected void parse(Document doc, XPath xp) throws Exception {
+
+		packaging = xp.evaluate("project/packaging", doc);
+		url = xp.evaluate("project/url", doc);
+		
+		Node parent = (Node) xp.evaluate("project/parent", doc, XPathConstants.NODE);
+		if (parent != null && parent.hasChildNodes()) {
+			File parentFile = IO.getFile(getPomFile().getParentFile(), "../pom.xml");
+
+			String parentGroupId = xp.evaluate("groupId", parent).trim();
+			String parentArtifactId = xp.evaluate("artifactId", parent).trim();
+			String parentVersion = xp.evaluate("version", parent).trim();
+			String parentPath = xp.evaluate("relativePath", parent).trim();
+			if (parentPath != null && parentPath.length()!=0) {
+				parentFile = IO.getFile(getPomFile().getParentFile(), parentPath);
+			}
+			if (parentFile.isFile()) {
+				ProjectPom parentPom = new ProjectPom(maven, parentFile);
+				parentPom.parse();
+				dependencies.addAll(parentPom.dependencies);
+				for ( Enumeration<?> e = parentPom.properties.propertyNames(); e.hasMoreElements(); ) {
+					String key = (String) e.nextElement();
+					if ( ! properties.contains(key))
+						properties.put(key, parentPom.properties.get(key));
+				}
+				repositories.addAll(parentPom.repositories);
+				
+				setNames(parentPom);
+			} else {
+				// This seems to be a bit bizarre, extending an external pom?
+				CachedPom parentPom = maven.getPom(parentGroupId, parentArtifactId, parentVersion);
+				dependencies.addAll(parentPom.dependencies);
+				setNames(parentPom);
+			}
+		}
+
+		NodeList propNodes = (NodeList) xp.evaluate("project/properties/*", doc,
+				XPathConstants.NODESET);
+		for (int i = 0; i < propNodes.getLength(); i++) {
+			Node node = propNodes.item(i);
+			String key = node.getNodeName();
+			String value = node.getTextContent();
+			if ( key == null || key.length()==0)
+				throw new IllegalArgumentException("Pom has an empty or null key");
+			if ( value == null || value.length()==0)
+				throw new IllegalArgumentException("Pom has an empty or null value for property " + key);
+			properties.setProperty(key, value.trim());
+		}
+
+		NodeList repos = (NodeList) xp.evaluate("project/repositories/repository/url", doc,
+				XPathConstants.NODESET);
+		for (int i = 0; i < repos.getLength(); i++) {
+			Node node = repos.item(i);
+			String URIString = node.getTextContent().trim();
+			URI uri = new URI(URIString);
+			if ( uri.getScheme() ==null )
+				uri = IO.getFile(pomFile.getParentFile(),URIString).toURI();
+			repositories.add(uri);
+		}
+
+		super.parse(doc, xp);
+	}
+
+//	private void print(Node node, String indent) {
+//		System.out.print(indent);
+//		System.out.println(node.getNodeName());
+//		Node rover = node.getFirstChild();
+//		while ( rover != null) {
+//			print( rover, indent+" ");
+//			rover = rover.getNextSibling();
+//		}
+//	}
+
+	/**
+	 * @param parentArtifactId
+	 * @param parentGroupId
+	 * @param parentVersion
+	 * @throws Exception
+	 */
+	private void setNames(Pom pom) throws Exception {
+		if (artifactId == null || artifactId.length()==0)
+			artifactId = pom.getArtifactId();
+		if (groupId == null || groupId.length()==0)
+			groupId = pom.getGroupId();
+		if (version == null || version.length()==0)
+			version = pom.getVersion();
+		if ( description == null )
+			description = pom.getDescription();
+		else
+			description = pom.getDescription() + "\n" + description;
+	
+	}
+
+	class Rover {
+
+		public Rover(Rover rover, Dependency d) {
+			this.previous = rover;
+			this.dependency = d;
+		}
+
+		final Rover			previous;
+		final Dependency	dependency;
+
+		public boolean excludes(String name) {
+			return dependency.exclusions.contains(name) && previous != null
+					&& previous.excludes(name);
+		}
+	}
+
+	public Set<Pom> getDependencies(Scope action) throws Exception {
+		return getDependencies(action, repositories.toArray(new URI[0]));
+	}
+
+	// Match any macros
+	final static Pattern	MACRO	= Pattern.compile("(\\$\\{\\s*([^}\\s]+)\\s*\\})");
+
+	protected String replace(String in) {
+		System.out.println("Replce: " + in);
+		if ( in == null) {
+			System.out.println("null??");
+		}
+		Matcher matcher = MACRO.matcher(in);
+		int last = 0;
+		StringBuilder sb = new StringBuilder();
+		while (matcher.find()) {
+			int n = matcher.start();
+			sb.append( in, last, n);
+			String replacement = get(matcher.group(2));
+			if ( replacement == null )
+				sb.append( matcher.group(1));
+			else
+				sb.append( replacement );
+			last = matcher.end();
+		}
+		if ( last == 0)
+			return in;
+		
+		sb.append( in, last, in.length());
+		return sb.toString();
+	}
+
+	private String get(String key) {
+		if (key.equals("pom.artifactId"))
+			return artifactId;
+		if (key.equals("pom.groupId"))
+			return groupId;
+		if (key.equals("pom.version"))
+			return version;
+		
+		if (key.equals("pom.name"))
+			return name;
+		
+		String prop = properties.getProperty(key);
+		if ( prop != null )
+			return prop;
+		
+		return System.getProperty(key);
+	}
+
+	public Properties getProperties() {
+		return properties;
+	}
+
+	public String getPackaging() {
+		return packaging;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public String getProperty(String key) {
+		String s = properties.getProperty(key);
+		return replace(s);
+	}
+
+	@Override public File getArtifact() throws Exception {
+		return null;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/maven/support/Repo.java b/bundleplugin/src/main/java/aQute/bnd/maven/support/Repo.java
new file mode 100644
index 0000000..dd1939f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/maven/support/Repo.java
@@ -0,0 +1,5 @@
+package aQute.bnd.maven.support;
+
+public class Repo {
+
+}