    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.felix.obrplugin;
    20
    21
    22  import java.io.File;
    23  import java.io.FileOutputStream;
    24  import java.io.IOException;
    25  import java.net.URI;
    26  import java.text.SimpleDateFormat;
    27  import java.util.ArrayList;
    28  import java.util.Date;
    29  import java.util.List;
    30  import java.util.Properties;
    31
    32  import javax.xml.parsers.DocumentBuilder;
    33  import javax.xml.parsers.DocumentBuilderFactory;
    34  import javax.xml.parsers.ParserConfigurationException;
    35  import javax.xml.transform.Result;
    36  import javax.xml.transform.Transformer;
    37  import javax.xml.transform.TransformerConfigurationException;
    38  import javax.xml.transform.TransformerException;
    39  import javax.xml.transform.TransformerFactory;
    40  import javax.xml.transform.dom.DOMSource;
    41  import javax.xml.transform.stream.StreamResult;
    42
    43  import org.apache.maven.artifact.repository.ArtifactRepository;
    44  import org.apache.maven.plugin.AbstractMojo;
    45  import org.apache.maven.plugin.MojoExecutionException;
    46  import org.codehaus.plexus.util.FileUtils;
    47  import org.w3c.dom.Document;
    48  import org.w3c.dom.Element;
    49  import org.w3c.dom.Node;
    50  import org.w3c.dom.NodeList;
    51  import org.xml.sax.SAXException;
    52
    53
    54  /**
    55   * Clean an OBR repository by finding and removing missing resources.
    56   *
    57   * @requiresProject false
    58   * @goal clean
    59   * @phase clean
    60   *
    61   * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
    62   */
    63  public class ObrCleanRepo extends AbstractMojo
    64  {
    65      /**
    66       * OBR Repository.
    67       *
    68       * @parameter expression="${obrRepository}"
    69       */
    70      private String obrRepository;
    71
    72      /**
    73       * Local Repository.
    74       *
    75       * @parameter expression="${localRepository}"
    76       * @required
    77       * @readonly
    78       */
    79      private ArtifactRepository localRepository;
    80
    81
    82      public void execute()
    83      {
    84          if ( "NONE".equalsIgnoreCase( obrRepository ) || "false".equalsIgnoreCase( obrRepository ) )
    85          {
    86              getLog().info( "Local OBR clean disabled (enable with -DobrRepository)" );
    87              return;
    88          }
    89
    90          try
    91          {
    92              // Compute local repository location
    93              URI repositoryXml = ObrUtils.findRepositoryXml( localRepository.getBasedir(), obrRepository );
    94              if ( !"file".equals( repositoryXml.getScheme() ) )
    95              {
    96                  getLog().error( "The repository URI " + repositoryXml + " is not a local file" );
    97                  return;
    98              }
    99
    100             File repositoryFile = new File( repositoryXml );
    101
    102             // Check if the file exist
    103             if ( !repositoryFile.exists() )
    104             {
    105                 getLog().error( "The repository file " + repositoryFile + " does not exist" );
    106                 return;
    107             }
    108
    109             getLog().info( "Cleaning..." );
    110
    111             Document doc = parseFile( repositoryFile, initConstructor() );
    112             Node finalDocument = cleanDocument( doc.getDocumentElement() ); // Analyze existing repository.
    113
    114             if ( finalDocument == null )
    115             {
    116                 getLog().info( "Nothing to clean in " + repositoryFile );
    117             }
    118             else
    119             {
    120                 writeToFile( repositoryXml, finalDocument ); // Write the new file
    121                 getLog().info( "Repository " + repositoryFile + " cleaned" );
    122             }
    123         }
    124         catch ( Exception e )
    125         {
    126             getLog().error( "Exception while cleaning local OBR: " + e.getLocalizedMessage(), e );
    127         }
    128     }
    129
    130
    131     /**
    132      * Analyze the given XML tree (DOM of the repository file) and remove missing resources.
    133      *
    134      * @param elem : the input XML tree
    135      * @return the cleaned XML tree
    136      */
    137     private Element cleanDocument( Element elem )
    138     {
    139         String localRepoPath = localRepository.getBasedir();
    140         URI baseURI = new File( localRepoPath + '/' ).toURI();
    141         NodeList nodes = elem.getElementsByTagName( "resource" );
    142         List toRemove = new ArrayList();
    143
    144         // First, look for missing resources
    145         for ( int i = 0; i < nodes.getLength(); i++ )
    146         {
    147             Element n = ( Element ) nodes.item( i );
    148             String value = n.getAttribute( "uri" );
    149
    150             URI resource;
    151             try
    152             {
    153                 resource = baseURI.resolve( value );
    154             }
    155             catch ( IllegalArgumentException e )
    156             {
    157                 getLog().error( "Malformed URL when creating the resource absolute URI : " + e.getMessage() );
    158                 return null;
    159             }
    160
    161             if ( "file".equals( resource.getScheme() ) && !new File( resource ).exists() )
    162             {
    163                 getLog().info(
    164                     "The bundle " + n.getAttribute( "presentationname" ) + " - " + n.getAttribute( "version" )
    165                         + " will be removed" );
    166                 toRemove.add( n );
    167             }
    168         }
    169
    170         Date d = new Date();
    171         if ( toRemove.size() > 0 )
    172         {
    173             // Then remove missing resources.
    174             for ( int i = 0; i < toRemove.size(); i++ )
    175             {
    176                 elem.removeChild( ( Node ) toRemove.get( i ) );
    177             }
    178
    179             // If we have to remove resources, we need to update 'lastmodified' attribute
    180             SimpleDateFormat format = new SimpleDateFormat( "yyyyMMddHHmmss.SSS" );
    181             d.setTime( System.currentTimeMillis() );
    182             elem.setAttribute( "lastmodified", format.format( d ) );
    183             return elem;
    184         }
    185
    186         return null;
    187     }
    188
    189
    190     /**
    191      * Initialize the document builder from Xerces.
    192      *
    193      * @return DocumentBuilder ready to create new document
    194      * @throws MojoExecutionException : occurs when the instantiation of the document builder fails
    195      */
    196     private DocumentBuilder initConstructor() throws MojoExecutionException
    197     {
    198         DocumentBuilder constructor = null;
    199         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    200         try
    201         {
    202             constructor = factory.newDocumentBuilder();
    203         }
    204         catch ( ParserConfigurationException e )
    205         {
    206             getLog().error( "Unable to create a new xml document" );
    207             throw new MojoExecutionException( "Cannot create the Document Builder : " + e.getMessage() );
    208         }
    209         return constructor;
    210     }
    211
    212
    213     /**
    214      * Open an XML file.
    215      *
    216      * @param file : XML file path
    217      * @param constructor DocumentBuilder get from xerces
    218      * @return Document which describes this file
    219      * @throws MojoExecutionException occurs when the given file cannot be opened or is a valid XML file.
    220      */
    221     private Document parseFile( File file, DocumentBuilder constructor ) throws MojoExecutionException
    222     {
    223         if ( constructor == null )
    224         {
    225             return null;
    226         }
    227         // The document is the root of the DOM tree.
    228         File targetFile = file.getAbsoluteFile();
    229         getLog().info( "Parsing " + targetFile );
    230         Document doc = null;
    231         try
    232         {
    233             doc = constructor.parse( targetFile );
    234         }
    235         catch ( SAXException e )
    236         {
    237             getLog().error( "Cannot parse " + targetFile + " : " + e.getMessage() );
    238             throw new MojoExecutionException( "Cannot parse " + targetFile + " : " + e.getMessage() );
    239         }
    240         catch ( IOException e )
    241         {
    242             getLog().error( "Cannot open " + targetFile + " : " + e.getMessage() );
    243             throw new MojoExecutionException( "Cannot open " + targetFile + " : " + e.getMessage() );
    244         }
    245         return doc;
    246     }
    247
    248
    249     /**
    250      * write a Node in a xml file.
    251      *
    252      * @param outputFilename URI to the output file
    253      * @param treeToBeWrite Node root of the tree to be write in file
    254      * @throws MojoExecutionException if the plugin failed
    255      */
    256     private void writeToFile( URI outputFilename, Node treeToBeWrite ) throws MojoExecutionException
    257     {
    258         // init the transformer
    259         Transformer transformer = null;
    260         TransformerFactory tfabrique = TransformerFactory.newInstance();
    261         try
    262         {
    263             transformer = tfabrique.newTransformer();
    264         }
    265         catch ( TransformerConfigurationException e )
    266         {
    267             getLog().error( "Unable to write to file: " + outputFilename.toString() );
    268             throw new MojoExecutionException( "Unable to write to file: " + outputFilename.toString() + " : "
    269                 + e.getMessage() );
    270         }
    271         Properties proprietes = new Properties();
    272         proprietes.put( "method", "xml" );
    273         proprietes.put( "version", "1.0" );
    274         proprietes.put( "encoding", "ISO-8859-1" );
    275         proprietes.put( "standalone", "yes" );
    276         proprietes.put( "indent", "yes" );
    277         proprietes.put( "omit-xml-declaration", "no" );
    278         transformer.setOutputProperties( proprietes );
    279
    280         DOMSource input = new DOMSource( treeToBeWrite );
    281
    282         File fichier = null;
    283         FileOutputStream flux = null;
    284         try
    285         {
    286             fichier = File.createTempFile( "repository", ".xml" );
    287             flux = new FileOutputStream( fichier );
    288         }
    289         catch ( IOException e )
    290         {
    291             getLog().error( "Unable to write to file: " + fichier.getName() );
    292             throw new MojoExecutionException( "Unable to write to file: " + fichier.getName() + " : " + e.getMessage() );
    293         }
    294         Result output = new StreamResult( flux );
    295         try
    296         {
    297             transformer.transform( input, output );
    298         }
    299         catch ( TransformerException e )
    300         {
    301             throw new MojoExecutionException( "Unable to write to file: " + outputFilename.toString() + " : "
    302                 + e.getMessage() );
    303         }
    304
    305         try
    306         {
    307             flux.flush();
    308             flux.close();
    309
    310             FileUtils.rename( fichier, new File( outputFilename ) );
    311         }
    312         catch ( IOException e )
    313         {
    314             throw new MojoExecutionException( "IOException when closing file : " + e.getMessage() );
    315         }
    316     }
    317 }