FELIX-491: initial copy of maven-obr-plugin files into bundleplugin (preserving history)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@628500 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/AbstractFileMojo.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/AbstractFileMojo.java
new file mode 100644
index 0000000..ddd6c1d
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/AbstractFileMojo.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.factory.ArtifactFactory;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+
+
+/**
+ * Base class for the command-line install-file and deploy-file goals.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class AbstractFileMojo extends AbstractMojo
+{
+    /**
+     * GroupId of the bundle. Retrieved from POM file if specified.
+     *
+     * @parameter expression="${groupId}"
+     */
+    private String groupId;
+
+    /**
+     * ArtifactId of the bundle. Retrieved from POM file if specified.
+     *
+     * @parameter expression="${artifactId}"
+     */
+    private String artifactId;
+
+    /**
+     * Version of the bundle. Retrieved from POM file if specified.
+     *
+     * @parameter expression="${version}"
+     */
+    private String version;
+
+    /**
+     * Packaging type of the bundle. Retrieved from POM file if specified.
+     *
+     * @parameter expression="${packaging}"
+     */
+    private String packaging;
+
+    /**
+     * Classifier type of the bundle. Defaults to none.
+     *
+     * @parameter expression="${classifier}"
+     */
+    private String classifier;
+
+    /**
+     * Location of an existing POM file.
+     *
+     * @parameter expression="${pomFile}"
+     */
+    private File pomFile;
+
+    /**
+     * Bundle file, defaults to the artifact in the local Maven repository.
+     *
+     * @parameter expression="${file}"
+     */
+    protected File file;
+
+    /**
+     * Optional XML file describing additional requirements and capabilities.
+     * 
+     * @parameter expression="${obrXml}"
+     */
+    protected String obrXml;
+
+    /**
+     * Component factory for Maven artifacts
+     * 
+     * @component
+     */
+    private ArtifactFactory m_factory;
+
+
+    /**
+     * @return project based on command-line settings, with bundle attached
+     * @throws MojoExecutionException
+     */
+    public MavenProject getProject() throws MojoExecutionException
+    {
+        final MavenProject project;
+        if ( pomFile != null && pomFile.exists() )
+        {
+            project = PomHelper.readPom( pomFile );
+
+            groupId = project.getGroupId();
+            artifactId = project.getArtifactId();
+            version = project.getVersion();
+            packaging = project.getPackaging();
+        }
+        else
+        {
+            project = PomHelper.buildPom( groupId, artifactId, version, packaging );
+        }
+
+        if ( groupId == null || artifactId == null || version == null || packaging == null )
+        {
+            throw new MojoExecutionException( "Missing group, artifact, version, or packaging information" );
+        }
+
+        Artifact bundle = m_factory.createArtifactWithClassifier( groupId, artifactId, version, packaging, classifier );
+        project.setArtifact( bundle );
+
+        return project;
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Capability.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Capability.java
new file mode 100644
index 0000000..4bf0f76
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Capability.java
@@ -0,0 +1,123 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * This class describe and store capability node.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Capability
+{
+
+    /**
+     * m_name: name of the capability.
+     */
+    private String m_name;
+
+    /**
+     * m_p: List of PElement.
+     */
+    private List m_p = new ArrayList();
+
+
+    /**
+     * get the name attribute.
+     * 
+     * @return name attribute
+     */
+    public String getName()
+    {
+        return m_name;
+    }
+
+
+    /**
+     * set the name attribute.
+     * 
+     * @param name new name value
+     *            
+     */
+    public void setName( String name )
+    {
+        m_name = name;
+    }
+
+
+    /**
+     * return the capabilities.
+     * 
+     * @return List of PElement
+     */
+    public List getP()
+    {
+        return m_p;
+    }
+
+
+    /**
+     * set the capabilities.
+     * 
+     * @param mp List of PElement
+     *            
+     */
+    public void setP( List mp )
+    {
+        m_p = mp;
+    }
+
+
+    /**
+     * add one element in List.
+     * 
+     * @param pelement PElement
+     *            
+     */
+    public void addP( PElement pelement )
+    {
+        m_p.add( pelement );
+    }
+
+
+    /**
+     * transform this object to Node.
+     * 
+     * @param father father document for create Node
+     * @return node
+     */
+    public Node getNode( Document father )
+    {
+        Element capability = father.createElement( "capability" );
+        capability.setAttribute( "name", getName() );
+        for ( int i = 0; i < getP().size(); i++ )
+        {
+            capability.appendChild( ( ( PElement ) ( getP().get( i ) ) ).getNode( father ) );
+        }
+        return capability;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Category.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Category.java
new file mode 100644
index 0000000..75086da
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Category.java
@@ -0,0 +1,73 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * describe and store category node.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+
+public class Category
+{
+    /**
+     * id of the category.
+     */
+    private String m_id;
+
+
+    /**
+     * get the id attribute.
+     * 
+     * @return id
+     */
+    public String getId()
+    {
+        return m_id;
+    }
+
+
+    /**
+     * set the id attribute.
+     * @param id new id value
+     */
+    public void setId( String id )
+    {
+        m_id = id;
+    }
+
+
+    /**
+     * transform this object to node.
+     * @param father father document for create Node
+     * @return node
+     */
+    public Node getNode( Document father )
+    {
+        Element category = father.createElement( "category" );
+        category.setAttribute( "id", getId() );
+        return category;
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Config.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Config.java
new file mode 100644
index 0000000..c2d12c7
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Config.java
@@ -0,0 +1,101 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.net.URI;
+
+
+/**
+ * this class is used to store some user information about configuration of the plugin.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ *
+ */
+public class Config
+{
+    private boolean m_pathRelative; // use relative or absolute path in repository.xml
+    private boolean m_remoteFile; // deploy file on remote server
+    private URI m_remoteBundle; // public address of deployed bundle
+
+
+    /**
+     * constructor: set default configuration: use relative path and don't upload file.
+     */
+    public Config()
+    {
+        // default configuration
+        m_pathRelative = true;
+        m_remoteFile = false;
+        m_remoteBundle = null;
+    }
+
+
+    /**
+     * @param value enable to use relative path
+     */
+    public void setPathRelative( boolean value )
+    {
+        m_pathRelative = value;
+    }
+
+
+    /**
+     * @param value enable when uploading
+     */
+    public void setRemoteFile( boolean value )
+    {
+        m_remoteFile = value;
+    }
+
+
+    /**
+     * @param value public address of deployed bundle
+     */
+    public void setRemoteBundle( URI value )
+    {
+        m_remoteBundle = value;
+    }
+
+
+    /**
+     * @return true if plugin uses relative path, else false
+     */
+    public boolean isPathRelative()
+    {
+        return m_pathRelative;
+    }
+
+
+    /**
+     * @return true if the file will be uploaded, else false
+     */
+    public boolean isRemoteFile()
+    {
+        return m_remoteFile;
+    }
+
+
+    /**
+     * @return public address of deployed bundle
+     */
+    public URI getRemoteBundle()
+    {
+        return m_remoteBundle;
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ExtractBindexInfo.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ExtractBindexInfo.java
new file mode 100644
index 0000000..86aee85
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ExtractBindexInfo.java
@@ -0,0 +1,299 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.osgi.impl.bundle.obr.resource.BundleInfo;
+import org.osgi.impl.bundle.obr.resource.CapabilityImpl;
+import org.osgi.impl.bundle.obr.resource.RepositoryImpl;
+import org.osgi.impl.bundle.obr.resource.RequirementImpl;
+import org.osgi.impl.bundle.obr.resource.ResourceImpl;
+import org.osgi.impl.bundle.obr.resource.VersionRange;
+
+
+/**
+ * this class is used to configure bindex and get information built by bindex about targeted bundle.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ExtractBindexInfo
+{
+
+    /**
+     * attribute get from bindex which describe targeted resource.
+     */
+    private ResourceImpl m_resource;
+
+
+    /**
+     * configure bindex and build information.
+     * @param repoFilename URI on OBR descriptor file
+     * @param outFile path on targeted jar-file
+     * @throws MojoExecutionException occurs if bindex configuration failed
+     */
+    public ExtractBindexInfo( URI repoFilename, String outFile ) throws MojoExecutionException
+    {
+
+        m_resource = null;
+        RepositoryImpl repository = null;
+        try
+        {
+            repository = new RepositoryImpl( new File( repoFilename ).getAbsoluteFile().toURL() );
+        }
+        catch ( MalformedURLException e )
+        {
+            e.printStackTrace();
+            throw new MojoExecutionException( "MalformedURLException" );
+        }
+        BundleInfo info = null;
+        try
+        {
+            info = new BundleInfo( repository, new File( outFile ) );
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            throw new MojoExecutionException( "Exception" );
+        }
+
+        try
+        {
+            m_resource = info.build();
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            throw new MojoExecutionException( "Exception" );
+        }
+    }
+
+
+    /**
+     * extract capabilities from bindex information.
+     * @return bundle capabilities List
+     */
+    public List getCapabilities()
+    {
+        List list = new ArrayList();
+        Collection res = m_resource.getCapabilityList();
+        Iterator it = res.iterator();
+        while ( it.hasNext() )
+        {
+            Capability capability = new Capability();
+            CapabilityImpl ci = ( CapabilityImpl ) it.next();
+            capability.setName( ci.getName() );
+            // System.out.println(ci.getName()) ;
+            if ( !( ci.getName().compareTo( "bundle" ) == 0 ) )
+            {
+                Map properties = ci.getProperties();
+                for ( Iterator e = properties.entrySet().iterator(); e.hasNext(); )
+                {
+                    PElement p = new PElement();
+                    Map.Entry entry = ( Map.Entry ) e.next();
+                    String key = ( String ) entry.getKey();
+                    List values = ( List ) entry.getValue();
+                    for ( Iterator v = values.iterator(); v.hasNext(); )
+                    {
+                        Object value = v.next();
+                        p.setN( key );
+                        if ( value != null )
+                        {
+                            p.setV( value.toString() );
+                        }
+                        else
+                        {
+                            System.out.println( "Missing value " + key );
+                        }
+                        String type = null;
+                        if ( value instanceof Number )
+                        {
+                            type = "number";
+                        }
+                        else
+                        {
+                            if ( value instanceof VersionRange )
+                            {
+                                type = "version";
+                            }
+                        }
+                        if ( type != null )
+                        {
+                            p.setT( type );
+                        }
+                    }
+                    capability.addP( p );
+                }
+
+                list.add( capability );
+            }
+        }
+        return list;
+    }
+
+
+    /**
+     * extract requirement from bindex information.
+     * @return bundle requirement List
+     */
+    public List getRequirement()
+    {
+        List list = new ArrayList();
+        Collection res = m_resource.getRequirementList();
+        Iterator it = res.iterator();
+        while ( it.hasNext() )
+        {
+            RequirementImpl ci = ( RequirementImpl ) it.next();
+            Require require = new Require();
+
+            require.setExtend( String.valueOf( ci.isExtend() ) );
+            require.setMultiple( String.valueOf( ci.isMultiple() ) );
+            require.setOptional( String.valueOf( ci.isOptional() ) );
+            require.setName( ci.getName() );
+            require.setFilter( ci.getFilter() );
+            require.setValue( ci.getComment() );
+            list.add( require );
+        }
+        return list;
+    }
+
+
+    /**
+     * extract symbolic name from bindex information.
+     * @return bundle symbolic name
+     */
+    public String getSymbolicName()
+    {
+        return m_resource.getSymbolicName();
+    }
+
+
+    /**
+     * extract version from bindex information.
+     * @return bundle version
+     */
+    public String getVersion()
+    {
+        if ( m_resource.getVersion() != null )
+        {
+            return m_resource.getVersion().toString();
+        }
+
+        return null;
+    }
+
+
+    /**
+     * extract presentation name from bindex information.
+     * @return bundle presentation name
+     */
+    public String getPresentationName()
+    {
+        return m_resource.getPresentationName();
+    }
+
+
+    /**
+     * extract copyright from bindex information.
+     * @return bundle copyright
+     */
+    public String getCopyright()
+    {
+        return m_resource.getCopyright();
+    }
+
+
+    /**
+     * extract description from bindex information.
+     * @return bundle description
+     */
+    public String getDescription()
+    {
+        return m_resource.getDescription();
+    }
+
+
+    /**
+     * extract documentation from bindex information.
+     * @return bundle documentation
+     */
+    public String getDocumentation()
+    {
+        if ( m_resource.getDocumentation() != null )
+        {
+            return m_resource.getDocumentation().toString();
+        }
+
+        return null;
+    }
+
+
+    /**
+     * extract license from bindex information.
+     * @return bundle license
+     */
+    public String getLicense()
+    {
+        if ( m_resource.getLicense() != null )
+        {
+            return m_resource.getLicense().toString();
+        }
+
+        return null;
+    }
+
+
+    /**
+     * extract source from bindex information.
+     * @return bundle source
+     */
+    public String getSource()
+    {
+        if ( m_resource.getSource() != null )
+        {
+            return m_resource.getSource().toString();
+        }
+
+        return null;
+    }
+
+
+    /**
+     * extract source from bindex information.
+     * @return bundle source
+     */
+    public String getId()
+    {
+        if ( m_resource.getId() != null )
+        {
+            return m_resource.getId();
+        }
+
+        return null;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrCleanRepo.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrCleanRepo.java
new file mode 100644
index 0000000..f89d08d
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrCleanRepo.java
@@ -0,0 +1,303 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Result;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Clean an OBR repository by finding and removing missing resources.
+ * 
+ * @requiresProject false
+ * @goal clean
+ * @phase clean
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ObrCleanRepo extends AbstractMojo
+{
+    /**
+     * OBR Repository.
+     * 
+     * @parameter expression="${obrRepository}"
+     */
+    private String obrRepository;
+
+    /**
+     * Local Repository.
+     * 
+     * @parameter expression="${localRepository}"
+     * @required
+     * @readonly
+     */
+    private ArtifactRepository localRepository;
+
+
+    public void execute()
+    {
+        if ( "NONE".equalsIgnoreCase( obrRepository ) )
+        {
+            getLog().info( "OBR clean disabled (enable with -DobrRepository)" );
+            return;
+        }
+
+        try
+        {
+            // Compute local repository location
+            URI repositoryXml = ObrUtils.findRepositoryXml( localRepository.getBasedir(), obrRepository );
+            if ( !"file".equals( repositoryXml.getScheme() ) )
+            {
+                getLog().error( "The repository URI " + repositoryXml + " is not a local file" );
+                return;
+            }
+
+            File repositoryFile = new File( repositoryXml );
+
+            // Check if the file exist
+            if ( !repositoryFile.exists() )
+            {
+                getLog().error( "The repository file " + repositoryFile + " does not exist" );
+                return;
+            }
+
+            getLog().info( "Cleaning..." );
+
+            Document doc = parseFile( repositoryFile, initConstructor() );
+            Node finalDocument = cleanDocument( doc.getDocumentElement() ); // Analyze existing repository.
+
+            if ( finalDocument == null )
+            {
+                getLog().info( "Nothing to clean in " + repositoryFile );
+            }
+            else
+            {
+                writeToFile( repositoryXml, finalDocument ); // Write the new file
+                getLog().info( "Repository " + repositoryFile + " cleaned" );
+            }
+        }
+        catch ( Exception e )
+        {
+            getLog().error( "Exception while cleaning local OBR: " + e.getLocalizedMessage(), e );
+        }
+    }
+
+
+    /**
+     * Analyze the given XML tree (DOM of the repository file) and remove missing resources.
+     * 
+     * @param elem : the input XML tree
+     * @return the cleaned XML tree
+     */
+    private Element cleanDocument( Element elem )
+    {
+        String localRepoPath = localRepository.getBasedir();
+        NodeList nodes = elem.getElementsByTagName( "resource" );
+        List toRemove = new ArrayList();
+
+        // First, look for missing resources
+        for ( int i = 0; i < nodes.getLength(); i++ )
+        {
+            Element n = ( Element ) nodes.item( i );
+            String value = n.getAttribute( "uri" );
+
+            File file = new File( localRepoPath, value );
+            if ( !file.exists() )
+            {
+                getLog().info(
+                    "The bundle " + n.getAttribute( "presentationname" ) + " - " + n.getAttribute( "version" )
+                        + " will be removed" );
+                toRemove.add( n );
+            }
+        }
+
+        Date d = new Date();
+        if ( toRemove.size() > 0 )
+        {
+            // Then remove missing resources.
+            for ( int i = 0; i < toRemove.size(); i++ )
+            {
+                elem.removeChild( ( Node ) toRemove.get( i ) );
+            }
+
+            // If we have to remove resources, we need to update 'lastmodified' attribute
+            SimpleDateFormat format = new SimpleDateFormat( "yyyyMMddHHmmss.SSS" );
+            d.setTime( System.currentTimeMillis() );
+            elem.setAttribute( "lastmodified", format.format( d ) );
+            return elem;
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Initialize the document builder from Xerces.
+     * 
+     * @return DocumentBuilder ready to create new document
+     * @throws MojoExecutionException : occurs when the instantiation of the document builder fails
+     */
+    private DocumentBuilder initConstructor() throws MojoExecutionException
+    {
+        DocumentBuilder constructor = null;
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try
+        {
+            constructor = factory.newDocumentBuilder();
+        }
+        catch ( ParserConfigurationException e )
+        {
+            getLog().error( "Unable to create a new xml document" );
+            throw new MojoExecutionException( "Cannot create the Document Builder : " + e.getMessage() );
+        }
+        return constructor;
+    }
+
+
+    /**
+     * Open an XML file.
+     * 
+     * @param filename : XML file path
+     * @param constructor DocumentBuilder get from xerces
+     * @return Document which describes this file
+     * @throws MojoExecutionException occurs when the given file cannot be opened or is a valid XML file.
+     */
+    private Document parseFile( File file, DocumentBuilder constructor ) throws MojoExecutionException
+    {
+        if ( constructor == null )
+        {
+            return null;
+        }
+        // The document is the root of the DOM tree.
+        File targetFile = file.getAbsoluteFile();
+        getLog().info( "Parsing " + targetFile );
+        Document doc = null;
+        try
+        {
+            doc = constructor.parse( targetFile );
+        }
+        catch ( SAXException e )
+        {
+            getLog().error( "Cannot parse " + targetFile + " : " + e.getMessage() );
+            throw new MojoExecutionException( "Cannot parse " + targetFile + " : " + e.getMessage() );
+        }
+        catch ( IOException e )
+        {
+            getLog().error( "Cannot open " + targetFile + " : " + e.getMessage() );
+            throw new MojoExecutionException( "Cannot open " + targetFile + " : " + e.getMessage() );
+        }
+        return doc;
+    }
+
+
+    /**
+     * write a Node in a xml file.
+     * 
+     * @param outputFilename URI to the output file
+     * @param treeToBeWrite Node root of the tree to be write in file
+     * @throws MojoExecutionException if the plugin failed
+     */
+    private void writeToFile( URI outputFilename, Node treeToBeWrite ) throws MojoExecutionException
+    {
+        // init the transformer
+        Transformer transformer = null;
+        TransformerFactory tfabrique = TransformerFactory.newInstance();
+        try
+        {
+            transformer = tfabrique.newTransformer();
+        }
+        catch ( TransformerConfigurationException e )
+        {
+            getLog().error( "Unable to write to file: " + outputFilename.toString() );
+            throw new MojoExecutionException( "Unable to write to file: " + outputFilename.toString() + " : "
+                + e.getMessage() );
+        }
+        Properties proprietes = new Properties();
+        proprietes.put( "method", "xml" );
+        proprietes.put( "version", "1.0" );
+        proprietes.put( "encoding", "ISO-8859-1" );
+        proprietes.put( "standalone", "yes" );
+        proprietes.put( "indent", "yes" );
+        proprietes.put( "omit-xml-declaration", "no" );
+        transformer.setOutputProperties( proprietes );
+
+        DOMSource input = new DOMSource( treeToBeWrite );
+
+        File fichier = new File( outputFilename );
+        FileOutputStream flux = null;
+        try
+        {
+            flux = new FileOutputStream( fichier );
+        }
+        catch ( FileNotFoundException e )
+        {
+            getLog().error( "Unable to write to file: " + fichier.getName() );
+            throw new MojoExecutionException( "Unable to write to file: " + fichier.getName() + " : " + e.getMessage() );
+        }
+        Result output = new StreamResult( flux );
+        try
+        {
+            transformer.transform( input, output );
+        }
+        catch ( TransformerException e )
+        {
+            throw new MojoExecutionException( "Unable to write to file: " + outputFilename.toString() + " : "
+                + e.getMessage() );
+        }
+
+        try
+        {
+            flux.flush();
+            flux.close();
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "IOException when closing file : " + e.getMessage() );
+        }
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrDeploy.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrDeploy.java
new file mode 100644
index 0000000..7915082
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrDeploy.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.manager.WagonManager;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.settings.Settings;
+
+
+/**
+ * Deploys bundle details to a remote OBR repository (life-cycle goal)
+ * 
+ * @goal deploy
+ * @phase deploy
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class ObrDeploy extends AbstractMojo
+{
+    /**
+     * When true, ignore remote locking.
+     * 
+     * @parameter expression="${ignoreLock}"
+     */
+    private boolean ignoreLock;
+
+    /**
+     * OBR Repository.
+     * 
+     * @parameter expression="${obrRepository}"
+     */
+    private String obrRepository;
+
+    /**
+     * Project types which this plugin supports.
+     *
+     * @parameter
+     */
+    private List supportedProjectTypes = Arrays.asList( new String[]
+        { "jar", "bundle" } );
+
+    /**
+     * @parameter expression="${project.distributionManagementArtifactRepository}"
+     * @readonly
+     */
+    private ArtifactRepository deploymentRepository;
+
+    /**
+     * Alternative deployment repository. Format: id::layout::url
+     * 
+     * @parameter expression="${altDeploymentRepository}"
+     */
+    private String altDeploymentRepository;
+
+    /**
+     * Local Repository.
+     * 
+     * @parameter expression="${localRepository}"
+     * @required
+     * @readonly
+     */
+    private ArtifactRepository localRepository;
+
+    /**
+     * The Maven project.
+     * 
+     * @parameter expression="${project}"
+     * @required
+     * @readonly
+     */
+    private MavenProject project;
+
+    /**
+     * Local Maven settings.
+     * 
+     * @parameter expression="${settings}"
+     * @required
+     * @readonly
+     */
+    private Settings settings;
+
+    /**
+     * The Wagon manager.
+     * 
+     * @component
+     */
+    private WagonManager m_wagonManager;
+
+
+    public void execute() throws MojoExecutionException
+    {
+        if ( !supportedProjectTypes.contains( project.getPackaging() ) )
+        {
+            getLog().info( "Ignoring packaging type " + project.getPackaging() );
+            return;
+        }
+        else if ( "NONE".equalsIgnoreCase( obrRepository ) )
+        {
+            getLog().info( "OBR update disabled (enable with -DobrRepository)" );
+            return;
+        }
+
+        URI tempURI = ObrUtils.findRepositoryXml( "", obrRepository );
+        String repositoryName = new File( tempURI.getPath() ).getName();
+
+        Log log = getLog();
+        ObrUpdate update;
+
+        RemoteFileManager remoteFile = new RemoteFileManager( m_wagonManager, settings, log );
+        openRepositoryConnection( remoteFile );
+
+        // ======== LOCK REMOTE OBR ========
+        log.info( "LOCK " + remoteFile + '/' + repositoryName );
+        remoteFile.lockFile( repositoryName, ignoreLock );
+        File downloadedRepositoryXml = null;
+
+        try
+        {
+            // ======== DOWNLOAD REMOTE OBR ========
+            log.info( "Downloading " + repositoryName );
+            downloadedRepositoryXml = remoteFile.get( repositoryName, ".xml" );
+
+            String mavenRepository = localRepository.getBasedir();
+
+            URI repositoryXml = downloadedRepositoryXml.toURI();
+            URI obrXmlFile = ObrUtils.findObrXml( project.getResources() );
+            URI bundleJar = ObrUtils.findBundleJar( localRepository, project.getArtifact() );
+
+            Config userConfig = new Config();
+            userConfig.setPathRelative( true );
+            userConfig.setRemoteFile( true );
+
+            update = new ObrUpdate( repositoryXml, obrXmlFile, project, bundleJar, mavenRepository, userConfig, log );
+
+            update.updateRepository();
+
+            if ( downloadedRepositoryXml.exists() )
+            {
+                // ======== UPLOAD MODIFIED OBR ========
+                log.info( "Uploading " + repositoryName );
+                remoteFile.put( downloadedRepositoryXml, repositoryName );
+            }
+        }
+        catch ( Exception e )
+        {
+            log.warn( "Exception while updating remote OBR: " + e.getLocalizedMessage(), e );
+        }
+        finally
+        {
+            // ======== UNLOCK REMOTE OBR ========
+            log.info( "UNLOCK " + remoteFile + '/' + repositoryName );
+            remoteFile.unlockFile( repositoryName );
+            remoteFile.disconnect();
+
+            if ( null != downloadedRepositoryXml )
+            {
+                downloadedRepositoryXml.delete();
+            }
+        }
+    }
+
+    private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile( "(.+)::(.+)::(.+)" );
+
+
+    private void openRepositoryConnection( RemoteFileManager remoteFile ) throws MojoExecutionException
+    {
+        if ( deploymentRepository == null && altDeploymentRepository == null )
+        {
+            String msg = "Deployment failed: repository element was not specified in the pom inside"
+                + " distributionManagement element or in -DaltDeploymentRepository=id::layout::url parameter";
+
+            throw new MojoExecutionException( msg );
+        }
+
+        if ( altDeploymentRepository != null )
+        {
+            getLog().info( "Using alternate deployment repository " + altDeploymentRepository );
+
+            Matcher matcher = ALT_REPO_SYNTAX_PATTERN.matcher( altDeploymentRepository );
+            if ( !matcher.matches() )
+            {
+                throw new MojoExecutionException( "Invalid syntax for alternative repository \""
+                    + altDeploymentRepository + "\". Use \"id::layout::url\"." );
+            }
+
+            remoteFile.connect( matcher.group( 1 ).trim(), matcher.group( 3 ).trim() );
+        }
+        else
+        {
+            remoteFile.connect( deploymentRepository.getId(), deploymentRepository.getUrl() );
+        }
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrDeployFile.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrDeployFile.java
new file mode 100644
index 0000000..ca673f0
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrDeployFile.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.maven.artifact.manager.WagonManager;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.settings.Settings;
+
+
+/**
+ * Deploys bundle details to a remote OBR repository (command-line goal)
+ * 
+ * @requiresProject false
+ * @goal deploy-file
+ * @phase deploy
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class ObrDeployFile extends AbstractFileMojo
+{
+    /**
+     * When true, ignore remote locking.
+     * 
+     * @parameter expression="${ignoreLock}"
+     */
+    private boolean ignoreLock;
+
+    /**
+     * OBR Repository.
+     * 
+     * @parameter expression="${obrRepository}"
+     */
+    private String obrRepository;
+
+    /**
+     * Project types which this plugin supports.
+     *
+     * @parameter
+     */
+    private List supportedProjectTypes = Arrays.asList( new String[]
+        { "jar", "bundle" } );
+
+    /**
+     * Remote repository id, used to lookup authentication settings.
+     *
+     * @parameter expression="${repositoryId}" default-value="remote-repository"
+     * @required
+     */
+    private String repositoryId;
+
+    /**
+     * Remote OBR repository URL, where the bundle details are to be uploaded.
+     *
+     * @parameter expression="${url}"
+     * @required
+     */
+    private String url;
+
+    /**
+     * Optional public URL where the bundle has been deployed.
+     *
+     * @parameter expression="${bundleUrl}"
+     */
+    private String bundleUrl;
+
+    /**
+     * Local Repository.
+     * 
+     * @parameter expression="${localRepository}"
+     * @required
+     * @readonly
+     */
+    private ArtifactRepository localRepository;
+
+    /**
+     * Local Maven settings.
+     * 
+     * @parameter expression="${settings}"
+     * @required
+     * @readonly
+     */
+    private Settings settings;
+
+    /**
+     * The Wagon manager.
+     * 
+     * @component
+     */
+    private WagonManager m_wagonManager;
+
+
+    public void execute() throws MojoExecutionException
+    {
+        MavenProject project = getProject();
+
+        if ( !supportedProjectTypes.contains( project.getPackaging() ) )
+        {
+            getLog().info( "Ignoring packaging type " + project.getPackaging() );
+            return;
+        }
+        else if ( "NONE".equalsIgnoreCase( obrRepository ) )
+        {
+            getLog().info( "OBR update disabled (enable with -DobrRepository)" );
+            return;
+        }
+
+        URI tempURI = ObrUtils.findRepositoryXml( "", obrRepository );
+        String repositoryName = new File( tempURI.getPath() ).getName();
+
+        Log log = getLog();
+        ObrUpdate update;
+
+        RemoteFileManager remoteFile = new RemoteFileManager( m_wagonManager, settings, log );
+        remoteFile.connect( repositoryId, url );
+
+        // ======== LOCK REMOTE OBR ========
+        log.info( "LOCK " + remoteFile + '/' + repositoryName );
+        remoteFile.lockFile( repositoryName, ignoreLock );
+        File downloadedRepositoryXml = null;
+
+        try
+        {
+            // ======== DOWNLOAD REMOTE OBR ========
+            log.info( "Downloading " + repositoryName );
+            downloadedRepositoryXml = remoteFile.get( repositoryName, ".xml" );
+
+            String mavenRepository = localRepository.getBasedir();
+
+            URI repositoryXml = downloadedRepositoryXml.toURI();
+            URI obrXmlFile = ObrUtils.toFileURI( obrXml );
+            URI bundleJar;
+
+            if ( null == file )
+            {
+                bundleJar = ObrUtils.findBundleJar( localRepository, project.getArtifact() );
+            }
+            else
+            {
+                bundleJar = file.toURI();
+            }
+
+            URI remoteBundleURI = null;
+            if ( null != bundleUrl )
+            {
+                remoteBundleURI = URI.create( bundleUrl );
+            }
+            else if ( null != file )
+            {
+                remoteBundleURI = URI.create( localRepository.pathOf( project.getArtifact() ) );
+            }
+
+            Config userConfig = new Config();
+            userConfig.setRemoteBundle( remoteBundleURI );
+            userConfig.setPathRelative( true );
+            userConfig.setRemoteFile( true );
+
+            update = new ObrUpdate( repositoryXml, obrXmlFile, project, bundleJar, mavenRepository, userConfig, log );
+
+            update.updateRepository();
+
+            if ( downloadedRepositoryXml.exists() )
+            {
+                // ======== UPLOAD MODIFIED OBR ========
+                log.info( "Uploading " + repositoryName );
+                remoteFile.put( downloadedRepositoryXml, repositoryName );
+            }
+        }
+        catch ( Exception e )
+        {
+            log.warn( "Exception while updating remote OBR: " + e.getLocalizedMessage(), e );
+        }
+        finally
+        {
+            // ======== UNLOCK REMOTE OBR ========
+            log.info( "UNLOCK " + remoteFile + '/' + repositoryName );
+            remoteFile.unlockFile( repositoryName );
+            remoteFile.disconnect();
+
+            if ( null != downloadedRepositoryXml )
+            {
+                downloadedRepositoryXml.delete();
+            }
+        }
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/OBRInstall.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrInstall.java
similarity index 96%
rename from bundleplugin/src/main/java/org/apache/felix/bundleplugin/OBRInstall.java
rename to bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrInstall.java
index 02d5419..8d0207f 100644
--- a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/OBRInstall.java
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrInstall.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.bundleplugin;
+package org.apache.felix.obr.plugin;
 
 
 import java.net.URI;
@@ -40,7 +40,7 @@
  * 
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
  */
-public final class OBRInstall extends AbstractMojo
+public final class ObrInstall extends AbstractMojo
 {
     /**
      * OBR Repository.
diff --git a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/OBRInstall.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrInstallFile.java
similarity index 63%
copy from bundleplugin/src/main/java/org/apache/felix/bundleplugin/OBRInstall.java
copy to bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrInstallFile.java
index 02d5419..2b22d57 100644
--- a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/OBRInstall.java
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrInstallFile.java
@@ -16,31 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.bundleplugin;
+package org.apache.felix.obr.plugin;
 
 
 import java.net.URI;
 import java.util.Arrays;
 import java.util.List;
 
-import org.apache.felix.obr.plugin.Config;
-import org.apache.felix.obr.plugin.ObrUpdate;
-import org.apache.felix.obr.plugin.ObrUtils;
 import org.apache.maven.artifact.repository.ArtifactRepository;
-import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.logging.Log;
 import org.apache.maven.project.MavenProject;
 
 
 /**
- * Installs bundle details in the local OBR repository (life-cycle goal)
+ * Installs bundle details in the local OBR repository (command-line goal)
  * 
- * @goal install
+ * @requiresProject false
+ * @goal install-file
  * @phase install
  * 
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
  */
-public final class OBRInstall extends AbstractMojo
+public final class ObrInstallFile extends AbstractFileMojo
 {
     /**
      * OBR Repository.
@@ -66,18 +64,11 @@
      */
     private ArtifactRepository localRepository;
 
-    /**
-     * The Maven project.
-     * 
-     * @parameter expression="${project}"
-     * @required
-     * @readonly
-     */
-    private MavenProject project;
 
-
-    public void execute()
+    public void execute() throws MojoExecutionException
     {
+        MavenProject project = getProject();
+
         if ( !supportedProjectTypes.contains( project.getPackaging() ) )
         {
             getLog().info( "Ignoring packaging type " + project.getPackaging() );
@@ -92,23 +83,25 @@
         Log log = getLog();
         ObrUpdate update;
 
-        try
+        String mavenRepository = localRepository.getBasedir();
+
+        URI repositoryXml = ObrUtils.findRepositoryXml( mavenRepository, obrRepository );
+        URI obrXmlFile = ObrUtils.toFileURI( obrXml );
+        URI bundleJar;
+
+        if ( null == file )
         {
-            String mavenRepository = localRepository.getBasedir();
-
-            URI repositoryXml = ObrUtils.findRepositoryXml( mavenRepository, obrRepository );
-            URI obrXmlFile = ObrUtils.findObrXml( project.getResources() );
-            URI bundleJar = ObrUtils.findBundleJar( localRepository, project.getArtifact() );
-
-            Config userConfig = new Config();
-
-            update = new ObrUpdate( repositoryXml, obrXmlFile, project, bundleJar, mavenRepository, userConfig, log );
-
-            update.updateRepository();
+            bundleJar = ObrUtils.findBundleJar( localRepository, project.getArtifact() );
         }
-        catch ( Exception e )
+        else
         {
-            log.warn( "Exception while updating local OBR: " + e.getLocalizedMessage(), e );
+            bundleJar = file.toURI();
         }
+
+        Config userConfig = new Config();
+
+        update = new ObrUpdate( repositoryXml, obrXmlFile, project, bundleJar, mavenRepository, userConfig, log );
+
+        update.updateRepository();
     }
 }
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrUpdate.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrUpdate.java
new file mode 100644
index 0000000..27dfd7e
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrUpdate.java
@@ -0,0 +1,582 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Properties;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Result;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+
+/**
+ * this class parse the old repository.xml file build the bundle resource description and update the repository.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ObrUpdate
+{
+    /**
+     * generate the date format to insert it in repository descriptor file.
+     */
+    static SimpleDateFormat m_format = new SimpleDateFormat( "yyyyMMddHHmmss.SSS" );
+
+    /**
+     * logger for this plugin.
+     */
+    private Log m_logger;
+
+    /**
+     * name and path to the repository descriptor file.
+     */
+    private URI m_repositoryXml;
+
+    /**
+     * name and path to the obr.xml file.
+     */
+    private URI m_obrXml;
+
+    /**
+     * name and path to the bundle jar file.
+     */
+    private URI m_bundleJar;
+
+    /**
+     * maven project description.
+     */
+    private MavenProject m_project;
+
+    /**
+     * user configuration information.
+     */
+    private Config m_userConfig;
+
+    /**
+     * used to build another xml document.
+     */
+    private DocumentBuilder m_documentBuilder;
+
+    /**
+     * root on parent document.
+     */
+    private Document m_repositoryDoc;
+
+    /**
+     * used to store bundle information.
+     */
+    private ResourcesBundle m_resourceBundle;
+
+    /**
+     * base URI used to relativize bundle URIs.
+     */
+    private URI m_baseURI;
+
+
+    /**
+     * initialize information.
+     * @param repositoryXml path to the repository descriptor file
+     * @param obrXml path and filename to the obr.xml file
+     * @param project maven project description
+     * @param bundleJar path to the bundle jar file
+     * @param mavenRepositoryPath path to the local maven repository
+     * @param userConfig user information
+     * @param logger plugin logger
+     */
+    public ObrUpdate( URI repositoryXml, URI obrXml, MavenProject project, URI bundleJar, String mavenRepositoryPath,
+        Config userConfig, Log logger )
+    {
+        m_bundleJar = bundleJar;
+        m_repositoryXml = repositoryXml;
+        m_obrXml = obrXml;
+        m_project = project;
+        m_logger = logger;
+
+        m_userConfig = userConfig;
+
+        m_resourceBundle = new ResourcesBundle( logger );
+
+        if ( userConfig.isRemoteFile() )
+        {
+            m_baseURI = ObrUtils.toFileURI( mavenRepositoryPath );
+        }
+        else
+        {
+            m_baseURI = m_repositoryXml;
+        }
+    }
+
+
+    /**
+     * update the repository descriptor file. parse the old repository descriptor file, get the old reference of the bundle or determine the id for a new bundle, extract information from bindex set the new information in descriptor file and save it.
+     * @throws MojoExecutionException if the plugin failed
+     */
+    public void updateRepository() throws MojoExecutionException
+    {
+        m_logger.debug( " (f) repositoryXml = " + m_repositoryXml );
+        m_logger.debug( " (f) bundleJar = " + m_bundleJar );
+        m_logger.debug( " (f) obrXml = " + m_obrXml );
+
+        m_documentBuilder = initDocumentBuilder();
+
+        if ( m_documentBuilder == null )
+        {
+            return;
+        }
+
+        // get the file size
+        File bundleFile = new File( m_bundleJar );
+        if ( bundleFile.exists() )
+        {
+            URI resourceURI = m_userConfig.getRemoteBundle();
+            if ( null == resourceURI )
+            {
+                resourceURI = m_bundleJar;
+                if ( m_userConfig.isPathRelative() )
+                {
+                    resourceURI = ObrUtils.getRelativeURI( m_baseURI, resourceURI );
+                }
+            }
+
+            m_resourceBundle.setSize( String.valueOf( bundleFile.length() ) );
+            m_resourceBundle.setUri( resourceURI.toASCIIString() );
+        }
+        else
+        {
+            m_logger.error( "file doesn't exist: " + m_bundleJar );
+            return;
+        }
+
+        // parse repository
+        if ( parseRepositoryXml() == -1 )
+        {
+            return;
+        }
+
+        // parse the obr.xml file
+        if ( m_obrXml != null )
+        {
+            m_logger.info( "Adding " + m_obrXml );
+
+            // URL url = getClass().getResource("/SchemaObr.xsd");
+            // TODO validate obr.xml file
+
+            Document obrXmlDoc = parseFile( m_obrXml, m_documentBuilder );
+            if ( obrXmlDoc == null )
+            {
+                return;
+            }
+
+            Node obrXmlRoot = obrXmlDoc.getDocumentElement();
+
+            // add contents to resource bundle
+            sortObrXml( obrXmlRoot );
+        }
+
+        ExtractBindexInfo bindexExtractor;
+        try
+        {
+            // use bindex to extract bundle information
+            bindexExtractor = new ExtractBindexInfo( m_repositoryXml, m_bundleJar.getPath() );
+        }
+        catch ( MojoExecutionException e )
+        {
+            m_logger.error( "unable to build Bindex informations" );
+            e.printStackTrace();
+
+            throw new MojoExecutionException( "BindexException" );
+        }
+
+        m_resourceBundle.construct( m_project, bindexExtractor );
+
+        Element rootElement = m_repositoryDoc.getDocumentElement();
+        if ( !walkOnTree( rootElement ) )
+        {
+            // the correct resource node was not found, we must create it
+            // we compute the new id
+            String id = m_resourceBundle.getId();
+            searchRepository( rootElement, id );
+        }
+
+        // the repository.xml file have been modified, so we save it
+        m_logger.info( "Writing OBR metadata" );
+        writeToFile( m_repositoryXml, rootElement );
+    }
+
+
+    /**
+     * init the document builder from xerces.
+     * @return DocumentBuilder ready to create new document
+     */
+    private DocumentBuilder initDocumentBuilder()
+    {
+        DocumentBuilder documentBuilder = null;
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try
+        {
+            documentBuilder = factory.newDocumentBuilder();
+        }
+        catch ( ParserConfigurationException e )
+        {
+            m_logger.error( "unable to create a new xml document" );
+            e.printStackTrace();
+        }
+        return documentBuilder;
+
+    }
+
+
+    /**
+     * Parse the repository descriptor file.
+     * 
+     * @return 0 if the bundle is already in the descriptor, else -1
+     * @throws MojoExecutionException if the plugin failed
+     */
+    private int parseRepositoryXml() throws MojoExecutionException
+    {
+
+        File fout = new File( m_repositoryXml );
+        if ( !fout.exists() )
+        {
+            Document doc = m_documentBuilder.newDocument();
+
+            // create xml tree
+            Date d = new Date();
+            d.setTime( System.currentTimeMillis() );
+            Element root = doc.createElement( "repository" );
+            root.setAttribute( "lastmodified", m_format.format( d ) );
+            root.setAttribute( "name", "MyRepository" );
+            try
+            {
+                writeToFile( m_repositoryXml, root );
+            }
+            catch ( MojoExecutionException e )
+            {
+                e.printStackTrace();
+                throw new MojoExecutionException( "IOException" );
+            }
+        }
+
+        // now we parse the repository.xml file
+        m_repositoryDoc = parseFile( m_repositoryXml, m_documentBuilder );
+        if ( m_repositoryDoc == null )
+        {
+            return -1;
+        }
+
+        return 0;
+    }
+
+
+    /**
+     * transform a xml file to a xerces Document.
+     * @param filename path to the xml file
+     * @param documentBuilder DocumentBuilder get from xerces
+     * @return Document which describe this file
+     */
+    private Document parseFile( URI filename, DocumentBuilder documentBuilder )
+    {
+        if ( documentBuilder == null )
+        {
+            return null;
+        }
+        // The document is the root of the DOM tree.
+        m_logger.info( "Parsing " + filename );
+        Document doc = null;
+        try
+        {
+            doc = documentBuilder.parse( new File( filename ) );
+        }
+        catch ( SAXException e )
+        {
+            e.printStackTrace();
+            return null;
+        }
+        catch ( IOException e )
+        {
+            m_logger.error( "cannot open file: " + filename );
+            e.printStackTrace();
+            return null;
+        }
+        return doc;
+    }
+
+
+    /**
+     * put the information from obr.xml into ressourceBundle object.
+     * @param node Node to the OBR.xml file
+     */
+    private void sortObrXml( Node node )
+    {
+        if ( node.getNodeName().compareTo( "require" ) == 0 )
+        {
+            Require newRequireNode = new Require();
+            NamedNodeMap list = node.getAttributes();
+            try
+            {
+                newRequireNode.setExtend( list.getNamedItem( "extend" ).getNodeValue() );
+                newRequireNode.setMultiple( list.getNamedItem( "multiple" ).getNodeValue() );
+                newRequireNode.setOptional( list.getNamedItem( "optional" ).getNodeValue() );
+                newRequireNode.setFilter( list.getNamedItem( "filter" ).getNodeValue() );
+                newRequireNode.setName( list.getNamedItem( "name" ).getNodeValue() );
+            }
+            catch ( NullPointerException e )
+            {
+                m_logger
+                    .error( "the obr.xml file seems to be invalid in a \"require\" tag (one or more attributes are missing)" );
+                // e.printStackTrace();
+            }
+            newRequireNode.setValue( XmlHelper.getTextContent( node ) );
+            m_resourceBundle.addRequire( newRequireNode );
+        }
+        else if ( node.getNodeName().compareTo( "capability" ) == 0 )
+        {
+            Capability newCapability = new Capability();
+            try
+            {
+                newCapability.setName( node.getAttributes().getNamedItem( "name" ).getNodeValue() );
+            }
+            catch ( NullPointerException e )
+            {
+                m_logger.error( "attribute \"name\" is missing in obr.xml in a \"capability\" tag" );
+                e.printStackTrace();
+            }
+            NodeList list = node.getChildNodes();
+            for ( int i = 0; i < list.getLength(); i++ )
+            {
+                PElement p = new PElement();
+                Node n = list.item( i );
+                Node item = null;
+                // System.err.println(n.getNodeName());
+                if ( n.getNodeName().compareTo( "p" ) == 0 )
+                {
+
+                    p.setN( n.getAttributes().getNamedItem( "n" ).getNodeValue() );
+                    item = n.getAttributes().getNamedItem( "t" );
+                    if ( item != null )
+                    {
+                        p.setT( item.getNodeValue() );
+                    }
+                    item = n.getAttributes().getNamedItem( "v" );
+                    if ( item != null )
+                    {
+                        p.setV( item.getNodeValue() );
+                    }
+
+                    newCapability.addP( p );
+                }
+            }
+            m_resourceBundle.addCapability( newCapability );
+        }
+        else if ( node.getNodeName().compareTo( "category" ) == 0 )
+        {
+            Category newCategory = new Category();
+            newCategory.setId( node.getAttributes().getNamedItem( "id" ).getNodeValue() );
+            m_resourceBundle.addCategory( newCategory );
+        }
+        else
+        {
+            NodeList list = node.getChildNodes();
+            for ( int i = 0; i < list.getLength(); i++ )
+            {
+                sortObrXml( list.item( i ) );
+            }
+        }
+    }
+
+
+    /**
+     * write a Node in a xml file.
+     * @param outputFilename URI to the output file
+     * @param treeToBeWrite Node root of the tree to be write in file
+     * @throws MojoExecutionException if the plugin failed
+     */
+    private void writeToFile( URI outputFilename, Node treeToBeWrite ) throws MojoExecutionException
+    {
+        // init the transformer
+        Transformer transformer = null;
+        TransformerFactory tfabrique = TransformerFactory.newInstance();
+        try
+        {
+            transformer = tfabrique.newTransformer();
+        }
+        catch ( TransformerConfigurationException e )
+        {
+            m_logger.error( "Unable to write to file: " + outputFilename.toString() );
+            e.printStackTrace();
+            throw new MojoExecutionException( "TransformerConfigurationException" );
+        }
+        Properties proprietes = new Properties();
+        proprietes.put( "method", "xml" );
+        proprietes.put( "version", "1.0" );
+        proprietes.put( "encoding", "ISO-8859-1" );
+        proprietes.put( "standalone", "yes" );
+        proprietes.put( "indent", "yes" );
+        proprietes.put( "omit-xml-declaration", "no" );
+        transformer.setOutputProperties( proprietes );
+
+        DOMSource input = new DOMSource( treeToBeWrite );
+
+        File fichier = new File( outputFilename );
+        fichier.getParentFile().mkdirs();
+        FileOutputStream flux = null;
+        try
+        {
+            flux = new FileOutputStream( fichier );
+        }
+        catch ( FileNotFoundException e )
+        {
+            m_logger.error( "Unable to write to file: " + fichier.getName() );
+            e.printStackTrace();
+            throw new MojoExecutionException( "FileNotFoundException" );
+        }
+        Result output = new StreamResult( flux );
+        try
+        {
+            transformer.transform( input, output );
+        }
+        catch ( TransformerException e )
+        {
+            e.printStackTrace();
+            throw new MojoExecutionException( "TransformerException" );
+        }
+
+        try
+        {
+            flux.flush();
+            flux.close();
+        }
+        catch ( IOException e )
+        {
+            e.printStackTrace();
+            throw new MojoExecutionException( "IOException" );
+        }
+
+    }
+
+
+    /**
+     * walk on the tree until the targeted node was found.
+     * @param node targeted node
+     * @return true if the requiered node was found else false.
+     */
+    private boolean walkOnTree( Node node )
+    {
+
+        if ( node.getNodeName().compareTo( "resource" ) == 0 )
+        {
+            return resource( node );
+        }
+
+        // look at the repository node (first in the file)
+        if ( node.getNodeName().compareTo( "repository" ) == 0 )
+        {
+            Date d = new Date();
+            d.setTime( System.currentTimeMillis() );
+            NamedNodeMap nList = node.getAttributes();
+            Node n = nList.getNamedItem( "lastmodified" );
+            n.setNodeValue( m_format.format( d ) );
+        }
+
+        NodeList list = node.getChildNodes();
+        if ( list.getLength() > 0 )
+        {
+            for ( int i = 0; i < list.getLength(); i++ )
+            {
+                if ( walkOnTree( list.item( i ) ) )
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * put the resource bundle in the tree.
+     * @param node Node on the xml file
+     * @param id id of the bundle ressource
+     */
+    private void searchRepository( Node node, String id )
+    {
+        if ( node.getNodeName().compareTo( "repository" ) == 0 )
+        {
+            node.appendChild( m_resourceBundle.getNode( m_repositoryDoc ) );
+            return;
+        }
+
+        m_logger.info( "Second branch..." );
+        NodeList list = node.getChildNodes();
+        if ( list.getLength() > 0 )
+        {
+            for ( int i = 0; i < list.getLength(); i++ )
+            {
+                searchRepository( list.item( i ), id );
+            }
+        }
+    }
+
+
+    /**
+     * compare two node and update the array which compute the smallest free id.
+     * @param node : node
+     * @return true if the node is the same bundle than the ressourceBundle, else false.
+     */
+    private boolean resource( Node node )
+    {
+
+        // this part save all the id free if we need to add resource
+        String id = node.getAttributes().getNamedItem( "id" ).getNodeValue();
+        NamedNodeMap map = node.getAttributes();
+
+        if ( m_resourceBundle.isSameBundleResource( map.getNamedItem( "symbolicname" ).getNodeValue(), map
+            .getNamedItem( "version" ).getNodeValue() ) )
+        {
+            m_resourceBundle.setId( String.valueOf( id ) );
+            node.getParentNode().replaceChild( m_resourceBundle.getNode( m_repositoryDoc ), node );
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrUtils.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrUtils.java
new file mode 100644
index 0000000..94728a0
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ObrUtils.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.model.Resource;
+
+
+/**
+ * Various OBR utility methods
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ObrUtils
+{
+    private static final String DOT_XML = ".xml";
+    private static final String REPO_XML = "repository.xml";
+    private static final String OBR_XML = "obr.xml";
+
+
+    /**
+     * @param mavenRepository path to local maven repository
+     * @param obrRepository path to specific repository.xml
+     * @return URI pointing to correct repository.xml
+     */
+    public static URI findRepositoryXml( String mavenRepository, String obrRepository )
+    {
+        String targetPath = obrRepository;
+
+        // Combine location settings into a single repository location
+        if ( null == targetPath || targetPath.trim().length() == 0 || "true".equalsIgnoreCase( targetPath ) )
+        {
+            targetPath = mavenRepository + '/' + REPO_XML;
+        }
+        else if ( !targetPath.toLowerCase().endsWith( DOT_XML ) )
+        {
+            targetPath = targetPath + '/' + REPO_XML;
+        }
+
+        URI uri;
+        try
+        {
+            uri = new URI( targetPath );
+            uri.toURL(); // check protocol
+        }
+        catch ( Exception e )
+        {
+            uri = null;
+        }
+
+        // fall-back to file-system approach
+        if ( null == uri || !uri.isAbsolute() )
+        {
+            uri = new File( targetPath ).toURI();
+        }
+
+        return uri;
+    }
+
+
+    /**
+     * @param resources collection of resource locations
+     * @return URI pointing to correct obr.xml, null if not found
+     */
+    public static URI findObrXml( Collection resources )
+    {
+        for ( Iterator i = resources.iterator(); i.hasNext(); )
+        {
+            Resource resource = ( Resource ) i.next();
+            File obrFile = new File( resource.getDirectory(), OBR_XML );
+            if ( obrFile.exists() )
+            {
+                return obrFile.toURI();
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * @param repository maven repository
+     * @param artifact maven artifact
+     * @return file URI pointing to artifact in repository
+     */
+    public static URI findBundleJar( ArtifactRepository repository, Artifact artifact )
+    {
+        String baseDir = repository.getBasedir();
+        String artifactPath = repository.pathOf( artifact );
+
+        return toFileURI( baseDir + '/' + artifactPath );
+    }
+
+
+    /**
+     * @param path filesystem path
+     * @return file URI for the path
+     */
+    public static URI toFileURI( String path )
+    {
+        if ( null == path )
+        {
+            return null;
+        }
+        else if ( path.startsWith( "file:" ) )
+        {
+            return URI.create( path );
+        }
+        else
+        {
+            return new File( path ).toURI();
+        }
+    }
+
+
+    /**
+     * @param repositoryXml URI pointing to repository.xml, or directory containing it
+     * @param bundleJar URI pointing to bundle jarfile
+     * @return relative URI to bundle jarfile
+     */
+    public static URI getRelativeURI( URI repositoryXml, URI bundleJar )
+    {
+        try
+        {
+            String repositoryPath = repositoryXml.getPath();
+            if ( repositoryPath.toLowerCase().endsWith( DOT_XML ) )
+            {
+                // remove filename to get containing directory
+                int dirnameIndex = repositoryPath.lastIndexOf( '/' );
+                repositoryPath = repositoryPath.substring( 0, dirnameIndex );
+            }
+
+            URI rootURI = new URI( null, repositoryPath, null );
+            URI localURI = new URI( null, bundleJar.getPath(), null );
+
+            return rootURI.relativize( localURI );
+        }
+        catch ( Exception e )
+        {
+            return bundleJar;
+        }
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/PElement.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/PElement.java
new file mode 100644
index 0000000..63ae409
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/PElement.java
@@ -0,0 +1,131 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * this class describe the p element in a capability tag.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ * 
+ */
+public class PElement
+{
+    /**
+     * store the v tag (value).
+     */
+    private String m_v;
+
+    /**
+     * store the t tag (type).
+     */
+    private String m_t;
+
+    /**
+     * store the n tag (name).
+     */
+    private String m_n;
+
+
+    /**
+     * get the n tag.
+     * @return attribute n
+     */
+    public String getN()
+    {
+        return m_n;
+    }
+
+
+    /**
+     * set the n tage.
+     * @param n new value
+     */
+    public void setN( String n )
+    {
+        m_n = n;
+    }
+
+
+    /**
+     * get the t tag.
+     * @return attribute t
+     */
+    public String getT()
+    {
+        return m_t;
+    }
+
+
+    /**
+     * set the t tag.
+     * @param t new value
+     */
+    public void setT( String t )
+    {
+        m_t = t;
+    }
+
+
+    /**
+     * get the v tag.
+     * @return attribute v
+     */
+    public String getV()
+    {
+        return m_v;
+    }
+
+
+    /**
+     * set the v tag.
+     * @param v new value
+     */
+    public void setV( String v )
+    {
+        m_v = v;
+    }
+
+
+    /**
+     * transform this object to node.
+     * @param father father document for create Node
+     * @return node
+     */
+    public Node getNode( Document father )
+    {
+        Element p = father.createElement( "p" );
+        p.setAttribute( "n", getN() );
+        if ( getT() != null )
+        {
+            p.setAttribute( "t", getT() );
+        }
+
+        if ( getV() != null )
+        {
+            p.setAttribute( "v", getV() );
+        }
+
+        return p;
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/PomHelper.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/PomHelper.java
new file mode 100644
index 0000000..7d81e09
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/PomHelper.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+
+/**
+ * Maven POM helper methods.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class PomHelper
+{
+    public static MavenProject readPom( File pomFile ) throws MojoExecutionException
+    {
+        Reader reader = null;
+
+        try
+        {
+            reader = new FileReader( pomFile );
+            MavenXpp3Reader modelReader = new MavenXpp3Reader();
+            return new MavenProject( modelReader.read( reader ) );
+        }
+        catch ( FileNotFoundException e )
+        {
+            throw new MojoExecutionException( "Error reading specified POM file: " + e.getMessage(), e );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "Error reading specified POM file: " + e.getMessage(), e );
+        }
+        catch ( XmlPullParserException e )
+        {
+            throw new MojoExecutionException( "Error reading specified POM file: " + e.getMessage(), e );
+        }
+        finally
+        {
+            IOUtil.close( reader );
+        }
+    }
+
+
+    public static MavenProject buildPom( String groupId, String artifactId, String version, String packaging )
+    {
+        Model model = new Model();
+
+        model.setModelVersion( "4.0.0" );
+        model.setGroupId( groupId );
+        model.setArtifactId( artifactId );
+        model.setVersion( version );
+        model.setPackaging( packaging );
+
+        return new MavenProject( model );
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/RemoteFileManager.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/RemoteFileManager.java
new file mode 100644
index 0000000..4a01d21
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/RemoteFileManager.java
@@ -0,0 +1,374 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.maven.artifact.manager.WagonConfigurationException;
+import org.apache.maven.artifact.manager.WagonManager;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.settings.Proxy;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.wagon.ConnectionException;
+import org.apache.maven.wagon.ResourceDoesNotExistException;
+import org.apache.maven.wagon.TransferFailedException;
+import org.apache.maven.wagon.UnsupportedProtocolException;
+import org.apache.maven.wagon.Wagon;
+import org.apache.maven.wagon.authentication.AuthenticationException;
+import org.apache.maven.wagon.authorization.AuthorizationException;
+import org.apache.maven.wagon.proxy.ProxyInfo;
+import org.apache.maven.wagon.repository.Repository;
+
+
+/**
+ * this class is used to manage all connections by wagon.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class RemoteFileManager
+{
+    /**
+     * save the connection.
+     */
+    private Wagon m_wagon;
+
+    /**
+     * the wagon manager.
+     */
+    private WagonManager m_wagonManager;
+
+    /**
+     * the project settings.
+     */
+    private Settings m_settings;
+
+    /**
+     * logger instance.
+     */
+    private Log m_log;
+
+
+    /**
+     * initialize main information.
+     * @param wm WagonManager provides by maven
+     * @param settings settings of the current project provides by maven
+     * @param log logger
+     */
+    public RemoteFileManager( WagonManager wm, Settings settings, Log log )
+    {
+        m_wagonManager = wm;
+        m_settings = settings;
+        m_log = log;
+        m_wagon = null;
+    }
+
+
+    /**
+     * disconnect the current object.
+     */
+    public void disconnect()
+    {
+        try
+        {
+            if ( m_wagon != null )
+            {
+                m_wagon.disconnect();
+            }
+        }
+        catch ( ConnectionException e )
+        {
+            m_log.error( "Error disconnecting Wagon", e );
+        }
+    }
+
+
+    /**
+     * connect the current object to repository given in constructor.
+     * @param id repository id
+     * @param url repository url
+     * @throws MojoExecutionException
+     */
+    public void connect( String id, String url ) throws MojoExecutionException
+    {
+        Repository repository = new Repository( id, url );
+
+        try
+        {
+            m_wagon = m_wagonManager.getWagon( repository );
+        }
+        catch ( UnsupportedProtocolException e )
+        {
+            throw new MojoExecutionException( "Unsupported protocol: '" + repository.getProtocol() + "'", e );
+        }
+        catch ( WagonConfigurationException e )
+        {
+            throw new MojoExecutionException( "Unable to configure Wagon: '" + repository.getProtocol() + "'", e );
+        }
+
+        try
+        {
+            ProxyInfo proxyInfo = getProxyInfo( m_settings );
+            if ( proxyInfo != null )
+            {
+                m_wagon.connect( repository, m_wagonManager.getAuthenticationInfo( id ), proxyInfo );
+            }
+            else
+            {
+                m_wagon.connect( repository, m_wagonManager.getAuthenticationInfo( id ) );
+            }
+        }
+        catch ( ConnectionException e )
+        {
+            throw new MojoExecutionException( "Connection failed", e );
+        }
+        catch ( AuthenticationException e )
+        {
+            throw new MojoExecutionException( "Authentication failed", e );
+        }
+    }
+
+
+    /**
+     * get a file from the current repository connected.
+     * @param url url to the targeted file
+     * @param suffix suggested file suffix
+     * @return get a file descriptor on the required resource
+     * @throws MojoExecutionException
+     */
+    public File get( String url, String suffix ) throws MojoExecutionException
+    {
+        if ( m_wagon == null )
+        {
+            m_log.error( "must be connected first!" );
+            return null;
+        }
+
+        File file = null;
+        try
+        {
+            file = File.createTempFile( String.valueOf( System.currentTimeMillis() ), suffix );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "I/O problem", e );
+        }
+
+        try
+        {
+            m_wagon.get( url, file );
+        }
+        catch ( TransferFailedException e )
+        {
+            file.delete(); // cleanup on failure
+            throw new MojoExecutionException( "Transfer failed", e );
+        }
+        catch ( AuthorizationException e )
+        {
+            file.delete(); // cleanup on failure
+            throw new MojoExecutionException( "Authorization failed", e );
+        }
+        catch ( ResourceDoesNotExistException e )
+        {
+            file.delete(); // return non-existent file
+        }
+
+        return file;
+    }
+
+
+    /**
+     * put a file on the current repository connected.
+     * @param file file to upload
+     * @param url url to copy file
+     * @throws MojoExecutionException
+     */
+    public void put( File file, String url ) throws MojoExecutionException
+    {
+        if ( m_wagon == null )
+        {
+            m_log.error( "must be connected first!" );
+            return;
+        }
+
+        try
+        {
+            m_wagon.put( file, url );
+        }
+        catch ( TransferFailedException e )
+        {
+            throw new MojoExecutionException( "Transfer failed", e );
+        }
+        catch ( AuthorizationException e )
+        {
+            throw new MojoExecutionException( "Authorization failed", e );
+        }
+        catch ( ResourceDoesNotExistException e )
+        {
+            throw new MojoExecutionException( "Resource does not exist:" + file, e );
+        }
+    }
+
+
+    /**
+     * Convenience method to map a Proxy object from the user system settings to a ProxyInfo object.
+     * @param settings project settings given by maven
+     * @return a proxyInfo object instancied or null if no active proxy is define in the settings.xml
+     */
+    public static ProxyInfo getProxyInfo( Settings settings )
+    {
+        ProxyInfo proxyInfo = null;
+        if ( settings != null && settings.getActiveProxy() != null )
+        {
+            Proxy settingsProxy = settings.getActiveProxy();
+
+            proxyInfo = new ProxyInfo();
+            proxyInfo.setHost( settingsProxy.getHost() );
+            proxyInfo.setType( settingsProxy.getProtocol() );
+            proxyInfo.setPort( settingsProxy.getPort() );
+            proxyInfo.setNonProxyHosts( settingsProxy.getNonProxyHosts() );
+            proxyInfo.setUserName( settingsProxy.getUsername() );
+            proxyInfo.setPassword( settingsProxy.getPassword() );
+        }
+
+        return proxyInfo;
+    }
+
+
+    public void lockFile( String fileName, boolean ignoreLock ) throws MojoExecutionException
+    {
+        if ( !ignoreLock )
+        {
+            int countError = 0;
+            while ( isLockedFile( fileName ) && countError < 2 )
+            {
+                countError++;
+                m_log.warn( "File is currently locked, retry in 10s" );
+                try
+                {
+                    Thread.sleep( 10000 );
+                }
+                catch ( InterruptedException e )
+                {
+                    m_log.warn( "Sleep interrupted" );
+                }
+            }
+
+            if ( countError == 2 )
+            {
+                m_log.error( "File " + fileName + " is locked. Use -DignoreLock to force uploading" );
+                throw new MojoExecutionException( "Remote file locked" );
+            }
+        }
+
+        File file = null;
+        try
+        {
+            // create a non-empty file used to lock the remote file
+            file = File.createTempFile( String.valueOf( System.currentTimeMillis() ), ".lock" );
+
+            Writer writer = new BufferedWriter( new FileWriter( file ) );
+            writer.write( "LOCKED" );
+            writer.close();
+
+            put( file, fileName + ".lock" );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "I/O problem", e );
+        }
+        finally
+        {
+            if ( null != file )
+            {
+                file.delete();
+            }
+        }
+    }
+
+
+    public void unlockFile( String fileName ) throws MojoExecutionException
+    {
+        File file = null;
+        try
+        {
+            // clear the contents of the file used to lock the remote file
+            file = File.createTempFile( String.valueOf( System.currentTimeMillis() ), ".lock" );
+
+            Writer writer = new BufferedWriter( new FileWriter( file ) );
+            writer.write( " " ); // write 1 byte to force wagon upload
+            writer.close();
+
+            put( file, fileName + ".lock" );
+        }
+        catch ( IOException e )
+        {
+            throw new MojoExecutionException( "I/O problem", e );
+        }
+        finally
+        {
+            if ( null != file )
+            {
+                file.delete();
+            }
+        }
+    }
+
+
+    /**
+     * this method indicates if the targeted file is locked or not.
+     * @param remote connection manager
+     * @param fileName name targeted
+     * @return  true if the required file is locked, else false
+     * @throws MojoExecutionException
+     */
+    public boolean isLockedFile( String fileName ) throws MojoExecutionException
+    {
+        File file = null;
+        try
+        {
+            file = get( fileName + ".lock", ".lock" );
+
+            // file is locked with contents "LOCKED"
+            if ( null != file && file.length() <= 2 )
+            {
+                return false;
+            }
+        }
+        finally
+        {
+            if ( null != file )
+            {
+                file.delete();
+            }
+        }
+
+        return true;
+    }
+
+
+    public String toString()
+    {
+        return m_wagon.getRepository().getUrl();
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Require.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Require.java
new file mode 100644
index 0000000..370eb38
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/Require.java
@@ -0,0 +1,204 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * this class store a Require tag.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Require
+{
+
+    /**
+     * store the extend attribute.
+     */
+    private String m_extend;
+
+    /**
+     * store the multiple attribute.
+     */
+    private String m_multiple;
+
+    /**
+     * store the optional attribute.
+     */
+    private String m_optional;
+
+    /**
+     * store the name attribute.
+     */
+    private String m_name;
+
+    /**
+     * store the filter attribute.
+     */
+    private String m_filter;
+
+    /**
+     * store the value of the tag.
+     */
+    private String m_value;
+
+
+    /**
+     * get the extend attribute.
+     * @return a string which contains the value of the boolean
+     */
+    public String getExtend()
+    {
+        return m_extend;
+    }
+
+
+    /**
+     * set the extend attribute.
+     * @param extend new value for the extend attribute
+     */
+    public void setExtend( String extend )
+    {
+        m_extend = extend;
+    }
+
+
+    /**
+     * get the filter attribute.
+     * @return m_filter value
+     */
+    public String getFilter()
+    {
+        return m_filter;
+    }
+
+
+    /**
+     * set the filter attribute.
+     * @param filter new value for filter
+     */
+    public void setFilter( String filter )
+    {
+        m_filter = filter;
+    }
+
+
+    /**
+     * get multiple attribute.
+     * @return m_multiple value
+     */
+    public String getMultiple()
+    {
+        return m_multiple;
+    }
+
+
+    /**
+     * set multiple attribute.
+     * @param multiple new value for m_multiple
+     */
+    public void setMultiple( String multiple )
+    {
+        m_multiple = multiple;
+    }
+
+
+    /**
+     * get name attribute.
+     * @return m_name value
+     */
+    public String getName()
+    {
+        return m_name;
+    }
+
+
+    /**
+     * set name attribute.
+     * @param name new value for m_name
+     */
+    public void setName( String name )
+    {
+        m_name = name;
+    }
+
+
+    /**
+     * get the optional attribute.
+     * @return m_optional value
+     */
+    public String getOptional()
+    {
+        return m_optional;
+    }
+
+
+    /**
+     * set the optional attribute.
+     * @param optionnal new value for m_optional
+     */
+    public void setOptional( String optionnal )
+    {
+        m_optional = optionnal;
+    }
+
+
+    /**
+     * get value of the tag.
+     * @return value of this tag
+     */
+    public String getValue()
+    {
+        return m_value;
+    }
+
+
+    /**
+     * set the value of the tag.
+     * @param value new value for this tag
+     */
+    public void setValue( String value )
+    {
+        m_value = value;
+    }
+
+
+    /**
+     * transform this object to Node.
+     * 
+     * @param father father document for create Node
+     * @return node
+     */
+    public Node getNode( Document father )
+    {
+        Element require = father.createElement( "require" );
+        require.setAttribute( "name", getName() );
+        require.setAttribute( "filter", getFilter() );
+        require.setAttribute( "extend", getExtend() );
+        require.setAttribute( "multiple", getMultiple() );
+        require.setAttribute( "optional", getOptional() );
+        XmlHelper.setTextContent( require, getValue() );
+
+        return require;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ResourcesBundle.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ResourcesBundle.java
new file mode 100644
index 0000000..1e5f01d
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/ResourcesBundle.java
@@ -0,0 +1,672 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * this class describe all information by bundle.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourcesBundle
+{
+    /**
+     * store the bundle symbolic name.
+     */
+    private String m_symbolicName;
+
+    /**
+     * store the bundle presentation name.
+     */
+    private String m_presentationName;
+
+    /**
+     * store the bundle version.
+     */
+    private String m_version;
+
+    /**
+     * store the bundle URI.
+     */
+    private String m_uri;
+
+    /**
+     * store the bundle description.
+     */
+    private String m_description;
+
+    /**
+     * store the bundle size.
+     */
+    private String m_size;
+
+    /**
+     * store the bundle documentation.
+     */
+    private String m_documentation;
+
+    /**
+     * store the bundle source.
+     */
+    private String m_source;
+
+    /**
+     * store the bundle license.
+     */
+    private String m_license;
+
+    /**
+     * store the bundle id.
+     */
+    private String m_id;
+
+    /**
+     * store the bundle categories.
+     */
+    private List m_category = new ArrayList();
+
+    /**
+     * store the bundle capabilities.
+     */
+    private List m_capability = new ArrayList();
+
+    /**
+     * store the bundle requirement.
+     */
+    private List m_require = new ArrayList();
+
+    /**
+     * get the plugin logger.
+     */
+    private Log m_logger;
+
+
+    /**
+     * initialize logger.
+     * @param log log use by plugin
+     */
+    public ResourcesBundle( Log log )
+    {
+        m_logger = log;
+    }
+
+
+    public List getCapability()
+    {
+        return m_capability;
+    }
+
+
+    public void setCapability( List capability )
+    {
+        m_capability = capability;
+    }
+
+
+    public List getCategory()
+    {
+        return m_category;
+    }
+
+
+    public void setCategory( List category )
+    {
+        m_category = category;
+    }
+
+
+    public String getLicense()
+    {
+        return m_license;
+    }
+
+
+    public void setLicense( String license )
+    {
+        m_license = license;
+    }
+
+
+    public String getDescription()
+    {
+        return m_description;
+    }
+
+
+    public void setDescription( String description )
+    {
+        m_description = description;
+    }
+
+
+    public String getDocumentation()
+    {
+        return m_documentation;
+    }
+
+
+    public void setDocumentation( String documentation )
+    {
+        m_documentation = documentation;
+    }
+
+
+    public String getPresentationName()
+    {
+        return m_presentationName;
+    }
+
+
+    public void setPresentationName( String name )
+    {
+        m_presentationName = name;
+    }
+
+
+    public String getSize()
+    {
+        return m_size;
+    }
+
+
+    public void setSize( String size )
+    {
+        m_size = size;
+    }
+
+
+    public String getSymbolicName()
+    {
+        return m_symbolicName;
+    }
+
+
+    public void setSymbolicName( String name )
+    {
+        m_symbolicName = name;
+    }
+
+
+    public String getUri()
+    {
+        return m_uri;
+    }
+
+
+    public void setUri( String url )
+    {
+        m_uri = url;
+    }
+
+
+    public String getVersion()
+    {
+        return m_version;
+    }
+
+
+    public void setVersion( String version )
+    {
+        m_version = version;
+    }
+
+
+    public List getRequire()
+    {
+        return m_require;
+    }
+
+
+    public void setRequire( List require )
+    {
+        m_require = require;
+    }
+
+
+    public String getSource()
+    {
+        return m_source;
+    }
+
+
+    public void setSource( String source )
+    {
+        m_source = source;
+    }
+
+
+    public String getId()
+    {
+        return m_id;
+    }
+
+
+    public void setId( String id )
+    {
+        m_id = id;
+    }
+
+
+    /**
+     * add a new capability for this bundle description.
+     * @param capability the Capability to add
+     */
+    public void addCapability( Capability capability )
+    {
+        m_capability.add( capability );
+    }
+
+
+    /**
+     * add a new requirement for this bundle description.
+     * @param require th Require to add
+     */
+    public void addRequire( Require require )
+    {
+        m_require.add( require );
+    }
+
+
+    /**
+     * add a new category for this bundle decription.
+     * @param category the Category to add
+     */
+    public void addCategory( Category category )
+    {
+        m_category.add( category );
+    }
+
+
+    /**
+     * transform this object to Node.
+     * tranform all sub-object to node also
+     * @param father father document for create Node
+     * @return node
+     */
+    public Node getNode( Document father )
+    {
+        // return the complete resource tree
+        if ( !isValid() || getId() == null )
+        {
+            m_logger.error( "those properties was not defined:" + getInvalidProperties() );
+            return null;
+        }
+
+        Element resource = father.createElement( "resource" );
+        Element description = father.createElement( "description" );
+        Element size = father.createElement( "size" );
+        Element documentation = father.createElement( "documentation" );
+        Element source = father.createElement( "source" );
+        Element license = father.createElement( "license" );
+
+        resource.setAttribute( "id", getId() );
+        resource.setAttribute( "symbolicname", getSymbolicName() );
+        resource.setAttribute( "presentationname", getPresentationName() );
+        resource.setAttribute( "uri", getUri() );
+        resource.setAttribute( "version", getVersion() );
+
+        XmlHelper.setTextContent( description, getDescription() );
+        resource.appendChild( description );
+
+        XmlHelper.setTextContent( size, getSize() );
+        resource.appendChild( size );
+
+        if ( getDocumentation() != null )
+        {
+            XmlHelper.setTextContent( documentation, getDocumentation() );
+            resource.appendChild( documentation );
+        }
+
+        if ( getSource() != null )
+        {
+            XmlHelper.setTextContent( source, getSource() );
+            resource.appendChild( source );
+        }
+
+        if ( getLicense() != null )
+        {
+            XmlHelper.setTextContent( license, getLicense() );
+            resource.appendChild( license );
+        }
+
+        List list = getNodeCategories( father );
+        for ( int i = 0; i < list.size(); i++ )
+        {
+            resource.appendChild( ( Node ) list.get( i ) );
+        }
+
+        list = getNodeCapabilities( father );
+        for ( int i = 0; i < list.size(); i++ )
+        {
+            resource.appendChild( ( Node ) list.get( i ) );
+        }
+
+        list = getNodeRequirement( father );
+        for ( int i = 0; i < list.size(); i++ )
+        {
+            resource.appendChild( ( Node ) list.get( i ) );
+        }
+
+        return resource;
+    }
+
+
+    /**
+     * this method gets information form pom.xml to complete missing data from those given by user.
+     * @param project project information given by maven
+     * @param ebi bundle information extracted from bindex
+     * @return true
+     */
+    public boolean construct( MavenProject project, ExtractBindexInfo ebi )
+    {
+
+        if ( ebi.getPresentationName() != null )
+        {
+            setPresentationName( ebi.getPresentationName() );
+            if ( project.getName() != null )
+            {
+                m_logger.debug( "pom property override:<presentationname> " + project.getName() );
+            }
+        }
+        else
+        {
+            setPresentationName( project.getName() );
+        }
+
+        if ( ebi.getSymbolicName() != null )
+        {
+            setSymbolicName( ebi.getSymbolicName() );
+            if ( project.getArtifactId() != null )
+            {
+                m_logger.debug( "pom property override:<symbolicname> " + project.getArtifactId() );
+            }
+        }
+        else
+        {
+            setSymbolicName( project.getArtifactId() );
+        }
+
+        if ( ebi.getVersion() != null )
+        {
+            setVersion( ebi.getVersion() );
+            if ( project.getVersion() != null )
+            {
+                m_logger.debug( "pom property override:<version> " + project.getVersion() );
+            }
+        }
+        else
+        {
+            setVersion( project.getVersion() );
+        }
+
+        if ( ebi.getId() != null )
+        {
+            setId( ebi.getId() );
+        }
+
+        if ( ebi.getDescription() != null )
+        {
+            setDescription( ebi.getDescription() );
+            if ( project.getDescription() != null )
+            {
+                m_logger.debug( "pom property override:<description> " + project.getDescription() );
+            }
+        }
+        else
+        {
+            setDescription( project.getDescription() );
+        }
+
+        if ( ebi.getDocumentation() != null )
+        {
+            setDocumentation( ebi.getDocumentation() );
+            if ( project.getUrl() != null )
+            {
+                m_logger.debug( "pom property override:<documentation> " + project.getUrl() );
+            }
+        }
+        else
+        {
+            setDocumentation( project.getUrl() );
+        }
+
+        if ( ebi.getSource() != null )
+        {
+            setSource( ebi.getSource() );
+            if ( project.getScm() != null )
+            {
+                m_logger.debug( "pom property override:<source> " + project.getScm() );
+            }
+        }
+        else
+        {
+            String src = null;
+            if ( project.getScm() != null )
+            {
+                src = project.getScm().getUrl();
+            }
+            setSource( src );
+        }
+
+        if ( ebi.getLicense() != null )
+        {
+            setLicense( ebi.getLicense() );
+            String lic = null;
+            List l = project.getLicenses();
+            Iterator it = l.iterator();
+            while ( it.hasNext() )
+            {
+                if ( it.next() != null )
+                {
+                    m_logger.debug( "pom property override:<license> " + lic );
+                    break;
+                }
+            }
+        }
+        else
+        {
+            String lic = null;
+            List l = project.getLicenses();
+            Iterator it = l.iterator();
+            while ( it.hasNext() )
+            {
+                lic = it.next() + ";";
+            }
+
+            setLicense( lic );
+        }
+
+        // create the first capability (ie : bundle)
+        Capability capability = new Capability();
+        capability.setName( "bundle" );
+        PElement p = new PElement();
+        p.setN( "manifestversion" );
+        p.setV( "2" );
+        capability.addP( p );
+
+        p = new PElement();
+        p.setN( "presentationname" );
+        p.setV( getPresentationName() );
+        capability.addP( p );
+
+        p = new PElement();
+        p.setN( "symbolicname" );
+        p.setV( getSymbolicName() );
+        capability.addP( p );
+
+        p = new PElement();
+        p.setN( "version" );
+        p.setT( "version" );
+        p.setV( getVersion() );
+        capability.addP( p );
+
+        addCapability( capability );
+
+        List capabilities = ebi.getCapabilities();
+        for ( int i = 0; i < capabilities.size(); i++ )
+        {
+            addCapability( ( Capability ) capabilities.get( i ) );
+        }
+
+        List requirement = ebi.getRequirement();
+        for ( int i = 0; i < requirement.size(); i++ )
+        {
+            addRequire( ( Require ) requirement.get( i ) );
+        }
+
+        // we also add the goupId
+        Category category = new Category();
+        category.setId( project.getGroupId() );
+        addCategory( category );
+
+        return true;
+    }
+
+
+    /**
+     * return if the bundle resource is complete.
+     * @return false if an information is missing, else true
+     */
+    public boolean isValid()
+    {
+        // we must verify required properties are present
+        return getPresentationName() != null && getSymbolicName() != null && getVersion() != null && getUri() != null
+            && getSize() != null;
+    }
+
+
+    /**
+     * test if this bundle has the same symbolicname, and version number.
+     * @param symbolicName symbolicName to compare with current bundle
+     * @param presentationName presentationName to compare with current bundlde
+     * @param version version to compare with current bundle
+     * @return true if the information are the same, else false
+     */
+    public boolean isSameBundleResource( String symbolicName, String version )
+    {
+        if ( isValid() )
+        {
+            return ( symbolicName.compareTo( getSymbolicName() ) == 0 ) && ( version.compareTo( getVersion() ) == 0 );
+        }
+
+        return false;
+    }
+
+
+    /**
+     * return a list of categories transformed to node.
+     * @param father father document to create node from same document
+     * @return List of Node
+     */
+    private List getNodeCategories( Document father )
+    {
+        List listNode = new ArrayList();
+        List listCategory = getCategory();
+        for ( int i = 0; i < listCategory.size(); i++ )
+        {
+            listNode.add( ( ( Category ) listCategory.get( i ) ).getNode( father ) );
+        }
+        return listNode;
+    }
+
+
+    /**
+     * return a list of capabilities transformed to node.
+     * @param father father document to create node from same document
+     * @return List of Node
+     */
+    private List getNodeCapabilities( Document father )
+    {
+        List listNode = new ArrayList();
+        List listCapability = getCapability();
+        for ( int i = 0; i < listCapability.size(); i++ )
+        {
+            listNode.add( ( ( Capability ) listCapability.get( i ) ).getNode( father ) );
+        }
+        return listNode;
+    }
+
+
+    /**
+     * return a list of requirement transformed to node.
+     * @param father father document to create node from same document
+     * @return List of Node.
+     */
+    private List getNodeRequirement( Document father )
+    {
+        List listNode = new ArrayList();
+        List listRequirement = getRequire();
+        for ( int i = 0; i < listRequirement.size(); i++ )
+        {
+            listNode.add( ( ( Require ) listRequirement.get( i ) ).getNode( father ) );
+        }
+        return listNode;
+    }
+
+
+    /**
+     * return the list of properties not define in this bundle resource.
+     * @return list of properties not define
+     */
+    private String getInvalidProperties()
+    {
+        if ( isValid() )
+        {
+            if ( getId() == null )
+            {
+                return "id";
+            }
+
+            return "";
+        }
+        String result = "";
+        if ( getPresentationName() == null )
+        {
+            result = result + "presentationName;";
+        }
+        if ( getSymbolicName() == null )
+        {
+            result = result + "symbolicName;";
+        }
+        if ( getVersion() == null )
+        {
+            result = result + "version;";
+        }
+        if ( getUri() == null )
+        {
+            result = result + "Uri;";
+        }
+        if ( getSize() == null )
+        {
+            result = result + "Size";
+        }
+        return result;
+    }
+
+}
diff --git a/bundleplugin/src/main/java/org/apache/felix/obr/plugin/XmlHelper.java b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/XmlHelper.java
new file mode 100644
index 0000000..5ad06fb
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obr/plugin/XmlHelper.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.obr.plugin;
+
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+/**
+ * Provide XML helper methods to support pre-Java5 runtimes
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class XmlHelper
+{
+    /**
+     * based on public Java5 javadoc of org.w3c.dom.Node.getTextContent method
+     */
+    public static String getTextContent( Node node )
+    {
+        switch ( node.getNodeType() )
+        {
+            case Node.ELEMENT_NODE:
+            case Node.ATTRIBUTE_NODE:
+            case Node.ENTITY_NODE:
+            case Node.ENTITY_REFERENCE_NODE:
+            case Node.DOCUMENT_FRAGMENT_NODE:
+                return mergeTextContent( node.getChildNodes() );
+            case Node.TEXT_NODE:
+            case Node.CDATA_SECTION_NODE:
+            case Node.COMMENT_NODE:
+            case Node.PROCESSING_INSTRUCTION_NODE:
+                return node.getNodeValue();
+            case Node.DOCUMENT_NODE:
+            case Node.DOCUMENT_TYPE_NODE:
+            case Node.NOTATION_NODE:
+            default:
+                return null;
+        }
+    }
+
+
+    /**
+     * based on the following quote from public Java5 javadoc of org.w3c.dom.Node.getTextContent method:
+     * 
+     * "concatenation of the textContent attribute value of every child node, excluding COMMENT_NODE and
+     * PROCESSING_INSTRUCTION_NODE nodes. This is the empty string if the node has no children"
+     */
+    private static String mergeTextContent( NodeList nodes )
+    {
+        StringBuffer buf = new StringBuffer();
+        for ( int i = 0; i < nodes.getLength(); i++ )
+        {
+            Node n = nodes.item( i );
+            final String text;
+
+            switch ( n.getNodeType() )
+            {
+                case Node.COMMENT_NODE:
+                case Node.PROCESSING_INSTRUCTION_NODE:
+                    text = null;
+                    break;
+                default:
+                    text = getTextContent( n );
+                    break;
+            }
+
+            if ( text != null )
+            {
+                buf.append( text );
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * based on public Java5 javadoc of org.w3c.dom.Node.setTextContent method
+     */
+    public static void setTextContent( Node node, final String text )
+    {
+        while ( node.hasChildNodes() )
+        {
+            node.removeChild( node.getFirstChild() );
+        }
+
+        if ( text != null && text.length() > 0 )
+        {
+            Node textNode = node.getOwnerDocument().createTextNode( text );
+            node.appendChild( textNode );
+        }
+    }
+}
diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/BundleInfo.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/BundleInfo.java
new file mode 100644
index 0000000..2e68a9a
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/BundleInfo.java
@@ -0,0 +1,506 @@
+/*

+ * $Id: BundleInfo.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import java.io.*;

+import java.net.URL;

+import java.util.*;

+import java.util.zip.*;

+

+import org.osgi.service.obr.Resource;

+

+/**

+ * Convert a bundle to a generic resource description and store its local

+ * dependencies (like for example a license file in the JAR) in a zip file.

+ * 

+ * @version $Revision: 44 $

+ */

+public class BundleInfo {

+	Manifest	manifest;

+	File		bundleJar;

+	ZipFile		jar;

+	String		license;

+	Properties	localization;

+	RepositoryImpl	repository;

+

+	/**

+	 * Parse a zipFile from the file system. We only need the manifest and the

+	 * localization. So a zip file is used to minimze memory consumption.

+	 * 

+	 * @param bundleJar Path name

+	 * @throws Exception Any errors that occur

+	 */

+	public BundleInfo(RepositoryImpl repository, File bundleJar) throws Exception {

+		this.bundleJar = bundleJar;

+		this.repository = repository;

+		

+		if (!this.bundleJar.exists())

+			throw new FileNotFoundException(bundleJar.toString());

+

+		jar = new ZipFile(bundleJar);

+		ZipEntry entry = jar.getEntry("META-INF/MANIFEST.MF");

+		if (entry == null)

+			throw new FileNotFoundException("No Manifest in "

+					+ bundleJar.toString());

+		manifest = new Manifest(jar.getInputStream(entry));

+	}

+

+	public BundleInfo(Manifest manifest) throws Exception {

+		this.manifest = manifest;

+	}

+

+	/**

+	 * Convert the bundle to a Resource. All URIs are going to be abslute, but

+	 * could be local.

+	 * 

+	 * @return the resource

+	 * @throws Exception

+	 */

+	public ResourceImpl build() throws Exception {

+		ResourceImpl resource;

+		// Setup the manifest

+		// and create a resource

+		resource = new ResourceImpl(repository, manifest.getSymbolicName(), manifest

+				.getVersion());

+

+		try {

+

+			// Calculate the location URL of the JAR

+			URL location = new URL("jar:" + bundleJar.toURL().toString() + "!/");

+			resource.setURL(bundleJar.toURL());

+			resource.setFile(bundleJar);

+

+			doReferences(resource, location);

+			doSize(resource);

+			doCategories(resource);

+			doImportExportServices(resource);

+			doDeclarativeServices(resource);

+			doFragment(resource);

+			doRequires(resource);

+			doBundle(resource);

+			doExports(resource);

+			doImports(resource);

+			doExecutionEnvironment(resource);

+

+			return resource;

+		}

+		finally {

+			try {

+				jar.close();

+			}

+			catch (Exception e) {

+				// ignore

+			}

+		}

+	}

+

+	/**

+	 * Check the size and add it.

+	 * 

+	 * @param resource

+	 */

+	void doSize(ResourceImpl resource) {

+		long size = bundleJar.length();

+		if (size > 0)

+			resource.setSize(size);

+	}

+

+	/**

+	 * Find the categories, break them up and add them.

+	 * 

+	 * @param resource

+	 */

+	void doCategories(ResourceImpl resource) {

+		for (int i = 0; i < manifest.getCategories().length; i++) {

+			String category = manifest.getCategories()[i];

+			resource.addCategory(category);

+		}

+	}

+

+	void doReferences(ResourceImpl resource, URL location) {

+		// Presentation name

+		String name = translated("Bundle-Name");

+		if (name != null)

+			resource.setPresentationName(name);

+

+		// Handle license. -l allows a global license

+		// set when no license is included.

+

+		String license = translated("Bundle-License");

+		if (license != null)

+			resource.setLicense(toURL(location, license));

+		else if (this.license != null)

+			resource.setLicense(toURL(location, this.license));

+

+		String description = translated("Bundle-Description");

+		if (description != null)

+			resource.setDescription(description);

+

+		String copyright = translated("Bundle-Copyright");

+		if (copyright != null)

+			resource.setCopyright(copyright);

+

+		String documentation = translated("Bundle-DocURL");

+		if (documentation != null)

+			resource.setDocumentation(toURL(location, documentation));

+

+		String source = manifest.getValue("Bundle-Source");

+		if (source != null)

+			resource.setSource(toURL(location, source));

+	}

+

+	URL toURL(URL location, String source) {

+		try {

+			return new URL(location, source);

+		}

+		catch (Exception e) {

+			System.err.println("Error in converting url: " + location + " : "

+					+ source);

+			return null;

+		}

+	}

+

+	void doDeclarativeServices(ResourceImpl resource) throws Exception {

+		String serviceComponent = manifest.getValue("service-component");

+		if (serviceComponent == null)

+			return;

+

+		StringTokenizer st = new StringTokenizer(serviceComponent, " ,\t");

+		String parts[] = new String[st.countTokens()];

+		for (int i = 0; i < parts.length; i++)

+			parts[i] = st.nextToken();

+

+		for (int i = 0; i < parts.length; i++) {

+			ZipEntry entry = jar.getEntry(parts[i]);

+			if (entry == null) {

+				System.err.println("Bad Service-Component header: "

+						+ serviceComponent + ", no such file " + parts[i]);

+			}

+			InputStream in = jar.getInputStream(entry);

+			// TODO parse declarative services files.

+			in.close();

+		}

+	}

+

+	void doImportExportServices(ResourceImpl resource) throws IOException {

+		String importServices = manifest.getValue("import-service");

+		if (importServices != null) {

+			List entries = manifest.getEntries(importServices);

+			for (Iterator i = entries.iterator(); i.hasNext();) {

+				ManifestEntry entry = (ManifestEntry) i.next();

+				RequirementImpl ri = new RequirementImpl("service");

+				ri.setFilter(createServiceFilter(entry));

+				ri.setComment("Import Service " + entry.getName());

+

+				// TODO the following is arbitrary

+				ri.setOptional(false);

+				ri.setMultiple(true);

+				resource.addRequirement(ri);

+			}

+		}

+

+		String exportServices = manifest.getValue("export-service");

+		if (exportServices != null) {

+			List entries = manifest.getEntries(exportServices);

+			for (Iterator i = entries.iterator(); i.hasNext();) {

+				ManifestEntry entry = (ManifestEntry) i.next();

+				CapabilityImpl cap = createServiceCapability(entry);

+				resource.addCapability(cap);

+			}

+		}

+	}

+

+	String translated(String key) {

+		return translate(manifest.getValue(key));

+	}

+

+	void doFragment(ResourceImpl resource) {

+		// Check if we are a fragment

+		ManifestEntry entry = manifest.getHost();

+		if (entry == null) {

+			return;

+		}

+		else {

+			// We are a fragment, create a requirement

+			// to our host.

+			RequirementImpl r = new RequirementImpl("bundle");

+			StringBuffer sb = new StringBuffer();

+			sb.append("(&(symbolicname=");

+			sb.append(entry.getName());

+			sb.append(")(version>=");

+			sb.append(entry.getVersion());

+			sb.append("))");

+			r.setFilter(sb.toString());

+			r.setComment("Required Host " + entry.getName() );

+			r.setExtend(true);

+			r.setOptional(false);

+			r.setMultiple(false);

+			resource.addRequirement(r);

+

+			// And insert a capability that we are available

+			// as a fragment. ### Do we need that with extend?

+			CapabilityImpl capability = new CapabilityImpl("fragment");

+			capability.addProperty("host", entry.getName());

+			capability.addProperty("version", entry.getVersion());

+			resource.addCapability(capability);

+		}

+	}

+

+	void doRequires(ResourceImpl resource) {

+		List entries = manifest.getRequire();

+		if (entries == null)

+			return;

+

+		for (Iterator i = entries.iterator(); i.hasNext();) {

+			ManifestEntry entry = (ManifestEntry) i.next();

+			RequirementImpl r = new RequirementImpl("bundle");

+

+			StringBuffer sb = new StringBuffer();

+			sb.append("(&(symbolicname=");

+			sb.append(entry.getName());

+			sb.append(")(version>=");

+			sb.append(entry.getVersion());

+			sb.append("))");

+			r.setFilter(sb.toString());

+			r.setComment("Require Bundle " + entry.getName() + "; "

+					+ entry.getVersion());

+			if (entry.directives == null

+					|| "true".equalsIgnoreCase((String) entry.directives

+							.get("resolution")))

+				r.setOptional(false);

+			else

+				r.setOptional(true);

+			resource.addRequirement(r);

+		}

+	}

+

+	void doExecutionEnvironment(ResourceImpl resource) {

+		String[] parts = manifest.getRequiredExecutionEnvironments();

+		if (parts == null)

+			return;

+

+		StringBuffer sb = new StringBuffer();

+		sb.append("(|");

+		for (int i = 0; i < parts.length; i++) {

+			String part = parts[i];

+			sb.append("(ee=");

+			sb.append(part);

+			sb.append(")");

+		}

+		sb.append(")");

+

+		RequirementImpl req = new RequirementImpl("ee");

+		req.setFilter(sb.toString());

+		req.setComment("Execution Environment " + sb.toString());

+		resource.addRequirement(req);

+	}

+

+	void doImports(ResourceImpl resource) {

+		List requirements = new ArrayList();

+		List packages = manifest.getImports();

+		if (packages == null)

+			return;

+

+		for (Iterator i = packages.iterator(); i.hasNext();) {

+			ManifestEntry pack = (ManifestEntry) i.next();

+			RequirementImpl requirement = new RequirementImpl("package");

+

+			createImportFilter(requirement, "package", pack);

+			requirement.setComment("Import package " + pack);

+			requirements.add(requirement);

+		}

+		for (Iterator i = requirements.iterator(); i.hasNext();)

+			resource.addRequirement((RequirementImpl) i.next());

+	}

+

+	String createServiceFilter(ManifestEntry pack) {

+		StringBuffer filter = new StringBuffer();

+		filter.append("(service=");

+		filter.append(pack.getName());

+		filter.append(")");

+		return filter.toString();

+	}

+

+	void createImportFilter(RequirementImpl req, String name, ManifestEntry pack) {

+		StringBuffer filter = new StringBuffer();

+		filter.append("(&(");

+		filter.append(name);

+		filter.append("=");

+		filter.append(pack.getName());

+		filter.append(")");

+		VersionRange version = pack.getVersion();

+		if (version != null) {

+			if ( version.isRange() ) {

+				filter.append("(version");

+				filter.append(">");

+				if (version.includeLow())

+					filter.append("=");

+				filter.append(version.low);

+				filter.append(")");

+

+				filter.append("(version");

+				filter.append("<");

+				if (version.includeHigh())

+					filter.append("=");

+				filter.append(version.high);

+				filter.append(")");

+			}

+			else {

+				filter.append("(version>=");

+				filter.append(pack.getVersion());

+				filter.append(")");

+			}

+		}

+		Map attributes = pack.getAttributes();

+		Set attrs = doImportPackageAttributes(req, filter, attributes);

+		if (attrs.size() > 0) {

+			String del = "";

+			filter.append("(mandatory:<*");

+			for (Iterator i = attrs.iterator(); i.hasNext();) {

+				filter.append(del);

+				filter.append(i.next());

+				del = ", ";

+			}

+			filter.append(")");

+		}

+		filter.append(")");

+		req.setFilter(filter.toString());

+	}

+

+	Set doImportPackageAttributes(RequirementImpl req, StringBuffer filter,

+			Map attributes) {

+		HashSet set = new HashSet();

+

+		if (attributes != null)

+			for (Iterator i = attributes.keySet().iterator(); i.hasNext();) {

+				String attribute = (String) i.next();

+				String value = (String) attributes.get(attribute);

+				if (attribute.equalsIgnoreCase("specification-version")

+						|| attribute.equalsIgnoreCase("version"))

+					continue;

+				else if (attribute.equalsIgnoreCase("resolution:")) {

+					req.setOptional(value.equalsIgnoreCase("optional"));

+				}

+				if (attribute.endsWith(":")) {

+					// Ignore

+				}

+				else {

+					filter.append("(");

+					filter.append(attribute);

+					filter.append("=");

+					filter.append(attributes.get(attribute));

+					filter.append(")");

+					set.add(attribute);

+				}

+			}

+		return set;

+	}

+

+	void doBundle(ResourceImpl resource) {

+		CapabilityImpl capability = new CapabilityImpl("bundle");

+		capability.addProperty("symbolicname", manifest.getSymbolicName());

+		if (manifest.getValue("Bundle-Name") != null)

+			capability.addProperty(

+					Resource.PRESENTATION_NAME,

+					translated("Bundle-Name"));

+		capability.addProperty("version", manifest.getVersion());

+		capability

+				.addProperty("manifestversion", manifest.getManifestVersion());

+

+		/**

+		 * Is this needed TODO

+		 */

+		ManifestEntry host = manifest.getHost();

+		if (host != null) {

+			capability.addProperty("host", host.getName());

+			if (host.getVersion() != null)

+				capability.addProperty("version", host.getVersion());

+		}

+		resource.addCapability(capability);

+	}

+

+	void doExports(ResourceImpl resource) {

+		List capabilities = new ArrayList();

+		List packages = manifest.getExports();

+		if (packages != null) {

+			for (Iterator i = packages.iterator(); i.hasNext();) {

+				ManifestEntry pack = (ManifestEntry) i.next();

+				CapabilityImpl capability = createCapability("package", pack);

+				capabilities.add(capability);

+			}

+        }

+		for (Iterator i = capabilities.iterator(); i.hasNext();)

+			resource.addCapability((CapabilityImpl) i.next());

+	}

+

+	CapabilityImpl createServiceCapability(ManifestEntry pack) {

+		CapabilityImpl capability = new CapabilityImpl("service");

+		capability.addProperty("service", pack.getName());

+		return capability;

+	}

+

+	CapabilityImpl createCapability(String name, ManifestEntry pack) {

+		CapabilityImpl capability = new CapabilityImpl(name);

+		capability.addProperty(name, pack.getName());

+		capability.addProperty("version", pack.getVersion());

+		Map attributes = pack.getAttributes();

+		if (attributes != null)

+			for (Iterator at = attributes.keySet().iterator(); at.hasNext();) {

+				String key = (String) at.next();

+				if (key.equalsIgnoreCase("specification-version")

+						|| key.equalsIgnoreCase("version"))

+					continue;

+				else {

+					Object value = attributes.get(key);

+					capability.addProperty(key, value);

+				}

+			}

+		return capability;

+	}

+

+	String translate(String s) {

+		if (s == null)

+			return null;

+

+		if (!s.startsWith("%")) {

+			return s;

+		}

+

+		if (localization == null)

+			try {

+				localization = new Properties();

+				String path = manifest

+						.getValue("Bundle-Localization", "bundle");

+				path += ".properties";

+				InputStream in = jar.getInputStream(new ZipEntry(path));

+				if (in != null) {

+					localization.load(in);

+					in.close();

+				}

+			}

+			catch (IOException e) {

+				e.printStackTrace();

+			}

+		s = s.substring(1);

+		return localization.getProperty(s, s);

+	}

+

+	File getZipFile() {

+		return bundleJar;

+	}

+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java
new file mode 100644
index 0000000..45d2d1b
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/CapabilityImpl.java
@@ -0,0 +1,109 @@
+/*

+ * $Id: CapabilityImpl.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import java.util.*;

+

+import org.osgi.service.obr.Capability;

+import org.xmlpull.v1.XmlPullParser;

+

+

+

+public class CapabilityImpl implements Capability {

+	String				name;

+	Map	properties	= new TreeMap();

+

+	public CapabilityImpl(String name) {

+		this.name = name;

+	}

+

+	public CapabilityImpl(XmlPullParser parser) throws Exception {

+		parser.require(XmlPullParser.START_TAG, null, "capability");

+		name = parser.getAttributeValue(null,"name");

+		while ( parser.nextTag() == XmlPullParser.START_TAG ) {

+			if ( parser.getName().equals("p")) {

+				String name = parser.getAttributeValue(null,"n");

+				String value = parser.getAttributeValue(null,"v");

+				String type = parser.getAttributeValue(null,"t");

+				Object v = value;

+

+				if ( "nummeric".equals(type))

+					v = new Long(value);

+				else if ( "version".equals(type))

+					v = new VersionRange(value);

+				addProperty(name,v);

+			}

+			parser.next();

+			parser.require(XmlPullParser.END_TAG, null, "p" );

+		}

+		parser.require(XmlPullParser.END_TAG, null, "capability" );

+	}

+

+

+	public void addProperty(String key, Object value) {

+		List values = (List) properties.get(key);

+		if (values == null) {

+			values = new ArrayList();

+			properties.put(key, values);

+		}

+		values.add(value);

+	}

+

+	public Tag toXML() {

+		return toXML(this);

+	}

+	

+	public static Tag toXML(Capability capability) {

+		Tag tag = new Tag("capability");

+		tag.addAttribute("name", capability.getName());

+		Map properties = capability.getProperties();

+		for ( Iterator k= properties.keySet().iterator(); k.hasNext(); ) {

+			String key = (String) k.next();

+			List values = (List) properties.get(key);

+			for ( Iterator v = values.iterator(); v.hasNext(); ) {

+				Object value = v.next();

+				Tag p = new Tag("p");

+				tag.addContent(p);

+				p.addAttribute("n", key);

+				if ( value != null )

+					p.addAttribute("v", value.toString());

+				else

+					System.out.println("Missing value " + key);

+				String type = null;

+				if (value instanceof Number )

+					type = "number";

+				else if (value.getClass() == VersionRange.class)

+					type = "version";

+				if (type != null)

+					p.addAttribute("t", type);

+			}

+		}

+		return tag;

+	}

+

+

+	public String getName() {

+		return name;

+	}

+

+

+	public Map getProperties() {

+		return properties;

+	}

+

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/FilterImpl.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/FilterImpl.java
new file mode 100644
index 0000000..bc12314
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/FilterImpl.java
@@ -0,0 +1,437 @@
+/*

+ * $Id: FilterImpl.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) 2000 Gatespace AB. All Rights Reserved.

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import java.lang.reflect.*;

+import java.math.BigInteger;

+import java.util.*;

+

+public class FilterImpl {

+	final char		WILDCARD	= 65535;

+

+	final int		EQ			= 0;

+	final int		LE			= 1;

+	final int		GE			= 2;

+	final int		APPROX		= 3;

+	final int		LESS		= 4;

+	final int		GREATER		= 5;

+	final int		SUBSET		= 6;

+	final int		SUPERSET	= 7;

+

+	private String	filter;

+

+	abstract class Query {

+		static final String	GARBAGE		= "Trailing garbage";

+		static final String	MALFORMED	= "Malformed query";

+		static final String	EMPTY		= "Empty list";

+		static final String	SUBEXPR		= "No subexpression";

+		static final String	OPERATOR	= "Undefined operator";

+		static final String	TRUNCATED	= "Truncated expression";

+		static final String	EQUALITY	= "Only equality supported";

+

+		private String		tail;

+

+		boolean match() throws IllegalArgumentException {

+			tail = filter;

+			boolean val = doQuery();

+			if (tail.length() > 0)

+				error(GARBAGE);

+			return val;

+		}

+

+		private boolean doQuery() throws IllegalArgumentException {

+			if (tail.length() < 3 || !prefix("("))

+				error(MALFORMED);

+			boolean val;

+

+			switch (tail.charAt(0)) {

+				case '&' :

+					val = doAnd();

+					break;

+				case '|' :

+					val = doOr();

+					break;

+				case '!' :

+					val = doNot();

+					break;

+				default :

+					val = doSimple();

+					break;

+			}

+

+			if (!prefix(")"))

+				error(MALFORMED);

+			return val;

+		}

+

+		private boolean doAnd() throws IllegalArgumentException {

+			tail = tail.substring(1);

+			boolean val = true;

+			if (!tail.startsWith("("))

+				error(EMPTY);

+			do {

+				if (!doQuery())

+					val = false;

+			} while (tail.startsWith("("));

+			return val;

+		}

+

+		private boolean doOr() throws IllegalArgumentException {

+			tail = tail.substring(1);

+			boolean val = false;

+			if (!tail.startsWith("("))

+				error(EMPTY);

+			do {

+				if (doQuery())

+					val = true;

+			} while (tail.startsWith("("));

+			return val;

+		}

+

+		private boolean doNot() throws IllegalArgumentException {

+			tail = tail.substring(1);

+			if (!tail.startsWith("("))

+				error(SUBEXPR);

+			return !doQuery();

+		}

+

+		private boolean doSimple() throws IllegalArgumentException {

+			int op = 0;

+			Object attr = getAttr();

+

+			if (prefix("="))

+				op = EQ;

+			else if (prefix("<="))

+				op = LE;

+			else if (prefix(">="))

+				op = GE;

+			else if (prefix("~="))

+				op = APPROX;

+			else if (prefix("*>"))

+				op = SUPERSET;

+			else if (prefix("<*"))

+				op = SUBSET;

+			else if (prefix("<"))

+				op = LESS;

+			else if (prefix(">"))

+				op = GREATER;

+			else

+				error(OPERATOR);

+

+			return compare(attr, op, getValue());

+		}

+

+		private boolean prefix(String pre) {

+			if (!tail.startsWith(pre))

+				return false;

+			tail = tail.substring(pre.length());

+			return true;

+		}

+

+		private Object getAttr() {

+			int len = tail.length();

+			int ix = 0;

+			label: for (; ix < len; ix++) {

+				switch (tail.charAt(ix)) {

+					case '(' :

+					case ')' :

+					case '<' :

+					case '>' :

+					case '=' :

+					case '~' :

+					case '*' :

+					case '}' :

+					case '{' :

+					case '\\' :

+						break label;

+				}

+			}

+			String attr = tail.substring(0, ix).toLowerCase();

+			tail = tail.substring(ix);

+			return getProp(attr);

+		}

+

+		abstract Object getProp(String key);

+

+		private String getValue() {

+			StringBuffer sb = new StringBuffer();

+			int len = tail.length();

+			int ix = 0;

+			label: for (; ix < len; ix++) {

+				char c = tail.charAt(ix);

+				switch (c) {

+					case '(' :

+					case ')' :

+						break label;

+					case '*' :

+						sb.append(WILDCARD);

+						break;

+					case '\\' :

+						if (ix == len - 1)

+							break label;

+						sb.append(tail.charAt(++ix));

+						break;

+					default :

+						sb.append(c);

+						break;

+				}

+			}

+			tail = tail.substring(ix);

+			return sb.toString();

+		}

+

+		private void error(String m) throws IllegalArgumentException {

+			throw new IllegalArgumentException(m + " " + tail);

+		}

+

+		private boolean compare(Object obj, int op, String s) {

+			if (obj == null) {

+				// No value is ok for a subset

+				if (op == SUBSET)

+					return true;

+

+				// No value is ok for a superset when the value is

+				// empty

+				if (op == SUPERSET) {

+					return s.trim().length() == 0;

+				}

+

+				return false;

+			}

+			try {

+				Class numClass = obj.getClass();

+				if (numClass == String.class) {

+					return compareString((String) obj, op, s);

+				}

+				else if (numClass == Character.class) {

+					return compareString(obj.toString(), op, s);

+				}

+				else if (numClass == Long.class) {

+					return compareSign(op, Long.valueOf(s)

+							.compareTo((Long) obj));

+				}

+				else if (numClass == Integer.class) {

+					return compareSign(op, Integer.valueOf(s).compareTo(

+							(Integer) obj));

+				}

+				else if (numClass == Short.class) {

+					return compareSign(op, Short.valueOf(s).compareTo(

+							(Short) obj));

+				}

+				else if (numClass == Byte.class) {

+					return compareSign(op, Byte.valueOf(s)

+							.compareTo((Byte) obj));

+				}

+				else if (numClass == Double.class) {

+					return compareSign(op, Double.valueOf(s).compareTo(

+							(Double) obj));

+				}

+				else if (numClass == Float.class) {

+					return compareSign(op, Float.valueOf(s).compareTo(

+							(Float) obj));

+				}

+				else if (numClass == Boolean.class) {

+					if (op != EQ)

+						return false;

+					int a = Boolean.valueOf(s).booleanValue() ? 1 : 0;

+					int b = ((Boolean) obj).booleanValue() ? 1 : 0;

+					return compareSign(op, a - b);

+				}

+				else if (numClass == BigInteger.class) {

+					return compareSign(op, new BigInteger(s)

+							.compareTo((BigInteger) obj));

+				}

+				else if (obj instanceof Collection) {

+					if (op == SUBSET || op == SUPERSET) {

+						StringSet set = new StringSet(s);

+						if (op == SUBSET)

+							return set.containsAll((Collection) obj);

+						else

+							return ((Collection) obj).containsAll(set);

+					}

+

+					for (Iterator i = ((Collection) obj).iterator(); i

+							.hasNext();) {

+						Object element = i.next();

+						if (compare(element, op, s))

+							return true;

+					}

+				}

+				else if (numClass.isArray()) {

+					int len = Array.getLength(obj);

+					for (int i = 0; i < len; i++)

+						if (compare(Array.get(obj, i), op, s))

+							return true;

+				}

+				else {

+					try {

+						if (op == SUPERSET || op == SUBSET) {

+							StringSet set = new StringSet(s);

+							if (op == SUPERSET)

+								return set.contains(obj);

+							else

+								return set.size() == 0

+										|| (set.size() == 1 && set.iterator()

+												.next().equals(obj));

+						}

+						else {

+							Constructor constructor = numClass

+									.getConstructor(new Class[] {String.class});

+							Object instance = constructor

+									.newInstance(new Object[] {s});

+							switch (op) {

+								case EQ :

+									return obj.equals(instance);

+								case LESS :

+									return ((Comparable) obj)

+											.compareTo(instance) < 0;

+								case GREATER :

+									return ((Comparable) obj)

+											.compareTo(instance) > 0;

+								case LE :

+									return ((Comparable) obj)

+											.compareTo(instance) <= 0;

+								case GE :

+									return ((Comparable) obj)

+											.compareTo(instance) >= 0;

+							}

+						}

+					}

+					catch (Exception e) {

+						e.printStackTrace();

+						// Ignore

+					}

+				}

+			}

+			catch (Exception e) {

+			}

+			return false;

+		}

+	}

+

+	class DictQuery extends Query {

+		private Map	dict;

+

+		DictQuery(Map dict) {

+			this.dict = dict;

+		}

+

+		Object getProp(String key) {

+			return dict.get(key);

+		}

+	}

+

+	public FilterImpl(String filter) throws IllegalArgumentException {

+		// NYI: Normalize the filter string?

+		this.filter = filter;

+		if (filter == null || filter.length() == 0)

+			throw new IllegalArgumentException("Null query");

+	}

+

+	public boolean match(Map dict) {

+		try {

+			return new DictQuery(dict).match();

+		}

+		catch (IllegalArgumentException e) {

+			return false;

+		}

+	}

+

+	public String toString() {

+		return filter;

+	}

+

+	public boolean equals(Object obj) {

+		return obj != null && obj instanceof FilterImpl

+				&& filter.equals(((FilterImpl) obj).filter);

+	}

+

+	public int hashCode() {

+		return filter.hashCode();

+	}

+

+	boolean compareString(String s1, int op, String s2) {

+		switch (op) {

+			case EQ :

+				return patSubstr(s1, s2);

+			case APPROX :

+				return patSubstr(fixupString(s1), fixupString(s2));

+			default :

+				return compareSign(op, s2.compareTo(s1));

+		}

+	}

+

+	boolean compareSign(int op, int cmp) {

+		switch (op) {

+			case LE :

+				return cmp >= 0;

+			case GE :

+				return cmp <= 0;

+			case EQ :

+				return cmp == 0;

+			default : /* APPROX */

+				return cmp == 0;

+		}

+	}

+

+	String fixupString(String s) {

+		StringBuffer sb = new StringBuffer();

+		int len = s.length();

+		boolean isStart = true;

+		boolean isWhite = false;

+		for (int i = 0; i < len; i++) {

+			char c = s.charAt(i);

+			if (Character.isWhitespace(c)) {

+				isWhite = true;

+			}

+			else {

+				if (!isStart && isWhite)

+					sb.append(' ');

+				if (Character.isUpperCase(c))

+					c = Character.toLowerCase(c);

+				sb.append(c);

+				isStart = false;

+				isWhite = false;

+			}

+		}

+		return sb.toString();

+	}

+

+	boolean patSubstr(String s, String pat) {

+		if (s == null)

+			return false;

+		if (pat.length() == 0)

+			return s.length() == 0;

+		if (pat.charAt(0) == WILDCARD) {

+			pat = pat.substring(1);

+			for (;;) {

+				if (patSubstr(s, pat))

+					return true;

+				if (s.length() == 0)

+					return false;

+				s = s.substring(1);

+			}

+		}

+		else {

+			if (s.length() == 0 || s.charAt(0) != pat.charAt(0))

+				return false;

+			return patSubstr(s.substring(1), pat.substring(1));

+		}

+	}

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Manifest.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Manifest.java
new file mode 100644
index 0000000..01c3f51
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Manifest.java
@@ -0,0 +1,400 @@
+/*

+ * $Id: Manifest.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import java.io.*;

+import java.util.*;

+

+

+public class Manifest extends Hashtable {

+	static final long	serialVersionUID	= 1L;

+	List				imports;

+	List				exports;

+	ManifestEntry		name;

+	String				activator;

+	String				classpath[]	= new String[] {"."};

+	int					section;

+	String				location;

+	Native				_native[];

+	Vector				duplicates	= new Vector();

+	final static String	wordparts	= "~!@#$%^&*_:/?><.-+";

+	ManifestEntry		bsn;

+	VersionRange			version;

+	ManifestEntry		host;

+	List				require;

+

+	public Manifest(InputStream in) throws IOException {

+		parse(new InputStreamReader(in, "UTF8"));

+	}

+

+	public Manifest(Reader in) throws IOException {

+		parse(in);

+	}

+

+	public Object put(Object header, Object value) {

+		if (containsKey(header)) {

+			if (!((String) header).equalsIgnoreCase("comment"))

+				duplicates.add(header + ":" + value);

+		}

+		return super.put(header, value);

+	}

+

+	void parse(Reader in) throws IOException {

+		BufferedReader rdr = new BufferedReader(in);

+		String current = " ";

+		String buffer = rdr.readLine();

+		int section = 0;

+		if (buffer != null && !buffer.startsWith("Manifest-Version")) {

+			System.err

+					.println("The first line of a manifest file must be the Manifest-Version attribute");

+			throw new IOException(

+					"The first line of a manifest file must be the Manifest-Version attribute");

+		}

+		while (buffer != null && current != null && section == 0) {

+			if (current.startsWith(" ")) {

+				buffer += current.substring(1);

+			}

+			else {

+				section += entry(buffer);

+				buffer = current;

+			}

+			current = rdr.readLine();

+		}

+		entry(buffer);

+	}

+

+	int entry(String line) throws IOException {

+		if (line.length() < 2)

+			return 1;

+		int colon = line.indexOf(':');

+		if (colon < 1) {

+			error("Invalid header '" + line + "'");

+		}

+		else {

+			String header = line.substring(0, colon).toLowerCase();

+			String alphanum = "abcdefghijklmnopqrstuvwxyz0123456789";

+			String set = alphanum;

+			if (alphanum.indexOf(header.charAt(0)) < 0)

+				error("Header does not start with alphanum: " + header);

+			for (int i = 0; i < header.length(); i++) {

+				if (set.indexOf(header.charAt(i)) < 0)

+					error("Header contains non alphanum, - _: " + header);

+				set = "_-" + alphanum;

+			}

+			String value = "";

+			if (colon + 2 < line.length())

+				value = line.substring(colon + 2);

+			else

+				error("No value for manifest header " + header);

+			if (section == 0) {

+				if (header.equals("bundle-symbolicname")) {

+					bsn = (ManifestEntry) getEntries(value).get(0);

+				}

+				if (header.equals("bundle-version")) {

+					try {

+						version = new VersionRange(value.trim());

+					}

+					catch (Exception e) {

+						version = new VersionRange("0");

+						System.err.println("Invalid version attr for: " + bsn

+								+ " value is " + value);

+					}

+				}

+				if (header.equals("fragment-host"))

+					host = (ManifestEntry) getEntries(value).get(0);

+				if (header.equals("require-bundle"))

+					require = getEntries(value);

+				if (header.equals("import-package"))

+					imports = getEntries(value);

+				else if (header.equals("export-package"))

+					exports = getEntries(value);

+				else if (header.equals("bundle-activator"))

+					activator = value.trim();

+				else if (header.equals("bundle-updatelocation"))

+					location = value.trim();

+				else if (header.equals("bundle-classpath"))

+					classpath = getClasspath(value);

+				else if (header.equals("bundle-nativecode"))

+					_native = getNative(value);

+				put(header, value);

+			}

+		}

+		return 0;

+	}

+

+	void error(String msg) throws IOException {

+		System.err.println("Reading manifest: " + msg);

+	}

+

+	void warning(String msg) throws IOException {

+		System.err.println("Reading manifest: " + msg);

+	}

+

+	StreamTokenizer getStreamTokenizer(String line) {

+		StreamTokenizer st = new StreamTokenizer(new StringReader(line));

+		st.resetSyntax();

+		st.wordChars('a', 'z');

+		st.wordChars('A', 'Z');

+		st.wordChars('0', '9');

+		st.whitespaceChars(0, ' ');

+		st.quoteChar('"');

+		for (int i = 0; i < wordparts.length(); i++)

+			st.wordChars(wordparts.charAt(i), wordparts.charAt(i));

+		return st;

+	}

+

+	String word(StreamTokenizer st) throws IOException {

+		switch (st.nextToken()) {

+			case '"' :

+			case StreamTokenizer.TT_WORD :

+				String result = st.sval;

+				st.nextToken();

+				return result;

+		}

+		return null;

+	}

+

+	Parameter getParameter(StreamTokenizer st) throws IOException {

+

+		Parameter parameter = new Parameter();

+		parameter.key = word(st);

+		if (st.ttype == ':') {

+			st.nextToken();

+			parameter.type = Parameter.DIRECTIVE;

+		}

+		else {

+			parameter.type = Parameter.ATTRIBUTE;

+		}

+

+		if (st.ttype == '=') {

+			parameter.value = word(st);

+			while (st.ttype == StreamTokenizer.TT_WORD || st.ttype == '"') {

+				parameter.value += " " + st.sval;

+				st.nextToken();

+			}

+		}

+

+		return parameter;

+	}

+

+	public List getEntries(String line) throws IOException {

+		List v = new Vector();

+		Set aliases = new HashSet();

+

+		StreamTokenizer st = getStreamTokenizer(line);

+		do {

+			Parameter parameter = getParameter(st);

+			ManifestEntry p = new ManifestEntry(parameter.key);

+			while (st.ttype == ';') {

+				parameter = getParameter(st);

+				if (parameter.value == null) {

+					aliases.add(parameter.key);

+				}

+				else {

+					if (parameter.type == Parameter.ATTRIBUTE)

+						p.addParameter(parameter);

+					else

+						p.addParameter(parameter);

+				}

+			}

+			v.add(p);

+			for (Iterator a = aliases.iterator(); a.hasNext();) {

+				v.add(p.getAlias((String) a.next()));

+			}

+		} while (st.ttype == ',');

+		return v;

+	}

+

+	Native[] getNative(String line) throws IOException {

+		Vector v = new Vector();

+		StreamTokenizer st = getStreamTokenizer(line);

+		do {

+			Native spec = new Native();

+			Vector names = new Vector();

+			do {

+				Parameter parameter = getParameter(st);

+				if (parameter.value == null)

+					names.add(parameter.key);

+				else if (parameter.is("processor", Parameter.ATTRIBUTE))

+					spec.processor = parameter.value;

+				else if (parameter.is("osname", Parameter.ATTRIBUTE))

+					spec.osname = parameter.value;

+				else if (parameter.is("osversion", Parameter.ATTRIBUTE))

+					spec.osversion = parameter.value;

+				else if (parameter.is("language", Parameter.ATTRIBUTE))

+					spec.language = parameter.value;

+				else if (parameter.is("selection-filter", Parameter.DIRECTIVE))

+					spec.filter = parameter.value;

+				else

+					warning("Unknown parameter for native code : " + parameter);

+			} while (st.ttype == ';');

+			spec.paths = new String[names.size()];

+			names.copyInto(spec.paths);

+			v.add(spec);

+		} while (st.ttype == ',');

+		Native[] result = new Native[v.size()];

+		v.copyInto(result);

+		return result;

+	}

+

+	String[] getClasspath(String line) throws IOException {

+		StringTokenizer st = new StringTokenizer(line, " \t,");

+		String result[] = new String[st.countTokens()];

+		for (int i = 0; i < result.length; i++)

+			result[i] = st.nextToken();

+		return result;

+	}

+

+	public List getImports() {

+		return imports;

+	}

+

+	public List getExports() {

+		return exports;

+	}

+

+	public String getActivator() {

+		return activator;

+	}

+

+	public String getLocation() {

+		return location;

+	}

+

+	public String[] getClasspath() {

+		return classpath;

+	}

+

+	public Native[] getNative() {

+		return _native;

+	}

+

+	public Object get(Object key) {

+		if (key instanceof String)

+			return super.get(((String) key).toLowerCase());

+		else

+			return null;

+	}

+

+	public String getValue(String key) {

+		return (String) super.get(key.toLowerCase());

+	}

+

+	public String getValue(String key, String deflt) {

+		String s = getValue(key);

+		if (s == null)

+			return deflt;

+		else

+			return s;

+	}

+

+	public String[] getRequiredExecutionEnvironments() {

+		String ees = getValue("Bundle-RequiredExecutionEnvironment");

+		if (ees != null)

+			return ees.trim().split("\\s*,\\s*");

+		else

+			return null;

+	}

+

+	public VersionRange getVersion() {

+		if (version == null)

+			return new VersionRange("0");

+		return version;

+	}

+

+	public String getSymbolicName() {

+		ManifestEntry bsn = getBsn();

+

+		if (bsn == null) {

+			String name = getValue("Bundle-Name");

+			if (name == null)

+				name = "Untitled-" + hashCode();

+			return name;

+		}

+		else

+			return bsn.getName();

+	}

+

+	public String getManifestVersion() {

+		return getValue("Bundle-ManifestVersion", "1");

+	}

+

+	public String getCopyright() {

+		return getValue("Bundle-Copyright");

+	}

+

+	public String getDocumentation() {

+		return getValue("Bundle-DocURL");

+	}

+

+	public String[] getCategories() {

+		String cats = getValue("Bundle-Category");

+		if (cats == null)

+			return new String[0];

+		else

+			return cats.split("\\s*,\\s*");

+	}

+

+	public Native[] get_native() {

+		return _native;

+	}

+

+	public void set_native(Native[] _native) {

+		this._native = _native;

+	}

+

+	public ManifestEntry getBsn() {

+		return bsn;

+	}

+

+	public void setBsn(ManifestEntry bsn) {

+		this.bsn = bsn;

+	}

+

+	public Vector getDuplicates() {

+		return duplicates;

+	}

+

+	public void setDuplicates(Vector duplicates) {

+		this.duplicates = duplicates;

+	}

+

+	public ManifestEntry getHost() {

+		return host;

+	}

+

+	public void setHost(ManifestEntry host) {

+		this.host = host;

+	}

+

+	public List getRequire() {

+		return require;

+	}

+

+}

+

+class Native {

+	String	filter;

+	int		index	= -1;

+	String	paths[];

+	String	osname;

+	String	osversion;

+	String	language;

+	String	processor;

+

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/ManifestEntry.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/ManifestEntry.java
new file mode 100644
index 0000000..2dc3e69
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/ManifestEntry.java
@@ -0,0 +1,111 @@
+/*

+ * $Id: ManifestEntry.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import java.util.*;

+

+

+public class ManifestEntry implements Comparable {

+	String		name;

+	VersionRange	version;

+	Map			attributes;

+	public Map	directives;

+	public Set	uses;

+

+	public ManifestEntry(String name) {

+		this.name = name;

+	}

+

+	public ManifestEntry(String name, VersionRange version) {

+		this.name = name;

+		this.version = version;

+	}

+

+	public String toString() {

+		if (version == null)

+			return name;

+		return name + " ;version=" + version;

+	}

+

+	public String getName() {

+		return name;

+	}

+

+	public VersionRange getVersion() {

+		if (version != null)

+			return version;

+		return new VersionRange("0");

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see java.lang.Comparable#compareTo(java.lang.Object)

+	 */

+	public int compareTo(Object o) {

+		ManifestEntry p = (ManifestEntry) o;

+		return name.compareTo(p.name);

+	}

+

+	/**

+	 * @return

+	 */

+	public Object getPath() {

+		return getName().replace('.', '/');

+	}

+

+	public Map getDirectives() {

+		return directives;

+	}

+

+	public Map getAttributes() {

+		return attributes;

+	}

+

+	/**

+	 * @param parameter

+	 */

+	public void addParameter(Parameter parameter) {

+		switch (parameter.type) {

+			case Parameter.ATTRIBUTE :

+				if (attributes == null)

+					attributes = new HashMap();

+				attributes.put(parameter.key, parameter.value);

+				if (parameter.key.equalsIgnoreCase("version")

+						|| parameter.key

+								.equalsIgnoreCase("specification-version"))

+					this.version = new VersionRange(parameter.value);

+				break;

+

+			case Parameter.DIRECTIVE :

+				if (directives == null)

+					directives = new HashMap();

+				directives.put(parameter.key, parameter.value);

+				break;

+		}

+	}

+

+	public ManifestEntry getAlias(String key) {

+		ManifestEntry me = new ManifestEntry(key);

+		me.attributes = attributes;

+		me.directives = directives;

+		me.version = version;

+		return me;

+	}

+

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Parameter.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Parameter.java
new file mode 100644
index 0000000..5038132
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Parameter.java
@@ -0,0 +1,49 @@
+/*

+ * $Id: Parameter.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+class Parameter {

+	final static int	ATTRIBUTE	= 1;

+	final static int	DIRECTIVE	= 2;

+	final static int	SINGLE		= 0;

+

+	int					type;

+	String				key;

+	String				value;

+

+	public String toString() {

+		StringBuffer sb = new StringBuffer();

+		sb.append(key);

+		switch (type) {

+			case ATTRIBUTE :

+				sb.append("=");

+				break;

+			case DIRECTIVE :

+				sb.append(":=");

+				break;

+			case SINGLE :

+				return sb.toString();

+		}

+		sb.append(value);

+		return sb.toString();

+	}

+

+	boolean is(String s, int type) {

+		return this.type == type && key.equalsIgnoreCase(s);

+	}

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/RepositoryImpl.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/RepositoryImpl.java
new file mode 100644
index 0000000..bc6953b
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/RepositoryImpl.java
@@ -0,0 +1,375 @@
+/*

+ * $Id: RepositoryImpl.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+package org.osgi.impl.bundle.obr.resource;

+

+import java.io.*;

+import java.net.*;

+import java.util.*;

+import java.util.zip.*;

+

+import org.kxml2.io.KXmlParser;

+import org.osgi.service.obr.*;

+import org.xmlpull.v1.*;

+

+/**

+ * Implements the basic repository. A repository holds a set of resources.

+ * 

+ * 

+ * @version $Revision: 44 $

+ */

+public class RepositoryImpl implements Repository {

+	transient Set			resources		= new HashSet();

+	URL						url;

+	String					date;

+	Set						visited			= new HashSet();

+	final static Resource[]	EMPTY_RESOURCE	= new Resource[0];

+	String					name			= "Untitled";

+	long					lastModified;

+	Exception				exception;

+	int						ranking=0;

+

+	/**

+	 * Each repository is identified by a single URL.

+	 * 

+	 * A repository can hold referrals to other repositories. These referred

+	 * repositories are included at the point of referall.

+	 * 

+	 * @param url

+	 */

+	public RepositoryImpl(URL url) {

+		this.url = url;

+	}

+

+	/**

+	 * Refresh the repository from the URL.

+	 * 

+	 * @throws Exception

+	 */

+	public boolean refresh() {

+		exception = null;

+		try {

+			resources.clear();

+			parseDocument(url);

+			visited = null;

+			return true;

+		}

+		catch (Exception e) {

+			e.printStackTrace();

+			exception = e;

+		}

+		return false;

+	}

+

+	/**

+	 * Parse the repository.

+	 * 

+	 * @param parser

+	 * @throws Exception

+	 */

+	private void parseRepository(XmlPullParser parser) throws Exception {

+		try {

+			parser.require(XmlPullParser.START_DOCUMENT, null, null);

+			parser.nextTag();

+			if (parser.getName().equals("bundles"))

+				parseOscar(parser);

+			else {

+				parser.require(XmlPullParser.START_TAG, null, "repository");

+				date = parser.getAttributeValue(null, "lastmodified");

+				name = parser.getAttributeValue(null, "name");

+				if (name == null)

+					name = "Untitled";

+

+				while (parser.nextTag() == XmlPullParser.START_TAG) {

+					if (parser.getName().equals("resource")) {

+						ResourceImpl resource = new ResourceImpl(this, parser);

+						resources.add(resource);

+					}

+					else if (parser.getName().equals("referral"))

+						referral(parser);

+					else

+						throw new IllegalArgumentException(

+								"Invalid tag in repository: " + url + " "

+										+ parser.getName());

+				}

+				parser.require(XmlPullParser.END_TAG, null, "repository");

+			}

+		}

+		catch (XmlPullParserException e) {

+			e.printStackTrace();

+			throw new IllegalArgumentException("XML unregognized around: "

+					+ e.getLineNumber() + " " + e.getMessage());

+		}

+	}

+

+	/**

+	 * Parse an old style OBR repository.

+	 * 

+	 * <dtd-version>1.0</dtd-version> <repository> <name>Oscar Bundle

+	 * Repository</name> <url>http://oscar-osgi.sourceforge.net/</url>

+	 * <date>Fri May 07 16:45:07 CEST 2004</date> <extern-repositories> <!--

+	 * Stefano Lenzi (kismet@interfree.it) -->

+	 * <url>http://domoware.isti.cnr.it/osgi-obr/niche-osgi-obr.xml</url>

+	 * <!--Manuel Palencia (santillan@dit.upm.es) --> <!--

+	 * <url>http://jmood.forge.os4os.org/repository.xml</url> --> <!-- Enrique

+	 * Rodriguez (erodriguez@apache.org) -->

+	 * <url>http://update.cainenable.org/repository.xml</url>

+	 * </extern-repositories> </repository> <bundle> <bundle-name>Bundle

+	 * Repository</bundle-name> <bundle-description> A bundle repository

+	 * service for Oscar. </bundle-description> <bundle-updatelocation>

+	 * http://oscar-osgi.sf.net/repo/bundlerepository/bundlerepository.jar

+	 * </bundle-updatelocation> <bundle-sourceurl>

+	 * http://oscar-osgi.sf.net/repo/bundlerepository/bundlerepository-src.jar

+	 * </bundle-sourceurl> <bundle-version>1.1.3</bundle-version>

+	 * <bundle-docurl> http://oscar-osgi.sf.net/repo/bundlerepository/

+	 * </bundle-docurl> <bundle-category>General</bundle-category>

+	 * <import-package package="org.osgi.framework"/> <export-package

+	 * package="org.ungoverned.osgi.service.bundlerepository"

+	 * specification-version="1.1.0"/> </bundle> *

+	 */

+	private void parseOscar(XmlPullParser parser) throws Exception {

+		parser.require(XmlPullParser.START_TAG, null, "bundles");

+		while (true) {

+			int event = parser.next();

+

+			// Error ..

+			if (event == XmlPullParser.TEXT)

+				event = parser.next();

+

+			if (event != XmlPullParser.START_TAG)

+				break;

+

+			ResourceImpl resource = new ResourceImpl(this);

+

+			if (parser.getName().equals("bundle")) {

+				while (parser.nextTag() == XmlPullParser.START_TAG) {

+					String key = parser.getName();

+					if (key.equals("import-package")) {

+						RequirementImpl requirement = new RequirementImpl(

+								"package");

+						

+						requirement.setOptional(false);

+						requirement.setMultiple(false);

+						

+						String p = parser.getAttributeValue(null, "package");

+						StringBuffer sb = new StringBuffer();

+						sb.append("(&(package=");

+						sb.append(p);

+						sb.append(")");

+						String version = parser.getAttributeValue(null,

+								"specification-version");

+						VersionRange v = new VersionRange("0");

+						if (version != null) {

+							sb.append("(version=");

+							sb.append(v= new VersionRange(version));

+							sb.append(")");

+						}

+						sb.append(")");

+						requirement.setFilter(sb.toString());

+						requirement.setComment("Import-Package: " + p + ";" + v );

+						resource.addRequirement(requirement);

+						

+						parser.nextTag();

+					}

+					else if (key.equals("export-package")) {

+						CapabilityImpl capability = new CapabilityImpl(

+								"package");

+						capability.addProperty("package", parser

+								.getAttributeValue(null, "package"));

+						String version = parser.getAttributeValue(null,

+								"specification-version");

+						if (version != null) {

+							capability.addProperty("version", new VersionRange(

+									version));

+						}

+						resource.addCapability(capability);

+						parser.nextTag();

+					}

+					else {

+						String value = parser.nextText().trim();

+						if (key.equals("bundle-sourceurl"))

+							resource.setSource(new URL(value));

+						else if (key.equals("bundle-docurl"))

+							resource.setDocumentation(new URL(value));

+						else if (key.equals("bundle-updatelocation"))

+							resource.setURL(new URL(value));

+						else if (key.equals("bundle-description"))

+							resource.setDescription(value);

+						else if (key.equals("bundle-category"))

+							resource.addCategory(value);

+						else if (key.equals("bundle-name")) {

+							resource.setName(value);

+							resource.setPresentationName(value);

+						}

+						else if (key.equals("bundle-version"))

+							resource.setVersion(new VersionRange(value));

+						else {

+							resource.put(key, value);

+						}

+					}

+				}

+				resources.add(resource);

+				parser.require(XmlPullParser.END_TAG, null, "bundle");

+			}

+			else if (parser.getName().equals("repository")) {

+				parser.require(XmlPullParser.START_TAG, null, "repository");

+				while (parser.nextTag() == XmlPullParser.START_TAG) {

+					String tag = parser.getName();

+					if (tag.equals("name")) {

+						String name = parser.nextText();

+						if (this.name == null)

+							this.name = name.trim();

+					}

+					else if (tag.equals("url"))

+						parser.nextText().trim();

+					else if (tag.equals("date"))

+						parser.nextText().trim();

+					else if (tag.equals("extern-repositories")) {

+						parser.require(XmlPullParser.START_TAG, null,

+								"extern-repositories");

+						while (parser.nextTag() == XmlPullParser.START_TAG) {

+							if (parser.getName().equals("url"))

+								parseDocument(new URL(parser.nextText().trim()));

+							else

+								throw new IllegalArgumentException(

+										"Invalid tag in repository while parsing extern repositories: "

+												+ url + " " + parser.getName());

+						}

+						parser.require(XmlPullParser.END_TAG, null,

+								"extern-repositories");

+					}

+					else

+						throw new IllegalArgumentException(

+								"Invalid tag in repository: " + url + " "

+										+ parser.getName());

+				}

+				parser.require(XmlPullParser.END_TAG, null, "repository");

+			}

+			else if (parser.getName().equals("dtd-version")) {

+				parser.nextText();

+			}

+			else

+				throw new IllegalArgumentException(

+						"Invalid tag in repository: " + url + " "

+								+ parser.getName());

+		}

+		parser.require(XmlPullParser.END_TAG, null, "bundles");

+	}

+

+	/**

+	 * We have a referral to another repository. Just create another parser and

+	 * read it inline.

+	 * 

+	 * @param parser

+	 */

+	void referral(XmlPullParser parser) {

+		// TODO handle depth!

+		try {

+			parser.require(XmlPullParser.START_TAG, null, "referral");

+			// String depth = parser.getAttributeValue(null, "depth");

+			String path = parser.getAttributeValue(null, "url");

+			URL url = new URL(this.url, path);

+			parseDocument(url);

+			parser.next();

+			parser.require(XmlPullParser.END_TAG, null, "referral");

+		}

+		catch (Exception e) {

+			e.printStackTrace();

+		}

+	}

+

+	/**

+	 * Parse a repository document.

+	 * 

+	 * @param url

+	 * @throws IOException

+	 * @throws XmlPullParserException

+	 * @throws Exception

+	 */

+	void parseDocument(URL url) throws IOException, XmlPullParserException,

+			Exception {

+		if (!visited.contains(url)) {

+			visited.add(url);

+			try {

+				System.out.println("Visiting: " + url);

+				InputStream in = null;

+				

+				if ( url.getPath().endsWith(".zip")) {

+					ZipInputStream zin = new ZipInputStream( url.openStream() );

+					ZipEntry entry = zin.getNextEntry();

+					while ( entry != null ) {

+						if ( entry.getName().equals("repository.xml")) {

+							in = zin;

+							break;

+						}

+						entry = zin.getNextEntry();

+					}

+				} else {

+					in = url.openStream();

+				}

+				Reader reader = new InputStreamReader(in);

+				XmlPullParser parser = new KXmlParser();

+				parser.setInput(reader);

+				parseRepository(parser);

+			} catch( MalformedURLException e ) {

+				System.out.println("Cannot create connection to url");

+			}

+		}

+	}

+

+	public URL getURL() {

+		return url;

+	}

+

+	/**

+	 * @return

+	 */

+	public Collection getResourceList() {

+		return resources;

+	}

+

+	public Resource[] getResources() {

+		return (Resource[]) getResourceList().toArray(EMPTY_RESOURCE);

+	}

+

+	public String getName() {

+		return name;

+	}

+

+	public Resource getResource(String id) {

+		for (Iterator i = getResourceList().iterator(); i.hasNext();) {

+			ResourceImpl resource = (ResourceImpl) i.next();

+			if (resource.getId().equals(id))

+				return resource;

+		}

+		return null;

+	}

+

+	public long getLastModified() {

+		return lastModified;

+	}

+

+	public int getRanking() {

+		return ranking;

+	}

+

+	public void setRanking(int ranking) {

+		this.ranking = ranking;

+	}

+

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/RequirementImpl.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/RequirementImpl.java
new file mode 100644
index 0000000..146618a
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/RequirementImpl.java
@@ -0,0 +1,177 @@
+/*

+ * $Id: RequirementImpl.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import org.osgi.service.obr.*;

+import org.xmlpull.v1.XmlPullParser;

+

+

+

+/**

+ * Implements the Requirement interface.

+ * 

+ * 

+ * @version $Revision: 44 $

+ */

+public class RequirementImpl implements Requirement {

+	int		id;

+	String	name;

+	String	filter="()";

+	FilterImpl	_filter;

+	String	comment;

+	boolean optional;

+	boolean multiple;

+	boolean extend;

+	

+	/**

+	 * Create a requirement with the given name.

+	 * 

+	 * @param name

+	 */

+	public RequirementImpl(String name) {

+		this.name = name;

+	}

+

+

+	/**

+	 * Parse the requirement from the pull parser.

+	 * 

+	 * @param parser

+	 * @throws Exception

+	 */

+	public RequirementImpl(XmlPullParser parser) throws Exception {

+		parser.require(XmlPullParser.START_TAG, null, null );

+		name = parser.getAttributeValue(null, "name");

+		filter = parser.getAttributeValue(null, "filter");

+		

+		String opt = parser.getAttributeValue(null,"optional");

+		String mul = parser.getAttributeValue(null,"multiple");

+		String ext = parser.getAttributeValue(null,"extend");

+		optional = "true".equalsIgnoreCase(opt);

+		multiple = "true".equalsIgnoreCase(mul);

+		extend = "true".equalsIgnoreCase(ext);

+		

+		

+		StringBuffer sb = new StringBuffer();

+		while ( parser.next() == XmlPullParser.TEXT ) {

+			sb.append( parser.getText() );

+		}

+		if ( sb.length() > 0 )

+			setComment(sb.toString().trim());

+			

+		parser.require(XmlPullParser.END_TAG, null, null );

+	}

+

+	public void setFilter(String filter) {

+		this.filter = filter;

+		_filter= null;

+	}

+

+	public String getFilter() {

+		return filter;

+	}

+

+	public Tag toXML(String name) {

+		Tag tag = toXML(this);

+		tag.rename(name);

+		return tag;

+	}

+

+

+	public String getName() {

+		return name;

+	}

+

+	public boolean isSatisfied(Capability capability) {

+		if (_filter == null)

+			_filter = new FilterImpl(filter);

+

+		boolean result = _filter.match(capability.getProperties());

+		return result;

+	}

+

+	public String toString() {

+		return name + " " + filter;

+	}

+

+

+	public String getComment() {

+		return comment;

+	}

+

+

+	public void setComment(String comment) {

+		this.comment=comment;

+	}

+

+

+	public static Tag toXML(Requirement requirement) {

+		Tag req = new Tag("require");

+		req.addAttribute("name", requirement.getName());

+		req.addAttribute("filter", requirement.getFilter());

+		

+		req.addAttribute("optional", requirement.isOptional()+"");

+		req.addAttribute("multiple", requirement.isMultiple()+"");

+		req.addAttribute("extend", requirement.isExtend()+"");

+		

+		if ( requirement.getComment() != null )

+			req.addContent(requirement.getComment());

+		

+		return req;

+	}

+

+

+	public boolean isMultiple() {

+		return multiple;

+	}

+

+

+	public boolean isOptional() {

+		return optional;

+	}

+

+

+	public void setOptional(boolean b) {

+		optional = b;

+	}

+

+	public void setMultiple(boolean b) {

+		multiple = b;

+	}

+

+

+	public boolean equals(Object o) {

+		if ( ! (o instanceof Requirement) )

+			return false;

+		

+		Requirement r2 = (Requirement)o;

+		return filter.equals(r2.getFilter()) && name.equals(r2.getName()); 

+	}

+	

+	public int hashCode() {

+		return filter.hashCode() ^ name.hashCode();

+	}

+	

+	public boolean isExtend() {

+		return extend;

+	}

+	

+	public void setExtend(boolean extend) {

+		this.extend = extend;

+	}

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/ResourceImpl.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/ResourceImpl.java
new file mode 100644
index 0000000..55cd298
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/ResourceImpl.java
@@ -0,0 +1,369 @@
+/*

+ * $Id: ResourceImpl.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import java.io.File;

+import java.net.URL;

+import java.util.*;

+

+import org.osgi.framework.Version;

+import org.osgi.service.obr.*;

+import org.xmlpull.v1.XmlPullParser;

+

+public class ResourceImpl implements Resource {

+	List			capabilities	= new ArrayList();

+	List			requirements	= new ArrayList();

+	URL				url;

+	String			symbolicName;

+	VersionRange		version;

+	List			categories		= new ArrayList();

+	long			size			= -1;

+	String			id;

+	static int		ID				= 1;

+	Map				map				= new HashMap();

+	RepositoryImpl	repository;

+	String			presentationName;

+	File			file;

+

+

+	public ResourceImpl(RepositoryImpl repository, String name,

+			VersionRange version) {

+		this.version = version;

+		if ( version == null)

+			this.version = new VersionRange("0");

+		this.symbolicName = name;

+		this.repository = repository;

+	}

+

+	public ResourceImpl(RepositoryImpl repository, XmlPullParser parser)

+			throws Exception {

+		this.repository = repository;

+		parser.require(XmlPullParser.START_TAG, null, "resource");

+		symbolicName = parser.getAttributeValue(null, "symbolicname");

+		if (symbolicName == null)

+			System.err.println("Hey, no symb name! "

+					+ parser.getAttributeValue(null, "uri"));

+

+		map.put(SYMBOLIC_NAME, symbolicName);

+		presentationName = parser.getAttributeValue(null, PRESENTATION_NAME);

+		if (presentationName != null)

+			map.put(PRESENTATION_NAME, presentationName);

+		String v = parser.getAttributeValue(null, "version");

+		if (v == null)

+			setVersion(new VersionRange("0"));

+		else

+			setVersion(new VersionRange(v));

+

+		setURL(toURL(parser.getAttributeValue(null, "uri")));

+

+		while (parser.nextTag() == XmlPullParser.START_TAG) {

+			if (parser.getName().equals("category")) {

+				categories.add(parser.getAttributeValue(null, "id").trim());

+			}

+			else if (parser.getName().equals("require"))

+				addRequirement(new RequirementImpl(parser));

+			else if (parser.getName().equals("capability"))

+				addCapability(new CapabilityImpl(parser));

+			else {

+				String text = parser.nextText();

+				if (text != null)

+					map.put(parser.getName(), text.trim());

+			}

+			parser.next();

+		}

+		parser.require(XmlPullParser.END_TAG, null, "resource");

+	}

+

+	public ResourceImpl(RepositoryImpl impl) {

+		this.repository = impl;

+	}

+

+	private URL toURL(String attributeValue) throws Exception {

+		if (attributeValue == null)

+			return null;

+

+		return new URL(repository.getURL(), attributeValue);

+	}

+

+	public void addCategory(String category) {

+		categories.add(category);

+	}

+

+	public void addCapability(CapabilityImpl capability) {

+		if (capability != null)

+			capabilities.add(capability);

+	}

+

+	public void addRequirement(RequirementImpl requirement) {

+		if (requirement != null)

+			requirements.add(requirement);

+	}

+

+	public void setLicense(URL license) {

+		if (license != null)

+			map.put(LICENSE_URL, license);

+	}

+

+	public String getDescription() {

+		return (String) map.get(DESCRIPTION);

+	}

+

+	public void setDescription(String description) {

+		if (description != null)

+			map.put(DESCRIPTION, description);

+	}

+

+	public Capability[] getCapabilities() {

+		return (Capability[]) capabilities.toArray(new Capability[capabilities

+				.size()]);

+	}

+

+	public URL getLicense() {

+		return (URL) map.get(LICENSE_URL);

+	}

+

+	public String getSymbolicName() {

+		return symbolicName;

+	}

+

+	public Requirement[] getRequirements() {

+		return (Requirement[]) requirements

+				.toArray(new Requirement[requirements.size()]);

+	}

+

+	public Tag toXML() {

+		return toXML(this );

+	}

+

+	public static Tag toXML(Resource resource) {

+		return toXML(resource,true);

+	}

+

+	public static Tag toXML(Resource resource, boolean relative ) {

+		Tag meta = new Tag("resource");

+		URL url = resource.getURL();

+		String urlString = url.toExternalForm();

+		

+		if ( relative )

+			urlString = makeRelative(resource.getRepository().getURL(), url);

+		

+		meta.addAttribute("uri", urlString );

+		meta.addAttribute(SYMBOLIC_NAME, resource.getSymbolicName());

+		if (resource.getPresentationName() != null)

+			meta

+					.addAttribute(PRESENTATION_NAME, resource

+							.getPresentationName());

+		meta.addAttribute(VERSION, resource.getVersion().toString());

+		meta.addAttribute("id", resource.getId());

+		Map map = new TreeMap(resource.getProperties());

+		for (int i = 0; i < Resource.KEYS.length; i++) {

+			String key = KEYS[i];

+			if (!(key.equals(URL) || key.equals(SYMBOLIC_NAME) || key

+					.equals(VERSION) || key.equals(PRESENTATION_NAME))) {

+				Object value = map.get(KEYS[i]);

+				if (value != null) {

+					if (value instanceof URL)

+						value = makeRelative(resource.getRepository().getURL(),(URL) value);

+					meta.addContent(new Tag(key, value.toString()));

+				}

+			}

+		}

+

+		String[] categories = resource.getCategories();

+		for (int i = 0; i < categories.length; i++) {

+			String category = categories[i];

+			meta.addContent(new Tag("category", new String[] {"id",

+					category.toLowerCase()}));

+		}

+

+		Capability[] capabilities = resource.getCapabilities();

+		for (int i = 0; i < capabilities.length; i++) {

+			meta.addContent(CapabilityImpl.toXML(capabilities[i]));

+		}

+

+		Requirement[] requirements = resource.getRequirements();

+		for (int i = 0; i < requirements.length; i++) {

+			meta.addContent(RequirementImpl.toXML(requirements[i]));

+		}

+		return meta;

+	}

+

+	public URL getURL() {

+		return url;

+	}

+

+	static String makeRelative(URL repository, URL url) {

+		try {

+			if (repository != null) {

+				String a = url.toExternalForm();

+				String b = repository.toExternalForm();

+				int index = b.lastIndexOf('/');

+				if ( index > 0 )

+					b = b.substring(0,index+1);

+				if (a.startsWith(b))

+					return a.substring(b.length());

+			}

+		}

+		catch (Exception e) {

+			// Ignore

+		}

+		return url.toExternalForm();

+	}

+

+	public void setURL(URL url) {

+		this.url = url;

+		if (url != null)

+			map.put(URL, url);

+	}

+

+	public String getCopyright() {

+		return (String) map.get(COPYRIGHT);

+	}

+

+	public Version getVersion() {

+		if (version == null)

+			version = new VersionRange("0");

+		return version.low;

+	}

+

+	void setVersion(VersionRange version) {

+		if (version == null)

+			this.version = new VersionRange("0");

+		else

+			this.version = version;

+	}

+

+	public void setCopyright(String copyright) {

+		if (copyright != null)

+			map.put(COPYRIGHT, copyright);

+	}

+

+	public URL getDocumentation() {

+		return (URL) map.get(DOCUMENTATION_URL);

+	}

+

+	public void setDocumentation(URL documentation) {

+		if (documentation != null)

+			map.put(DOCUMENTATION_URL, documentation);

+	}

+

+	public URL getSource() {

+		return (URL) map.get(SOURCE_URL);

+	}

+

+	public void setSource(URL source) {

+		if (source != null)

+			map.put(SOURCE_URL, source);

+	}

+

+	public boolean satisfies(RequirementImpl requirement) {

+		for (Iterator i = capabilities.iterator(); i.hasNext();) {

+			CapabilityImpl capability = (CapabilityImpl) i.next();

+			if (requirement.isSatisfied(capability))

+				return true;

+		}

+		return false;

+	}

+

+	public String toString() {

+		return symbolicName + "-" + version;

+	}

+

+	public long getSize() {

+		return size;

+	}

+

+	public void setSize(long size) {

+		this.size = size;

+		map.put(SIZE, new Long(size));

+	}

+

+	public Collection getRequirementList() {

+		return requirements;

+	}

+

+	public Collection getCapabilityList() {

+		return capabilities;

+	}

+

+	public int hashCode() {

+		return symbolicName.hashCode() ^ version.hashCode();

+	}

+

+	public boolean equals(Object o) {

+		try {

+			ResourceImpl other = (ResourceImpl) o;

+			return symbolicName.equals(other.symbolicName)

+					&& version.equals(other.version);

+		}

+		catch (ClassCastException e) {

+			return false;

+		}

+	}

+

+	public String[] getCategories() {

+		return (String[]) categories.toArray(new String[categories.size()]);

+	}

+

+	public Map getProperties() {

+		return Collections.unmodifiableMap(map);

+	}

+

+	public synchronized String getId() {

+		if ( id == null )

+			id = symbolicName + "/" + version;

+		return id;

+	}

+

+	public Repository getRepository() {

+		return repository;

+	}

+

+	void setName(String value) {

+		this.symbolicName = value;

+	}

+

+	void put(String name, Object value) {

+		map.put(name, value);

+	}

+

+	public void setPresentationName(String name) {

+		presentationName = name;

+		if (name != null)

+			map.put(PRESENTATION_NAME, name);

+	}

+

+	public String getPresentationName() {

+		return presentationName;

+	}

+

+	public void setFile(File zipFile) {

+		file = zipFile;

+	}

+

+	public Set getExtendList() {

+		Set set = new HashSet();

+		for (Iterator i = requirements.iterator(); i.hasNext();) {

+			RequirementImpl	impl = (RequirementImpl) i.next();

+			if ( impl.isExtend())

+				set.add(impl);

+		}

+		return set;

+	}

+

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/StringSet.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/StringSet.java
new file mode 100644
index 0000000..d2bbd5d
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/StringSet.java
@@ -0,0 +1,32 @@
+/*

+ * $Id: StringSet.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+

+package org.osgi.impl.bundle.obr.resource;

+

+import java.util.*;

+

+public class StringSet extends HashSet {

+	static final long	serialVersionUID	= 1L;

+

+	public StringSet(String set) {

+		StringTokenizer st = new StringTokenizer(set, ",");

+		while (st.hasMoreTokens())

+			add(st.nextToken().trim());

+	}

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Tag.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Tag.java
new file mode 100644
index 0000000..3c1375f
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/Tag.java
@@ -0,0 +1,488 @@
+/*

+ * $Id: Tag.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.osgi.impl.bundle.obr.resource;

+

+import java.io.*;

+import java.text.SimpleDateFormat;

+import java.util.*;

+

+/**

+ * The Tag class represents a minimal XML tree. It consist of a named element

+ * with a hashtable of named attributes. Methods are provided to walk the tree

+ * and get its constituents. The content of a Tag is a list that contains String

+ * objects or other Tag objects.

+ */

+public class Tag {

+	Tag						parent;

+	String					name;

+	Map						attributes	= new TreeMap();

+	Vector					content		= new Vector();

+

+	static SimpleDateFormat	format		= new SimpleDateFormat(

+												"yyyyMMddhhmmss.SSS");

+

+	/**

+	 * Construct a new Tag with a name.

+	 */

+	public Tag(String name) {

+		this.name = name;

+	}

+

+	/**

+	 * Construct a new Tag with a name.

+	 */

+	public Tag(String name, Map attributes) {

+		this.name = name;

+		this.attributes = attributes;

+	}

+

+	/**

+	 * Construct a new Tag with a name and a set of attributes. The attributes

+	 * are given as ( name, value ) ...

+	 */

+	public Tag(String name, String[] attributes) {

+		this.name = name;

+		for (int i = 0; i < attributes.length; i += 2)

+			addAttribute(attributes[i], attributes[i + 1]);

+	}

+

+	/**

+	 * Construct a new Tag with a single string as content.

+	 */

+	public Tag(String name, String content) {

+		this.name = name;

+		addContent(content);

+	}

+

+	/**

+	 * Add a new attribute.

+	 */

+	public void addAttribute(String key, String value) {

+		attributes.put(key, value);

+	}

+

+	/**

+	 * Add a new attribute.

+	 */

+	public void addAttribute(String key, Object value) {

+		if (value == null)

+			return;

+		attributes.put(key, value.toString());

+	}

+

+	/**

+	 * Add a new attribute.

+	 */

+	public void addAttribute(String key, int value) {

+		attributes.put(key, Integer.toString(value));

+	}

+

+	/**

+	 * Add a new date attribute. The date is formatted as the SimpleDateFormat

+	 * describes at the top of this class.

+	 */

+	public void addAttribute(String key, Date value) {

+		attributes.put(key, format.format(value));

+	}

+

+	/**

+	 * Add a new content string.

+	 */

+	public void addContent(String string) {

+		content.addElement(string);

+	}

+

+	/**

+	 * Add a new content tag.

+	 */

+	public void addContent(Tag tag) {

+		content.addElement(tag);

+		tag.parent = this;

+	}

+

+	/**

+	 * Return the name of the tag.

+	 */

+	public String getName() {

+		return name;

+	}

+

+	/**

+	 * Return the attribute value.

+	 */

+	public String getAttribute(String key) {

+		return (String) attributes.get(key);

+	}

+

+	/**

+	 * Return the attribute value or a default if not defined.

+	 */

+	public String getAttribute(String key, String deflt) {

+		String answer = getAttribute(key);

+		return answer == null ? deflt : answer;

+	}

+

+	/**

+	 * Answer the attributes as a Dictionary object.

+	 */

+	public Map getAttributes() {

+		return attributes;

+	}

+

+	/**

+	 * Return the contents.

+	 */

+	public Vector getContents() {

+		return content;

+	}

+

+	/**

+	 * Return a string representation of this Tag and all its children

+	 * recursively.

+	 */

+	public String toString() {

+		StringWriter sw = new StringWriter();

+		print(0, new PrintWriter(sw));

+		return sw.toString();

+	}

+

+	/**

+	 * Return only the tags of the first level of descendants that match the

+	 * name.

+	 */

+	public Vector getContents(String tag) {

+		Vector out = new Vector();

+		for (Enumeration e = content.elements(); e.hasMoreElements();) {

+			Object o = e.nextElement();

+			if (o instanceof Tag && ((Tag) o).getName().equals(tag))

+				out.addElement(o);

+		}

+		return out;

+	}

+

+	/**

+	 * Return the whole contents as a String (no tag info and attributes).

+	 */

+	public String getContentsAsString() {

+		StringBuffer sb = new StringBuffer();

+		getContentsAsString(sb);

+		return sb.toString();

+	}

+

+	/**

+	 * convenient method to get the contents in a StringBuffer.

+	 */

+	public void getContentsAsString(StringBuffer sb) {

+		for (Enumeration e = content.elements(); e.hasMoreElements();) {

+			Object o = e.nextElement();

+			if (o instanceof Tag)

+				((Tag) o).getContentsAsString(sb);

+			else

+				sb.append(o.toString());

+		}

+	}

+

+	/**

+	 * Print the tag formatted to a PrintWriter.

+	 */

+	public void print(int indent, PrintWriter pw) {

+		pw.print("\n");

+		spaces(pw, indent);

+		pw.print('<');

+		pw.print(name);

+

+		for (Iterator e = attributes.keySet().iterator(); e.hasNext();) {

+			String key = (String) e.next();

+			String value = escape((String) attributes.get(key));

+			pw.print(' ');

+			pw.print(key);

+			pw.print("=");

+			String quote = "'";

+			if (value.indexOf(quote) >= 0)

+				quote = "\"";

+			pw.print(quote);

+			pw.print(value);

+			pw.print(quote);

+		}

+

+		if (content.size() == 0)

+			pw.print('/');

+		else {

+			pw.print('>');

+			for (Enumeration e = content.elements(); e.hasMoreElements();) {

+				Object content = e.nextElement();

+				if (content instanceof String) {

+					formatted(pw, indent + 2, 60, escape((String) content));

+				}

+				else if (content instanceof Tag) {

+					Tag tag = (Tag) content;

+					tag.print(indent + 2, pw);

+				}

+			}

+			pw.print("\n");

+			spaces(pw, indent);

+			pw.print("</");

+			pw.print(name);

+		}

+		pw.print('>');

+	}

+

+	/**

+	 * Convenience method to print a string nicely and does character conversion

+	 * to entities.

+	 */

+	void formatted(PrintWriter pw, int left, int width, String s) {

+		int pos = width + 1;

+		s = s.trim();

+

+		for (int i = 0; i < s.length(); i++) {

+			char c = s.charAt(i);

+			if (i == 0 || (Character.isWhitespace(c) && pos > width - 3)) {

+				pw.print("\n");

+				spaces(pw, left);

+				pos = 0;

+			}

+			switch (c) {

+				case '<' :

+					pw.print("&lt;");

+					pos += 4;

+					break;

+				case '>' :

+					pw.print("&gt;");

+					pos += 4;

+					break;

+				case '&' :

+					pw.print("&amp;");

+					pos += 5;

+					break;

+				default :

+					pw.print(c);

+					pos++;

+					break;

+			}

+

+		}

+	}

+

+	/**

+	 * Escape a string, do entity conversion.

+	 */

+	String escape(String s) {

+		if  ( s == null )

+			return "?null?";

+		

+		StringBuffer sb = new StringBuffer();

+		for (int i = 0; i < s.length(); i++) {

+			char c = s.charAt(i);

+			switch (c) {

+				case '<' :

+					sb.append("&lt;");

+					break;

+				case '>' :

+					sb.append("&gt;");

+					break;

+				case '&' :

+					sb.append("&amp;");

+					break;

+				default :

+					sb.append(c);

+					break;

+			}

+		}

+		return sb.toString();

+	}

+

+	/**

+	 * Make spaces.

+	 */

+	void spaces(PrintWriter pw, int n) {

+		while (n-- > 0)

+			pw.print(' ');

+	}

+

+	/**

+	 * root/preferences/native/os

+	 */

+	public Tag[] select(String path) {

+		return select(path, (Tag) null);

+	}

+

+	public Tag[] select(String path, Tag mapping) {

+		Vector v = new Vector();

+		select(path, v, mapping);

+		Tag[] result = new Tag[v.size()];

+		v.copyInto(result);

+		return result;

+	}

+

+	void select(String path, Vector results, Tag mapping) {

+		if (path.startsWith("//")) {

+			int i = path.indexOf('/', 2);

+			String name = path.substring(2, i < 0 ? path.length() : i);

+

+			for (Enumeration e = content.elements(); e.hasMoreElements();) {

+				Object o = e.nextElement();

+				if (o instanceof Tag) {

+					Tag child = (Tag) o;

+					if (match(name, child, mapping))

+						results.add(child);

+					child.select(path, results, mapping);

+				}

+

+			}

+			return;

+		}

+

+		if (path.length() == 0) {

+			results.addElement(this);

+			return;

+		}

+

+		int i = path.indexOf("/");

+		String elementName = path;

+		String remainder = "";

+		if (i > 0) {

+			elementName = path.substring(0, i);

+			remainder = path.substring(i + 1);

+		}

+

+		for (Enumeration e = content.elements(); e.hasMoreElements();) {

+			Object o = e.nextElement();

+			if (o instanceof Tag) {

+				Tag child = (Tag) o;

+				if (child.getName().equals(elementName)

+						|| elementName.equals("*"))

+					child.select(remainder, results, mapping);

+			}

+		}

+	}

+

+	public boolean match(String search, Tag child, Tag mapping) {

+		String target = child.getName();

+		String sn = null;

+		String tn = null;

+

+		if (search.equals("*"))

+			return true;

+

+		int s = search.indexOf(':');

+		if (s > 0) {

+			sn = search.substring(0, s);

+			search = search.substring(s + 1);

+		}

+		int t = target.indexOf(':');

+		if (t > 0) {

+			tn = target.substring(0, t);

+			target = target.substring(t + 1);

+		}

+

+		if (!search.equals(target)) // different tag names

+			return false;

+

+		if (mapping == null) {

+			return tn == sn || (sn != null && sn.equals(tn));

+		}

+		else {

+			String suri = sn == null ? mapping.getAttribute("xmlns") : mapping

+					.getAttribute("xmlns:" + sn);

+			String turi = tn == null ? child.findRecursiveAttribute("xmlns")

+					: child.findRecursiveAttribute("xmlns:" + tn);

+			return turi == suri

+					|| (turi != null && suri != null && turi.equals(suri));

+		}

+	}

+

+	public String getString(String path) {

+		String attribute = null;

+		int index = path.indexOf("@");

+		if (index >= 0) {

+			// attribute

+			attribute = path.substring(index + 1);

+

+			if (index > 0) {

+				// prefix path

+				path = path.substring(index - 1); // skip -1

+			}

+			else

+				path = "";

+		}

+		Tag tags[] = select(path);

+		StringBuffer sb = new StringBuffer();

+		for (int i = 0; i < tags.length; i++) {

+			if (attribute == null)

+				tags[i].getContentsAsString(sb);

+			else

+				sb.append(tags[i].getAttribute(attribute));

+		}

+		return sb.toString();

+	}

+

+	public String getStringContent() {

+		StringBuffer sb = new StringBuffer();

+		for (Enumeration e = content.elements(); e.hasMoreElements();) {

+			Object c = e.nextElement();

+			if (!(c instanceof Tag))

+				sb.append(c);

+		}

+		return sb.toString();

+	}

+

+	public String getNameSpace() {

+		return getNameSpace(name);

+	}

+

+	public String getNameSpace(String name) {

+		int index = name.indexOf(':');

+		if (index > 0) {

+			String ns = name.substring(0, index);

+			return findRecursiveAttribute("xmlns:" + ns);

+		}

+		else

+			return findRecursiveAttribute("xmlns");

+	}

+

+	public String findRecursiveAttribute(String name) {

+		String value = getAttribute(name);

+		if (value != null)

+			return value;

+		if (parent != null)

+			return parent.findRecursiveAttribute(name);

+		return null;

+	}

+

+	public String getLocalName() {

+		int index = name.indexOf(':');

+		if (index <= 0)

+			return name;

+

+		return name.substring(index + 1);

+	}

+

+	public void rename(String string) {

+		name = string;

+	}

+

+

+	public static void convert( Collection c, String type, Tag parent ) {

+		for ( Iterator i=c.iterator(); i.hasNext(); ) {

+			Map	map = (Map) i.next();

+			parent.addContent( new Tag(type, map) );

+		}

+	}

+

+}

diff --git a/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/VersionRange.java b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/VersionRange.java
new file mode 100644
index 0000000..afc99fc
--- /dev/null
+++ b/bundleplugin/src/main/java/org/osgi/impl/bundle/obr/resource/VersionRange.java
@@ -0,0 +1,119 @@
+/*

+ * $Id: VersionRange.java 45 2007-10-01 12:56:02Z peter.kriens@aqute.biz $

+ * 

+ * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+package org.osgi.impl.bundle.obr.resource;

+

+import java.util.regex.*;

+

+import org.osgi.framework.*;

+

+public class VersionRange implements Comparable {

+	Version high;

+	Version low;

+	char start = '[';

+	char end = ']';

+

+	static String V = "\\s*[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[a-zA-Z0-9_-]+)?)?)?\\s*";

+	static Pattern RANGE = Pattern.compile("(\\(|\\[)(" + V + "),(" + V

+			+ ")(\\)|\\])");

+

+	public VersionRange(String string) {

+		string = string.trim();

+		Matcher m = RANGE.matcher(string);

+		if (m.matches()) {

+			start = m.group(1).charAt(0);

+			low = new Version(m.group(2).trim());

+			high = new Version(m.group(6).trim());

+			end = m.group(10).charAt(0);

+			if (low.compareTo(high) >= 0)

+				throw new IllegalArgumentException(

+						"Low Range is higher than High Range: " + low + "-"

+								+ high);

+

+		} else

+			high = low = new Version(string);

+	}

+

+	public boolean isRange() {

+		return high != low;

+	}

+

+	public boolean includeLow() {

+		return start == '[';

+	}

+

+	public boolean includeHigh() {

+		return end == ']';

+	}

+

+	public String toString() {

+		if (high == low)

+			return high.toString();

+

+		StringBuffer sb = new StringBuffer();

+		sb.append(start);

+		sb.append(low);

+		sb.append(',');

+		sb.append(high);

+		sb.append(end);

+		return sb.toString();

+	}

+

+	public boolean equals(Object other) {

+		if (other instanceof VersionRange) {

+			return compareTo(other)==0;

+		}

+		return false;

+	}

+

+	public int hashCode() {

+		return low.hashCode() * high.hashCode();

+	}

+

+	public int compareTo(Object other) {

+		VersionRange range = (VersionRange) other;

+		VersionRange a = this, b = range;

+		if (range.isRange()) {

+			a = range;

+			b = this;

+		} else {

+			if ( !isRange() )

+				return low.compareTo(range.high);

+		}

+		int l = a.low.compareTo(b.low);

+		boolean ll = false;

+		if (a.includeLow())

+			ll = l <= 0;

+		else

+			ll = l < 0;

+

+		if (!ll)

+			return -1;

+

+		int h = a.high.compareTo(b.high);

+		if (a.includeHigh())

+			ll = h >= 0;

+		else

+			ll = h > 0;

+

+		if (ll)

+			return 0;

+		else

+			return 1;

+	}

+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/resources/META-INF/plexus/components.xml b/bundleplugin/src/main/resources/META-INF/plexus/components.xml
index c26255a..5c99b1f 100644
--- a/bundleplugin/src/main/resources/META-INF/plexus/components.xml
+++ b/bundleplugin/src/main/resources/META-INF/plexus/components.xml
@@ -38,7 +38,10 @@
                 org.apache.maven.plugins:maven-install-plugin:install,
                 org.apache.felix:maven-bundle-plugin:install
               </install>
-              <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
+              <deploy>
+                org.apache.maven.plugins:maven-deploy-plugin:deploy<!--,
+         TODO:  org.apache.felix:maven-bundle-plugin:deploy-->
+              </deploy>
             </phases>
             <!-- END SNIPPET: bundle-lifecycle -->
           </lifecycle>
diff --git a/bundleplugin/src/main/resources/SchemaObr.xsd b/bundleplugin/src/main/resources/SchemaObr.xsd
new file mode 100644
index 0000000..63b63e9
--- /dev/null
+++ b/bundleplugin/src/main/resources/SchemaObr.xsd
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>

+<!--

+ Licensed to the Apache Software Foundation (ASF) under one

+ or more contributor license agreements.  See the NOTICE file

+ distributed with this work for additional information

+ regarding copyright ownership.  The ASF licenses this file

+ to you under the Apache License, Version 2.0 (the

+ "License"); you may not use this file except in compliance

+ with the License.  You may obtain a copy of the License at

+

+    http://www.apache.org/licenses/LICENSE-2.0

+

+ Unless required by applicable law or agreed to in writing,

+ software distributed under the License is distributed on an

+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ KIND, either express or implied.  See the License for the

+ specific language governing permissions and limitations

+ under the License.

+-->

+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

+<!-- definition of simple elements -->

+

+<!-- definition of attribute  -->

+<xs:attribute name="name"     type="xs:string"/>

+<xs:attribute name="filter"   type="xs:string"/>

+<xs:attribute name="extend"   type="xs:boolean"/>

+<xs:attribute name="multiple" type="xs:boolean"/>

+<xs:attribute name="optional" type="xs:boolean"/>

+<xs:attribute name="id" type="xs:string"/>

+<xs:attribute name="n" type="xs:string"/>

+<xs:attribute name="v" type="xs:string"/>

+<xs:attribute name="t" type="xs:string"/>

+

+<xs:element name="require">

+  <xs:complexType mixed="true">

+    <xs:attribute ref="name"     use="required"/>

+    <xs:attribute ref="filter"   use="required"/>

+    <xs:attribute ref="extend"   use="required"/>

+    <xs:attribute ref="multiple" use="required"/>

+    <xs:attribute ref="optional" use="required"/>

+  </xs:complexType>

+</xs:element>

+

+<xs:element name="category">

+  <xs:complexType>

+	<xs:attribute ref="id" use="required"/>

+  </xs:complexType>

+</xs:element>

+

+<xs:element name="p">

+  <xs:complexType>

+	<xs:attribute ref="n" use="required"/>

+    <xs:attribute ref="v" use="required"/>

+    <xs:attribute ref="t" />

+  </xs:complexType>

+</xs:element>

+

+

+<xs:element name="capability">

+  <xs:complexType>

+    <xs:sequence>

+  		<xs:element ref="p" minOccurs="0" maxOccurs="unbounded"/>

+	</xs:sequence>

+  </xs:complexType>

+</xs:element>

+

+<xs:element name="resource">

+  <xs:complexType>

+    <xs:sequence>

+		  <xs:element ref="capability" minOccurs="0" maxOccurs="unbounded"/> 

+		  <xs:element ref="require" minOccurs="0" maxOccurs="unbounded"/> 

+		  <xs:element ref="category" minOccurs="0" maxOccurs="unbounded"/> 

+	</xs:sequence>

+  </xs:complexType>

+</xs:element>

+</xs:schema>