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 }