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.maven.plugin.eclipse.writers;
20  
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.util.Arrays;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.Set;
29  import java.util.jar.Attributes;
30  import java.util.jar.Manifest;
31  
32  import org.apache.maven.artifact.repository.ArtifactRepository;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.eclipse.EclipseSourceDir;
35  import org.apache.maven.plugin.eclipse.Messages;
36  import org.apache.maven.plugin.eclipse.writers.wtp.AbstractWtpResourceWriter;
37  import org.apache.maven.plugin.ide.IdeDependency;
38  import org.apache.maven.project.MavenProject;
39  import org.codehaus.plexus.util.IOUtil;
40  
41  /**
42   * Common behaviours for creating or adapting the manifest files for eclipse environments.
43   * 
44   * @author <a href="mailto:nir@cfc.at">Richard van Nieuwenhoven </a>
45   */
46  public abstract class AbstractEclipseManifestWriter
47      extends AbstractEclipseWriter
48  {
49  
50      protected static final String MANIFEST_MF_FILENAME = "MANIFEST.MF";
51  
52      protected static final String META_INF_DIRECTORY = "META-INF";
53  
54      public AbstractEclipseManifestWriter()
55      {
56          super();
57      }
58  
59      /**
60       * Aphabeticaly sort the classpath. Do this by splitting it up, sort the entries and gleue them together again.
61       * 
62       * @param newValue classpath to sort
63       * @return the sorted classpath
64       */
65      protected String orderClasspath( String newValue )
66      {
67          if ( newValue == null )
68          {
69              return null;
70          }
71          String[] entries = newValue.split( " " );
72          Arrays.sort( entries );
73          StringBuffer buffer = new StringBuffer( newValue.length() );
74          for ( int index = 0; index < entries.length; index++ )
75          {
76              buffer.append( entries[index] );
77              buffer.append( ' ' );
78          }
79          return buffer.toString();
80      }
81  
82      /**
83       * Read and parse the existing manifest file.
84       * 
85       * @param manifestFile file
86       * @return the read manifest
87       * @throws IOException if the file could not be read
88       */
89      protected Manifest readExistingManifest( File manifestFile )
90          throws IOException
91      {
92          if ( !manifestFile.exists() )
93          {
94              return null;
95          }
96  
97          Manifest existingManifest = new Manifest();
98          FileInputStream inputStream = new FileInputStream( manifestFile );
99          try
100         {
101             existingManifest.read( inputStream );
102         }
103         finally
104         {
105             IOUtil.close( inputStream );
106         }
107         return existingManifest;
108     }
109 
110     /**
111      * Add one dependency to the blank separated classpath stringbuffer. When the project is available in the reactor
112      * (current build) then the project is used else the jar representing the artifact. System dependencies will only be
113      * included if they are in this project.
114      * 
115      * @param classpath existing classpath to append
116      * @param dependency dependency to append as jar or as project
117      */
118     protected void addDependencyToClassPath( StringBuffer classpath, IdeDependency dependency )
119     {
120         if ( !dependency.isTestDependency() && !dependency.isProvided()
121             && !dependency.isSystemScopedOutsideProject( this.config.getProject() ) )
122         {
123 
124             // blank is the separator in manifest classpath's
125             if ( classpath.length() != 0 )
126             {
127                 classpath.append( ' ' );
128             }
129             // if the dependency is a workspace project add the project and not
130             // the jar
131             if ( !dependency.isReferencedProject() )
132             {
133                 classpath.append( dependency.getFile().getName() );
134             }
135             else
136             {
137                 classpath.append( dependency.getEclipseProjectName() + ".jar" );
138             }
139         }
140     }
141 
142     /**
143      * Check if the two manifests are equal. Manifest.equal can not be used because of the special case the Classpath
144      * entr, witch must be comaired sorted so that a different oder in the classpath does not result in "not equal".
145      * This not not realy correct but in this case it is more important to reduce the number of version-controll files.
146      * 
147      * @param manifest the new manifest
148      * @param existingManifest to compaire the new one with
149      * @return are the manifests equal
150      */
151     protected boolean areManifestsEqual( Manifest manifest, Manifest existingManifest )
152     {
153         if ( existingManifest == null )
154         {
155             log.info( "@@@ FALSE - Manifest are not equal because existingManifest is null" );
156             return false;
157         }
158 
159         Set keys = new HashSet();
160         Attributes existingMap = existingManifest.getMainAttributes();
161         Attributes newMap = manifest.getMainAttributes();
162         keys.addAll( existingMap.keySet() );
163         keys.addAll( newMap.keySet() );
164         Iterator iterator = keys.iterator();
165         while ( iterator.hasNext() )
166         {
167             Attributes.Name key = (Attributes.Name) iterator.next();
168             String newValue = (String) newMap.get( key );
169             String existingValue = (String) existingMap.get( key );
170             // special case classpath... they are equal when there entries
171             // are equal
172             if ( Attributes.Name.CLASS_PATH.equals( key ) )
173             {
174                 newValue = orderClasspath( newValue );
175                 existingValue = orderClasspath( existingValue );
176             }
177             if ( ( newValue == null || !newValue.equals( existingValue ) )
178                 && ( existingValue == null || !existingValue.equals( newValue ) ) )
179             {
180                 log.info( "@@@ FALSE - Manifest are not equal because key = " + key + " has existing value = "
181                     + existingValue + " and new value = " + newValue + " are different" );
182                 return false;
183             }
184         }
185         log.info( "@@@ TRUE - Manifests are equal" );
186         return true;
187     }
188 
189     /**
190      * Convert all dependencies in a blank seperated list of jars and projects representing the classpath.
191      * 
192      * @return the blank separeted classpath string
193      */
194     protected String constructManifestClasspath()
195     {
196         StringBuffer stringBuffer = new StringBuffer();
197         IdeDependency[] deps = this.config.getDepsOrdered();
198 
199         for ( int index = 0; index < deps.length; index++ )
200         {
201             addDependencyToClassPath( stringBuffer, deps[index] );
202         }
203 
204         return stringBuffer.toString();
205     }
206 
207     /**
208      * Create a manifest contaigning the required classpath.
209      * 
210      * @return the newly created manifest
211      */
212     protected Manifest createNewManifest()
213     {
214         Manifest manifest = new Manifest();
215         manifest.getMainAttributes().put( Attributes.Name.MANIFEST_VERSION, "1.0" );
216         manifest.getMainAttributes().put( Attributes.Name.CLASS_PATH, constructManifestClasspath() );
217         return manifest;
218     }
219 
220     /**
221      * Verify is the manifest sould be overwritten this sould take in account that the manifest should only be written
222      * if the contents of the classpath was changed not the order. The classpath sorting oder should be ignored.
223      * 
224      * @param manifest the newly created classpath
225      * @param manifestFile the file where the manifest
226      * @return if the new manifest file must be written
227      * @throws MojoExecutionException
228      */
229     protected boolean shouldNewManifestFileBeWritten( Manifest manifest, File manifestFile )
230         throws MojoExecutionException
231     {
232         try
233         {
234             Manifest existingManifest = readExistingManifest( manifestFile );
235             if ( areManifestsEqual( manifest, existingManifest ) )
236             {
237                 this.log.info( Messages.getString( "EclipsePlugin.unchangedmanifest", manifestFile.getAbsolutePath() ) );
238                 return false;
239             }
240         }
241         catch ( Exception e )
242         {
243             throw new MojoExecutionException( Messages.getString( "EclipseCleanMojo.nofilefound",
244                                                                   manifestFile.getAbsolutePath() ), e );
245         }
246         return true;
247     }
248 
249     /**
250      * Search the project for the existing META-INF directory where the manifest should be located.
251      * 
252      * @return the absolute path to the META-INF directory
253      * @throws MojoExecutionException
254      */
255     protected abstract String getMetaInfBaseDirectory( MavenProject project )
256         throws MojoExecutionException;
257 
258     /**
259      * If the existing manifest file located in <code>getMetaInfBaseDirectory()</code> already has a correct
260      * MANIFEST_VERSION and CLASS_PATH value then do nothing.
261      * <p>
262      * Otherwise generate a <b>NEW</b> (i.e the old one is overwritten) which only contains values for MANIFEST_VERSION
263      * and CLASS_PATH, all other previous entries are not kept.
264      * 
265      * @see AbstractWtpResourceWriter#write(EclipseSourceDir[], ArtifactRepository, File)
266      * @param sourceDirs all eclipse source directorys
267      * @param localRepository the local reposetory
268      * @param buildOutputDirectory build output directory (target)
269      * @throws MojoExecutionException when writing the config files was not possible
270      */
271     public void write()
272         throws MojoExecutionException
273     {
274         String metaInfBaseDirectory = getMetaInfBaseDirectory( this.config.getProject() );
275 
276         if ( metaInfBaseDirectory == null )
277         {
278             // TODO: if this really is an error, shouldn't we stop the build??
279             throw new MojoExecutionException(
280                                               Messages.getString(
281                                                                   "EclipseCleanMojo.nofilefound",
282                                                                   new Object[] { EclipseManifestWriter.META_INF_DIRECTORY } ) );
283         }
284         File manifestFile =
285             new File( metaInfBaseDirectory + File.separatorChar + EclipseManifestWriter.META_INF_DIRECTORY
286                 + File.separatorChar + EclipseManifestWriter.MANIFEST_MF_FILENAME );
287         Manifest manifest = createNewManifest();
288 
289         if ( shouldNewManifestFileBeWritten( manifest, manifestFile ) )
290         {
291             log.info( "Writing manifest..." );
292 
293             manifestFile.getParentFile().mkdirs();
294 
295             try
296             {
297                 FileOutputStream stream = new FileOutputStream( manifestFile );
298 
299                 manifest.write( stream );
300 
301                 stream.close();
302 
303             }
304             catch ( Exception e )
305             {
306                 this.log.error( Messages.getString( "EclipsePlugin.cantwritetofile",
307                                                     new Object[] { manifestFile.getAbsolutePath() } ) );
308             }
309         }
310     }
311 
312 }