View Javadoc
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.apache.maven.plugins.annotations.LifecyclePhase;
47  import org.apache.maven.plugins.annotations.Mojo;
48  import org.apache.maven.plugins.annotations.Parameter;
49  import org.codehaus.plexus.util.FileUtils;
50  import org.w3c.dom.Document;
51  import org.w3c.dom.Element;
52  import org.w3c.dom.Node;
53  import org.w3c.dom.NodeList;
54  import org.xml.sax.SAXException;
55  
56  
57  /**
58   * Clean an OBR repository by finding and removing missing resources.
59   *
60   * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
61   */
62  @Mojo( name = "clean", requiresProject = false, defaultPhase = LifecyclePhase.CLEAN )
63  public class ObrCleanRepo extends AbstractMojo
64  {
65      /**
66       * OBR Repository.
67       */
68      @Parameter( property = "obrRepository" )
69      private String obrRepository;
70  
71      /**
72       * Local Repository.
73       */
74      @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
75      private ArtifactRepository localRepository;
76  
77  
78      public void execute()
79      {
80          if ( "NONE".equalsIgnoreCase( obrRepository ) || "false".equalsIgnoreCase( obrRepository ) )
81          {
82              getLog().info( "Local OBR clean disabled (enable with -DobrRepository)" );
83              return;
84          }
85  
86          try
87          {
88              // Compute local repository location
89              URI repositoryXml = ObrUtils.findRepositoryXml( localRepository.getBasedir(), obrRepository );
90              if ( !"file".equals( repositoryXml.getScheme() ) )
91              {
92                  getLog().error( "The repository URI " + repositoryXml + " is not a local file" );
93                  return;
94              }
95  
96              File repositoryFile = new File( repositoryXml );
97  
98              // Check if the file exist
99              if ( !repositoryFile.exists() )
100             {
101                 getLog().error( "The repository file " + repositoryFile + " does not exist" );
102                 return;
103             }
104 
105             getLog().info( "Cleaning..." );
106 
107             Document doc = parseFile( repositoryFile, initConstructor() );
108             Node finalDocument = cleanDocument( doc.getDocumentElement() ); // Analyze existing repository.
109 
110             if ( finalDocument == null )
111             {
112                 getLog().info( "Nothing to clean in " + repositoryFile );
113             }
114             else
115             {
116                 writeToFile( repositoryXml, finalDocument ); // Write the new file
117                 getLog().info( "Repository " + repositoryFile + " cleaned" );
118             }
119         }
120         catch ( Exception e )
121         {
122             getLog().error( "Exception while cleaning local OBR: " + e.getLocalizedMessage(), e );
123         }
124     }
125 
126 
127     /**
128      * Analyze the given XML tree (DOM of the repository file) and remove missing resources.
129      *
130      * @param elem : the input XML tree
131      * @return the cleaned XML tree
132      */
133     private Element cleanDocument( Element elem )
134     {
135         String localRepoPath = localRepository.getBasedir();
136         URI baseURI = new File( localRepoPath + '/' ).toURI();
137         NodeList nodes = elem.getElementsByTagName( "resource" );
138         List toRemove = new ArrayList();
139 
140         // First, look for missing resources
141         for ( int i = 0; i < nodes.getLength(); i++ )
142         {
143             Element n = ( Element ) nodes.item( i );
144             String value = n.getAttribute( "uri" );
145 
146             URI resource;
147             try
148             {
149                 resource = baseURI.resolve( value );
150             }
151             catch ( IllegalArgumentException e )
152             {
153                 getLog().error( "Malformed URL when creating the resource absolute URI : " + e.getMessage() );
154                 return null;
155             }
156 
157             if ( "file".equals( resource.getScheme() ) && !new File( resource ).exists() )
158             {
159                 getLog().info(
160                     "The bundle " + n.getAttribute( "presentationname" ) + " - " + n.getAttribute( "version" )
161                         + " will be removed" );
162                 toRemove.add( n );
163             }
164         }
165 
166         Date d = new Date();
167         if ( toRemove.size() > 0 )
168         {
169             // Then remove missing resources.
170             for ( int i = 0; i < toRemove.size(); i++ )
171             {
172                 elem.removeChild( ( Node ) toRemove.get( i ) );
173             }
174 
175             // If we have to remove resources, we need to update 'lastmodified' attribute
176             SimpleDateFormat format = new SimpleDateFormat( "yyyyMMddHHmmss.SSS" );
177             d.setTime( System.currentTimeMillis() );
178             elem.setAttribute( "lastmodified", format.format( d ) );
179             return elem;
180         }
181 
182         return null;
183     }
184 
185 
186     /**
187      * Initialize the document builder from Xerces.
188      *
189      * @return DocumentBuilder ready to create new document
190      * @throws MojoExecutionException : occurs when the instantiation of the document builder fails
191      */
192     private DocumentBuilder initConstructor() throws MojoExecutionException
193     {
194         DocumentBuilder constructor = null;
195         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
196         try
197         {
198             constructor = factory.newDocumentBuilder();
199         }
200         catch ( ParserConfigurationException e )
201         {
202             getLog().error( "Unable to create a new xml document" );
203             throw new MojoExecutionException( "Cannot create the Document Builder : " + e.getMessage() );
204         }
205         return constructor;
206     }
207 
208 
209     /**
210      * Open an XML file.
211      *
212      * @param file : XML file path
213      * @param constructor DocumentBuilder get from xerces
214      * @return Document which describes this file
215      * @throws MojoExecutionException occurs when the given file cannot be opened or is a valid XML file.
216      */
217     private Document parseFile( File file, DocumentBuilder constructor ) throws MojoExecutionException
218     {
219         if ( constructor == null )
220         {
221             return null;
222         }
223         // The document is the root of the DOM tree.
224         File targetFile = file.getAbsoluteFile();
225         getLog().info( "Parsing " + targetFile );
226         Document doc = null;
227         try
228         {
229             doc = constructor.parse( targetFile );
230         }
231         catch ( SAXException e )
232         {
233             getLog().error( "Cannot parse " + targetFile + " : " + e.getMessage() );
234             throw new MojoExecutionException( "Cannot parse " + targetFile + " : " + e.getMessage() );
235         }
236         catch ( IOException e )
237         {
238             getLog().error( "Cannot open " + targetFile + " : " + e.getMessage() );
239             throw new MojoExecutionException( "Cannot open " + targetFile + " : " + e.getMessage() );
240         }
241         return doc;
242     }
243 
244 
245     /**
246      * write a Node in a xml file.
247      *
248      * @param outputFilename URI to the output file
249      * @param treeToBeWrite Node root of the tree to be write in file
250      * @throws MojoExecutionException if the plugin failed
251      */
252     private void writeToFile( URI outputFilename, Node treeToBeWrite ) throws MojoExecutionException
253     {
254         // init the transformer
255         Transformer transformer = null;
256         TransformerFactory tfabrique = TransformerFactory.newInstance();
257         try
258         {
259             transformer = tfabrique.newTransformer();
260         }
261         catch ( TransformerConfigurationException e )
262         {
263             getLog().error( "Unable to write to file: " + outputFilename.toString() );
264             throw new MojoExecutionException( "Unable to write to file: " + outputFilename.toString() + " : "
265                 + e.getMessage() );
266         }
267         Properties proprietes = new Properties();
268         proprietes.put( "method", "xml" );
269         proprietes.put( "version", "1.0" );
270         proprietes.put( "encoding", "ISO-8859-1" );
271         proprietes.put( "standalone", "yes" );
272         proprietes.put( "indent", "yes" );
273         proprietes.put( "omit-xml-declaration", "no" );
274         transformer.setOutputProperties( proprietes );
275 
276         DOMSource input = new DOMSource( treeToBeWrite );
277 
278         File fichier = null;
279         FileOutputStream flux = null;
280         try
281         {
282             fichier = File.createTempFile( "repository", ".xml" );
283             flux = new FileOutputStream( fichier );
284         }
285         catch ( IOException e )
286         {
287             getLog().error( "Unable to write to file: " + fichier.getName() );
288             throw new MojoExecutionException( "Unable to write to file: " + fichier.getName() + " : " + e.getMessage() );
289         }
290         Result output = new StreamResult( flux );
291         try
292         {
293             transformer.transform( input, output );
294         }
295         catch ( TransformerException e )
296         {
297             throw new MojoExecutionException( "Unable to write to file: " + outputFilename.toString() + " : "
298                 + e.getMessage() );
299         }
300 
301         try
302         {
303             flux.flush();
304             flux.close();
305 
306             FileUtils.rename( fichier, new File( outputFilename ) );
307         }
308         catch ( IOException e )
309         {
310             throw new MojoExecutionException( "IOException when closing file : " + e.getMessage() );
311         }
312     }
313 }