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