FELIX-553: add bundle:remote-clean goal (use -DremoteOBR=http://... to specify repository prefix)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@682012 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/org/apache/felix/obrplugin/ObrRemoteClean.java b/bundleplugin/src/main/java/org/apache/felix/obrplugin/ObrRemoteClean.java
new file mode 100644
index 0000000..26f8487
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/obrplugin/ObrRemoteClean.java
@@ -0,0 +1,453 @@
+/*
+ * 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.obrplugin;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.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;
+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 a remote repository file.
+ * It just looks for every resources and check that pointed file exists.
+ *
+ * @requiresProject false
+ * @goal remote-clean
+ * @phase clean
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class ObrRemoteClean extends AbstractMojo
+{
+ /**
+ * When true, ignore remote locking.
+ *
+ * @parameter expression="${ignoreLock}"
+ */
+ private boolean ignoreLock;
+
+ /**
+ * Remote OBR Repository.
+ *
+ * @parameter expression="${remoteOBR}" default-value="NONE"
+ */
+ private String remoteOBR;
+
+ /**
+ * Local 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;
+
+ /**
+ * 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
+ {
+ String projectType = project.getPackaging();
+
+ // ignore unsupported project types, useful when bundleplugin is configured in parent pom
+ if ( !supportedProjectTypes.contains( projectType ) )
+ {
+ getLog().warn( "Ignoring project type " + projectType +
+ " - supportedProjectTypes = " + supportedProjectTypes );
+ return;
+ }
+ else if ( "NONE".equalsIgnoreCase( remoteOBR ) || "false".equalsIgnoreCase( remoteOBR ) )
+ {
+ getLog().info( "Remote OBR update disabled (enable with -DremoteOBR)" );
+ return;
+ }
+
+ // if the user doesn't supply an explicit name for the remote OBR file, use the local name instead
+ if ( null == remoteOBR || remoteOBR.trim().length() == 0 || "true".equalsIgnoreCase( remoteOBR ) )
+ {
+ remoteOBR = obrRepository;
+ }
+
+ URI tempURI = ObrUtils.findRepositoryXml( "", remoteOBR );
+ String repositoryName = new File( tempURI.getSchemeSpecificPart() ).getName();
+
+ Log log = getLog();
+
+ 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" );
+
+ URI repositoryXml = downloadedRepositoryXml.toURI();
+
+ Config userConfig = new Config();
+ userConfig.setRemoteFile( true );
+
+ // Clean the downloaded file.
+ Document doc = parseFile(new File(repositoryXml), initConstructor());
+ Node finalDocument = cleanDocument(tempURI, doc.getDocumentElement());
+
+ if ( finalDocument == null )
+ {
+ getLog().info( "Nothing to clean in " + repositoryName);
+ }
+ else
+ {
+ writeToFile( repositoryXml, finalDocument ); // Write the new file
+ getLog().info( "Repository " + repositoryName + " cleaned" );
+ // ======== 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() );
+ }
+ }
+
+ /**
+ * Analyze the given XML tree (DOM of the repository file) and remove missing resources.
+ * This method ask the user before deleting the resources from the repository.
+ * @param elem : the input XML tree
+ * @return the cleaned XML tree
+ */
+ private Element cleanDocument( URI prefix, Element elem )
+ {
+ 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" );
+
+ URL url;
+ try {
+ url = new URL(prefix.toURL(), value);
+ } catch (MalformedURLException e) {
+ getLog().error("Malformed URL when creating the resource absolute URI : " + e.getMessage());
+ return null;
+ }
+
+ try {
+ url.openConnection().getContent();
+ } catch (IOException e) {
+ getLog().info(
+ "The bundle " + n.getAttribute( "presentationname" ) + " - " + n.getAttribute( "version" )
+ + " will be removed : " + e.getMessage() );
+ toRemove.add( n );
+ }
+ }
+
+ Date d = new Date();
+ if ( toRemove.size() > 0 )
+ {
+ System.out.println("Do you want to remove these bundles from the repository file [y/N]:");
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ String answer = null;
+
+ try {
+ answer = br.readLine();
+ } catch (IOException ioe) {
+ getLog().error("IO error trying to read the user confirmation");
+ return null;
+ }
+
+ if (answer != null && answer.trim().equalsIgnoreCase("y")) {
+ // 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;
+ } else {
+ return null;
+ }
+ }
+
+ 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() );
+ }
+ }
+}