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.rad;
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.model.Resource;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.eclipse.Constants;
36  import org.apache.maven.plugin.eclipse.EclipseSourceDir;
37  import org.apache.maven.plugin.eclipse.Messages;
38  import org.apache.maven.plugin.eclipse.writers.AbstractEclipseWriter;
39  import org.apache.maven.plugin.eclipse.writers.wtp.AbstractWtpResourceWriter;
40  import org.apache.maven.plugin.ide.IdeDependency;
41  import org.apache.maven.plugin.ide.IdeUtils;
42  import org.apache.maven.plugin.ide.JeeUtils;
43  import org.apache.maven.project.MavenProject;
44  
45  /**
46   * Create or adapt the manifest files for the RAD6 runtime dependencys. attention these will not be used for the real
47   * ear these are just to get the runtime enviorment using the maven dependencies. WARNING: The manifest resources added
48   * here will not have the benefit of the dependencies of the project, since that's not provided in the setup() apis, one
49   * of the locations from which this writer is used in the RadPlugin.
50   * 
51   * @author <a href="mailto:nir@cfc.at">Richard van Nieuwenhoven </a>
52   */
53  public class RadManifestWriter
54      extends AbstractEclipseWriter
55  {
56  
57      private static final String MANIFEST_MF_FILENAME = "MANIFEST.MF";
58  
59      private static final String META_INF_DIRECTORY = "META-INF";
60  
61      private static final String DEFAULT_WEBAPP_RESOURCE_DIR =
62          "src" + File.separatorChar + "main" + File.separatorChar + "webapp";
63  
64      /**
65       * Search the project for the existing META-INF directory where the manifest should be located.
66       * 
67       * @return the apsolute path to the META-INF directory
68       * @throws MojoExecutionException
69       */
70      private String getMetaInfBaseDirectory( MavenProject project )
71          throws MojoExecutionException
72      {
73          String metaInfBaseDirectory = null;
74  
75          if ( config.getProject().getPackaging().equals( Constants.PROJECT_PACKAGING_WAR ) )
76          {
77              // Generating web content settings based on war plug-in warSourceDirectory property
78              File warSourceDirectory =
79                  new File( IdeUtils.getPluginSetting( config.getProject(), JeeUtils.ARTIFACT_MAVEN_WAR_PLUGIN,
80                                                       "warSourceDirectory", //$NON-NLS-1$
81                                                       DEFAULT_WEBAPP_RESOURCE_DIR ) );
82  
83              String webContentDir =
84                  IdeUtils.toRelativeAndFixSeparator( config.getEclipseProjectDirectory(), warSourceDirectory, false );
85  
86              metaInfBaseDirectory =
87                  config.getProject().getBasedir().getAbsolutePath() + File.separatorChar + webContentDir;
88  
89              log.debug( "Attempting to use: " + metaInfBaseDirectory + " for location of META-INF in war project." );
90  
91              File metaInfDirectoryFile = new File( metaInfBaseDirectory + File.separatorChar + META_INF_DIRECTORY );
92  
93              if ( metaInfDirectoryFile.exists() && !metaInfDirectoryFile.isDirectory() )
94              {
95                  metaInfBaseDirectory = null;
96              }
97          }
98  
99          if ( metaInfBaseDirectory == null )
100         {
101             for ( Iterator iterator = project.getResources().iterator(); iterator.hasNext(); )
102             {
103                 metaInfBaseDirectory = ( (Resource) iterator.next() ).getDirectory();
104 
105                 File metaInfDirectoryFile = new File( metaInfBaseDirectory + File.separatorChar + META_INF_DIRECTORY );
106 
107                 log.debug( "Checking for existence of META-INF directory: " + metaInfDirectoryFile );
108 
109                 if ( metaInfDirectoryFile.exists() && !metaInfDirectoryFile.isDirectory() )
110                 {
111                     metaInfBaseDirectory = null;
112                 }
113             }
114         }
115 
116         return metaInfBaseDirectory;
117     }
118 
119     /**
120      * Write the manifest files use an existing one it it exists (it will be overwritten!! in a war use webapp/META-INF
121      * else use the generated rad6 sourcefolder
122      * 
123      * @see AbstractWtpResourceWriter#write(EclipseSourceDir[], ArtifactRepository, File)
124      * @param sourceDirs all eclipse source directorys
125      * @param localRepository the local reposetory
126      * @param buildOutputDirectory build output directory (target)
127      * @throws MojoExecutionException when writing the config files was not possible
128      */
129     public void write()
130         throws MojoExecutionException
131     {
132         String metaInfBaseDirectory = getMetaInfBaseDirectory( config.getProject() );
133 
134         if ( metaInfBaseDirectory == null )
135         {
136             // TODO: if this really is an error, shouldn't we stop the build??
137             throw new MojoExecutionException( Messages.getString( "EclipseCleanMojo.nofilefound",
138                                                                   new Object[] { META_INF_DIRECTORY } ) );
139         }
140 
141         Manifest manifest = createNewManifest();
142 
143         File manifestFile =
144             new File( metaInfBaseDirectory + File.separatorChar + META_INF_DIRECTORY + File.separatorChar +
145                 MANIFEST_MF_FILENAME );
146 
147         log.info( "MANIFEST LOCATION: " + manifestFile );
148 
149         if ( shouldNewManifestFileBeWritten( manifest, manifestFile ) )
150         {
151             log.info( "Writing manifest..." );
152 
153             manifestFile.getParentFile().mkdirs();
154 
155             try
156             {
157                 FileOutputStream stream = new FileOutputStream( manifestFile );
158 
159                 manifest.write( stream );
160 
161                 stream.close();
162 
163                 verifyManifestBasedirInSourceDirs( metaInfBaseDirectory );
164 
165             }
166             catch ( Exception e )
167             {
168                 log.error( Messages.getString( "EclipsePlugin.cantwritetofile", new Object[] { metaInfBaseDirectory +
169                     File.separatorChar + MANIFEST_MF_FILENAME } ) );
170             }
171 
172         }
173     }
174 
175     // NOTE: This could change the config!
176     private void verifyManifestBasedirInSourceDirs( String metaInfBaseDirectory )
177     {
178         EclipseSourceDir[] sourceDirs = config.getSourceDirs();
179 
180         if ( sourceDirs != null )
181         {
182             boolean foundMetaInfBaseDirectory = false;
183 
184             for ( int i = 0; i < sourceDirs.length; i++ )
185             {
186                 EclipseSourceDir esd = sourceDirs[i];
187 
188                 if ( esd.getPath().equals( metaInfBaseDirectory ) )
189                 {
190                     foundMetaInfBaseDirectory = true;
191                     break;
192                 }
193             }
194 
195             if ( !foundMetaInfBaseDirectory )
196             {
197                 EclipseSourceDir dir =
198                     new EclipseSourceDir( metaInfBaseDirectory, null, true, false, null, null, false );
199 
200                 EclipseSourceDir[] newSourceDirs = new EclipseSourceDir[sourceDirs.length + 1];
201                 newSourceDirs[sourceDirs.length] = dir;
202 
203                 System.arraycopy( sourceDirs, 0, newSourceDirs, 0, sourceDirs.length );
204 
205                 config.setSourceDirs( newSourceDirs );
206             }
207         }
208     }
209 
210     /**
211      * Add one dependency to the black seperated classpath stringbuffer. Wenn the project is available in the reactor
212      * (current build) then the project is used else the jar representing the artifact.
213      * 
214      * @param classpath existing classpath to append
215      * @param dependency dependency to append as jar or as project
216      */
217     private void addDependencyToClassPath( StringBuffer classpath, IdeDependency dependency )
218     {
219         if ( !dependency.isTestDependency() && !dependency.isProvided() )
220         {
221             // blank is the separetor in manifest classpaths
222             if ( classpath.length() != 0 )
223             {
224                 classpath.append( ' ' );
225             }
226             // if the dependency is a workspace project add the project and not
227             // the jar
228             if ( !dependency.isReferencedProject() )
229             {
230                 classpath.append( dependency.getFile().getName() );
231             }
232             else
233             {
234                 classpath.append( dependency.getArtifactId() + ".jar" );
235             }
236         }
237     }
238 
239     /**
240      * Check if the two manifests are equal. Manifest.equal can not be used because of the special case the Classpath
241      * entr, witch must be comaired sorted so that a different oder in the classpath does not result in "not equal".
242      * This not not realy correct but in this case it is more important to reduce the number of version-controll files.
243      * 
244      * @param manifest the new manifest
245      * @param existingManifest to compaire the new one with
246      * @return are the manifests equal
247      */
248     private boolean areManifestsEqual( Manifest manifest, Manifest existingManifest )
249     {
250         if ( existingManifest == null )
251         {
252             return false;
253         }
254 
255         Set keys = new HashSet();
256         Attributes existingMap = existingManifest.getMainAttributes();
257         Attributes newMap = manifest.getMainAttributes();
258         keys.addAll( existingMap.keySet() );
259         keys.addAll( newMap.keySet() );
260         Iterator iterator = keys.iterator();
261         while ( iterator.hasNext() )
262         {
263             Attributes.Name key = (Attributes.Name) iterator.next();
264             String newValue = (String) newMap.get( key );
265             String existingValue = (String) existingMap.get( key );
266             // special case classpath... they are qual when there entries
267             // are equal
268             if ( Attributes.Name.CLASS_PATH.equals( key ) )
269             {
270                 newValue = orderClasspath( newValue );
271                 existingValue = orderClasspath( existingValue );
272             }
273             if ( ( newValue == null || !newValue.equals( existingValue ) ) &&
274                 ( existingValue == null || !existingValue.equals( newValue ) ) )
275             {
276                 return false;
277             }
278         }
279         return true;
280     }
281 
282     /**
283      * Convert all dependencies in a blank seperated list of jars and projects representing the classpath.
284      * 
285      * @return the blank separeted classpath string
286      */
287     private String constructManifestClasspath()
288     {
289         StringBuffer stringBuffer = new StringBuffer();
290         IdeDependency[] deps = config.getDepsOrdered();
291 
292         for ( int index = 0; index < deps.length; index++ )
293         {
294             addDependencyToClassPath( stringBuffer, deps[index] );
295         }
296 
297         return stringBuffer.toString();
298     }
299 
300     /**
301      * Create a manifest contaigning the required classpath.
302      * 
303      * @return the newly created manifest
304      */
305     private Manifest createNewManifest()
306     {
307         Manifest manifest = new Manifest();
308         manifest.getMainAttributes().put( Attributes.Name.MANIFEST_VERSION, "1.0" );
309         manifest.getMainAttributes().put( Attributes.Name.CLASS_PATH, constructManifestClasspath() );
310         return manifest;
311     }
312 
313     /**
314      * Aphabeticaly sort the classpath. Do this by splitting it up, sort the entries and gleue them together again.
315      * 
316      * @param newValue classpath to sort
317      * @return the sorted classpath
318      */
319     private String orderClasspath( String newValue )
320     {
321         String[] entries = newValue.split( " " );
322         Arrays.sort( entries );
323         StringBuffer buffer = new StringBuffer( newValue.length() );
324         for ( int index = 0; index < entries.length; index++ )
325         {
326             buffer.append( entries[index] );
327             buffer.append( ' ' );
328         }
329         return buffer.toString();
330     }
331 
332     /**
333      * Read and parse the existing manifest file.
334      * 
335      * @param manifestFile file
336      * @return the read manifest
337      * @throws IOException if the file could not be read
338      */
339     private Manifest readExistingManifest( File manifestFile )
340         throws IOException
341     {
342         if ( !manifestFile.exists() )
343         {
344             return null;
345         }
346 
347         Manifest existingManifest = new Manifest();
348         FileInputStream inputStream = new FileInputStream( manifestFile );
349         existingManifest.read( inputStream );
350         inputStream.close();
351         return existingManifest;
352     }
353 
354     /**
355      * Verify is the manifest sould be overwritten this sould take in account that the manifest should only be written
356      * if the contents of the classpath was changed not the order. The classpath sorting oder should be ignored.
357      * 
358      * @param manifest the newly created classpath
359      * @param manifestFile the file where the manifest
360      * @return if the new manifest file must be written
361      * @throws MojoExecutionException
362      */
363     private boolean shouldNewManifestFileBeWritten( Manifest manifest, File manifestFile )
364         throws MojoExecutionException
365     {
366         try
367         {
368             Manifest existingManifest = readExistingManifest( manifestFile );
369             if ( areManifestsEqual( manifest, existingManifest ) )
370             {
371                 log.info( Messages.getString( "EclipseCleanMojo.unchanged", manifestFile.getAbsolutePath() ) );
372                 return false;
373             }
374         }
375         catch ( Exception e )
376         {
377             throw new MojoExecutionException( Messages.getString( "EclipseCleanMojo.nofilefound",
378                                                                   manifestFile.getAbsolutePath() ), e );
379         }
380         return true;
381     }
382 }