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.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.io.OutputStreamWriter;
28  import java.io.Reader;
29  import java.io.Writer;
30  
31  import org.apache.maven.artifact.repository.ArtifactRepository;
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.eclipse.Constants;
34  import org.apache.maven.plugin.eclipse.EclipseSourceDir;
35  import org.apache.maven.plugin.eclipse.Messages;
36  import org.apache.maven.plugin.eclipse.writers.AbstractEclipseWriter;
37  import org.apache.maven.plugin.eclipse.writers.wtp.AbstractWtpResourceWriter;
38  import org.apache.maven.plugin.ide.IdeDependency;
39  import org.apache.maven.plugin.ide.IdeUtils;
40  import org.apache.maven.plugin.ide.JeeUtils;
41  import org.codehaus.plexus.util.IOUtil;
42  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
43  import org.codehaus.plexus.util.xml.XMLWriter;
44  import org.codehaus.plexus.util.xml.Xpp3Dom;
45  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
46  import org.codehaus.plexus.util.xml.Xpp3DomWriter;
47  
48  /**
49   * This writer creates the application.xml and the .modulemaps files for RAD6 in the META-INF directory in the project
50   * root. this is where RAD6 requires the files to be. These will be independent of the real application.xml witch will
51   * be generated the stad. maven way.
52   * 
53   * @author <a href="mailto:nir@cfc.at">Richard van Nieuwenhoven </a>
54   */
55  public class RadApplicationXMLWriter
56      extends AbstractEclipseWriter
57  {
58  
59      private static final String APPLICATION_XML_APPLICATION = "application";
60  
61      private static final String APPLICATION_XML_CONTEXT_ROOT = "context-root";
62  
63      private static final String APPLICATION_XML_DESCRIPTION = "description";
64  
65      private static final String APPLICATION_XML_DISPLAY_NAME = "display-name";
66  
67      private static final String APPLICATION_XML_FILENAME = "application.xml";
68  
69      private static final String APPLICATION_XML_MODULE = "module";
70  
71      private static final String APPLICATION_XML_WEB = "web";
72  
73      private static final String APPLICATION_XML_WEB_URI = "web-uri";
74  
75      private static final String HREF = "href";
76  
77      private static final String ID = "id";
78  
79      private static final String MODULEMAP_EARPROJECT_MAP = "modulemap:EARProjectMap";
80  
81      private static final String MODULEMAPS_APPLICATION_EJB_MODULE = "application:EjbModule";
82  
83      private static final String MODULEMAPS_APPLICATION_WEB_MODULE = "application:WebModule";
84  
85      private static final String MODULEMAPS_FILENAME = ".modulemaps";
86  
87      private static final String MODULEMAPS_MAPPINGS = "mappings";
88  
89      private static final String MODULEMAPS_PROJECT_NAME = "projectName";
90  
91      private static final String MODULEMAPS_UTILITY_JARMAPPINGS = "utilityJARMappings";
92  
93      private static final String URI = "uri";
94  
95      private static final String VERSION = "version";
96  
97      private static final String XMI_ID = "xmi:id";
98  
99      private static final String XMI_TYPE = "xmi:type";
100 
101     private static final String XMI_VERSION = "xmi:version";
102 
103     private static final String XMLNS = "xmlns";
104 
105     private static final String XMLNS_APPLICATION = "xmlns:application";
106 
107     private static final String XMLNS_MODULEMAP = "xmlns:modulemap";
108 
109     private static final String XMLNS_SCHEMA_LOCATION = "xsi:schemaLocation";
110 
111     private static final String XMLNS_XMI = "xmlns:xmi";
112 
113     private static final String XMLNS_XSI = "xmlns:xsi";
114 
115     private Xpp3Dom[] applicationXmlDomChildren;
116 
117     private long baseId = System.currentTimeMillis();
118 
119     private Xpp3Dom[] modulemapsXmlDomChildren;
120 
121     private Xpp3Dom[] webModulesFromPoms;
122 
123     /**
124      * write the application.xml and the .modulemaps file to the META-INF directory.
125      * 
126      * @see AbstractWtpResourceWriter#write(EclipseSourceDir[], ArtifactRepository, File)
127      * @param sourceDirs all eclipse source directorys
128      * @param localRepository the local reposetory
129      * @param buildOutputDirectory build output directory (target)
130      * @throws MojoExecutionException when writing the config files was not possible
131      */
132     public void write()
133         throws MojoExecutionException
134     {
135         String packaging = config.getPackaging();
136         if ( Constants.PROJECT_PACKAGING_EAR.equalsIgnoreCase( packaging ) )
137         {
138             File applicationXmlFile =
139                 new File( config.getEclipseProjectDirectory(), "META-INF" + File.separator + APPLICATION_XML_FILENAME );
140             Xpp3Dom applicationXmlDom = readXMLFile( applicationXmlFile );
141             if ( applicationXmlDom == null )
142             {
143                 applicationXmlDom = createNewApplicationXml();
144             }
145             this.applicationXmlDomChildren = applicationXmlDom.getChildren( APPLICATION_XML_MODULE );
146 
147             File modulemapsXmlFile =
148                 new File( config.getEclipseProjectDirectory(), "META-INF" + File.separator + MODULEMAPS_FILENAME );
149             Xpp3Dom modulemapsXmlDom = readXMLFile( modulemapsXmlFile );
150             if ( modulemapsXmlDom == null )
151             {
152                 modulemapsXmlDom = createNewModulemaps();
153             }
154             this.modulemapsXmlDomChildren = modulemapsXmlDom.getChildren();
155 
156             this.webModulesFromPoms =
157                 IdeUtils.getPluginConfigurationDom( config.getProject(), JeeUtils.ARTIFACT_MAVEN_EAR_PLUGIN,
158                                                     new String[] { "modules", "webModule" } );
159 
160             IdeDependency[] deps = config.getDepsOrdered();
161             for ( int index = 0; index < deps.length; index++ )
162             {
163                 updateApplicationXml( applicationXmlDom, modulemapsXmlDom, deps[index] );
164             }
165 
166             removeUnusedEntries( applicationXmlDom, modulemapsXmlDom );
167 
168             writePrettyXmlFile( applicationXmlFile, applicationXmlDom );
169             writePrettyXmlFile( modulemapsXmlFile, modulemapsXmlDom );
170         }
171     }
172 
173     /**
174      * there is no existing application.xml file so create a new one.
175      * 
176      * @return the domtree representing the contents of application.xml
177      */
178     private Xpp3Dom createNewApplicationXml()
179     {
180         String j2eeVersion = JeeUtils.resolveJeeVersion( config.getProject() );
181         // By default J2EE version is in the format X.X
182         // Must be fixed for JEE < 1.4. Schemas didn't exist
183         Xpp3Dom result = new Xpp3Dom( APPLICATION_XML_APPLICATION );
184         result.setAttribute( ID, "Application_ID" );
185         result.setAttribute( VERSION, j2eeVersion );
186         result.setAttribute( XMLNS, "http://java.sun.com/xml/ns/j2ee" );
187         result.setAttribute( XMLNS_XSI, "http://www.w3.org/2001/XMLSchema-instance" );
188         result.setAttribute( XMLNS_SCHEMA_LOCATION,
189                              "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application_"
190                                  + j2eeVersion.charAt( 0 ) + "_" + j2eeVersion.charAt( 2 ) + ".xsd" );
191         result.addChild( new Xpp3Dom( APPLICATION_XML_DESCRIPTION ) );
192         Xpp3Dom name = new Xpp3Dom( APPLICATION_XML_DISPLAY_NAME );
193         name.setValue( config.getProject().getArtifactId() );
194         result.addChild( name );
195         return result;
196     }
197 
198     /**
199      * there is no existing .modulemaps file so create a new one.
200      * 
201      * @return the domtree representing the contents of the .modulemaps file
202      */
203     private Xpp3Dom createNewModulemaps()
204     {
205         Xpp3Dom result = new Xpp3Dom( MODULEMAP_EARPROJECT_MAP );
206         result.setAttribute( XMI_VERSION, "2.0" );
207         result.setAttribute( XMLNS_XMI, "http://www.omg.org/XMI" );
208         result.setAttribute( XMLNS_APPLICATION, "application.xmi" );
209         result.setAttribute( XMLNS_MODULEMAP, "modulemap.xmi" );
210         result.setAttribute( XMI_ID, "EARProjectMap_" + ( this.baseId++ ) );
211         return result;
212     }
213 
214     /**
215      * find an existing module entry in the application.xml file by looking up the id in the modulemaps file and then
216      * using that to locate the entry in the application.xml file.
217      * 
218      * @param applicationXmlDom application.xml dom tree
219      * @param mapping .modulemaps dom tree
220      * @return dom tree representing the module
221      */
222     private Xpp3Dom findModuleInApplicationXml( Xpp3Dom applicationXmlDom, Xpp3Dom mapping )
223     {
224         String id = getIdFromMapping( mapping );
225         Xpp3Dom[] children = applicationXmlDom.getChildren();
226         for ( int index = 0; index < children.length; index++ )
227         {
228             String childId = children[index].getAttribute( ID );
229             if ( childId != null && childId.equals( id ) )
230             {
231                 return children[index];
232             }
233         }
234         return null;
235     }
236 
237     /**
238      * find an artifact in the modulemaps dom tree, if it is missing create a new entry in the modulemaps dom tree.
239      * 
240      * @param dependency dependency to find
241      * @param modulemapXmlDom dom-tree of modulemaps
242      * @return dom-tree representing the artifact
243      */
244     private Xpp3Dom findOrCreateArtifact( IdeDependency dependency, Xpp3Dom modulemapXmlDom )
245     {
246         // first try to find it
247         Xpp3Dom[] children = modulemapXmlDom.getChildren();
248         for ( int index = 0; index < children.length; index++ )
249         {
250             if ( children[index].getAttribute( MODULEMAPS_PROJECT_NAME ).equals( dependency.getArtifactId() ) )
251             {
252                 if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_EJB )
253                     && children[index].getName().equals( MODULEMAPS_MAPPINGS )
254                     && children[index].getChild( APPLICATION_XML_MODULE ).getAttribute( XMI_TYPE ).equals(
255                                                                                                            MODULEMAPS_APPLICATION_EJB_MODULE ) )
256                 {
257                     return children[index];
258                 }
259                 else if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_WAR )
260                     && children[index].getName().equals( MODULEMAPS_MAPPINGS )
261                     && children[index].getChild( APPLICATION_XML_MODULE ).getAttribute( XMI_TYPE ).equals(
262                                                                                                            MODULEMAPS_APPLICATION_WEB_MODULE ) )
263                 {
264                     return children[index];
265                 }
266                 else if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_JAR )
267                     && children[index].getName().equals( MODULEMAPS_UTILITY_JARMAPPINGS ) )
268                 {
269                     return children[index];
270                 }
271                 else
272                 {
273                     modulemapXmlDom.removeChild( index );
274                     break;
275                 }
276             }
277         }
278         // ok, its missing (or it changed type). create a new one based on its
279         // type
280         long id = this.baseId++;
281         if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_EJB ) )
282         {
283             Xpp3Dom mapping = new Xpp3Dom( MODULEMAPS_MAPPINGS );
284             mapping.setAttribute( XMI_ID, "ModuleMapping_" + id );
285             mapping.setAttribute( MODULEMAPS_PROJECT_NAME, dependency.getArtifactId() );
286             Xpp3Dom module = new Xpp3Dom( APPLICATION_XML_MODULE );
287             module.setAttribute( XMI_TYPE, MODULEMAPS_APPLICATION_EJB_MODULE );
288             module.setAttribute( HREF, "META-INF/application.xml#EjbModule_" + id );
289             mapping.addChild( module );
290             modulemapXmlDom.addChild( mapping );
291             return mapping;
292         }
293         else if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_WAR ) )
294         {
295             Xpp3Dom mapping = new Xpp3Dom( MODULEMAPS_MAPPINGS );
296             mapping.setAttribute( XMI_ID, "ModuleMapping_" + id );
297             mapping.setAttribute( MODULEMAPS_PROJECT_NAME, dependency.getArtifactId() );
298             Xpp3Dom module = new Xpp3Dom( APPLICATION_XML_MODULE );
299             module.setAttribute( XMI_TYPE, MODULEMAPS_APPLICATION_WEB_MODULE );
300             module.setAttribute( HREF, "META-INF/application.xml#WebModule_" + id );
301             mapping.addChild( module );
302             modulemapXmlDom.addChild( mapping );
303             return mapping;
304         }
305         else
306         {
307             Xpp3Dom utilityJARMapping = new Xpp3Dom( MODULEMAPS_UTILITY_JARMAPPINGS );
308             utilityJARMapping.setAttribute( XMI_ID, "UtilityJARMapping_" + id );
309             utilityJARMapping.setAttribute( MODULEMAPS_PROJECT_NAME, dependency.getArtifactId() );
310             utilityJARMapping.setAttribute( URI, dependency.getArtifactId() + ".jar" );
311             modulemapXmlDom.addChild( utilityJARMapping );
312             return utilityJARMapping;
313         }
314     }
315 
316     /**
317      * get the id from the href of a modulemap.
318      * 
319      * @param mapping the dom-tree of modulemaps
320      * @return module identifier
321      */
322     private String getIdFromMapping( Xpp3Dom mapping )
323     {
324         if ( mapping.getChildCount() < 1 )
325         {
326             return "";
327         }
328         String href = mapping.getChild( 0 ).getAttribute( HREF );
329         String id = href.substring( href.indexOf( '#' ) + 1 );
330         return id;
331     }
332 
333     /**
334      * mark the domtree entry as handled (all not handled ones will be deleted).
335      * 
336      * @param xpp3Dom dom element to mark handled
337      */
338     private void handled( Xpp3Dom xpp3Dom )
339     {
340         for ( int index = 0; index < this.applicationXmlDomChildren.length; index++ )
341         {
342             if ( this.applicationXmlDomChildren[index] == xpp3Dom )
343             {
344                 this.applicationXmlDomChildren[index] = null;
345             }
346         }
347         for ( int index = 0; index < this.modulemapsXmlDomChildren.length; index++ )
348         {
349             if ( this.modulemapsXmlDomChildren[index] == xpp3Dom )
350             {
351                 this.modulemapsXmlDomChildren[index] = null;
352             }
353         }
354     }
355 
356     /**
357      * read an xml file (application.xml or .modulemaps).
358      * 
359      * @param xmlFile an xmlfile
360      * @return dom-tree representing the file contents
361      */
362     private Xpp3Dom readXMLFile( File xmlFile )
363     {
364         try
365         {
366             Reader reader = new InputStreamReader( new FileInputStream( xmlFile ), "UTF-8" );
367             Xpp3Dom applicationXmlDom = Xpp3DomBuilder.build( reader );
368             return applicationXmlDom;
369         }
370         catch ( FileNotFoundException e )
371         {
372             return null;
373         }
374         catch ( Exception e )
375         {
376             log.error( Messages.getString( "EclipsePlugin.cantreadfile", xmlFile.getAbsolutePath() ) );
377             // this will trigger creating a new file
378             return null;
379         }
380     }
381 
382     /**
383      * delete all unused entries from the dom-trees.
384      * 
385      * @param applicationXmlDom dom-tree of application.xml
386      * @param modulemapsXmlDom dom-tree of modulemaps
387      */
388     private void removeUnusedEntries( Xpp3Dom applicationXmlDom, Xpp3Dom modulemapsXmlDom )
389     {
390         for ( int index = 0; index < this.modulemapsXmlDomChildren.length; index++ )
391         {
392             if ( this.modulemapsXmlDomChildren[index] != null )
393             {
394                 Xpp3Dom[] newModulemapsXmlDomChildren = modulemapsXmlDom.getChildren();
395                 for ( int newIndex = 0; newIndex < newModulemapsXmlDomChildren.length; newIndex++ )
396                 {
397                     if ( ( newModulemapsXmlDomChildren[newIndex] != null )
398                         && ( newModulemapsXmlDomChildren[newIndex] == this.modulemapsXmlDomChildren[index] ) )
399                     {
400                         modulemapsXmlDom.removeChild( newIndex );
401                         break;
402                     }
403                 }
404             }
405         }
406         for ( int index = 0; index < this.applicationXmlDomChildren.length; index++ )
407         {
408             if ( this.applicationXmlDomChildren[index] != null )
409             {
410                 Xpp3Dom[] newApplicationXmlDomChildren = applicationXmlDom.getChildren();
411                 for ( int newIndex = 0; newIndex < newApplicationXmlDomChildren.length; newIndex++ )
412                 {
413                     if ( newApplicationXmlDomChildren[newIndex] == this.applicationXmlDomChildren[index] )
414                     {
415                         applicationXmlDom.removeChild( newIndex );
416                         break;
417                     }
418                 }
419             }
420         }
421     }
422 
423     /**
424      * update the application.xml and the .modulemaps file for a specified dependency.all WAR an EJB dependencies will
425      * go in both files all others only in the modulemaps files. Webapplications contextroots are corrected to the
426      * contextRoot specified in the pom.
427      * 
428      * @param applicationXmlDom dom-tree of application.xml
429      * @param modulemapXmlDom dom-tree of modulemaps
430      * @param dependency the eclipse dependency to handle
431      */
432     private void updateApplicationXml( Xpp3Dom applicationXmlDom, Xpp3Dom modulemapXmlDom, IdeDependency dependency )
433     {
434         boolean isEar = Constants.PROJECT_PACKAGING_EJB.equals( dependency.getType() );
435         boolean isWar = Constants.PROJECT_PACKAGING_WAR.equals( dependency.getType() );
436 
437         if ( dependency.isReferencedProject() || isEar || isWar )
438         {
439             Xpp3Dom mapping = findOrCreateArtifact( dependency, modulemapXmlDom );
440             handled( mapping );
441             if ( isEar )
442             {
443                 Xpp3Dom module = findModuleInApplicationXml( applicationXmlDom, mapping );
444                 if ( module == null )
445                 {
446                     module = new Xpp3Dom( APPLICATION_XML_MODULE );
447                     module.setAttribute( ID, getIdFromMapping( mapping ) );
448                     Xpp3Dom ejb = new Xpp3Dom( Constants.PROJECT_PACKAGING_EJB );
449                     ejb.setValue( dependency.getArtifactId() + ".jar" );
450                     module.addChild( ejb );
451                     applicationXmlDom.addChild( module );
452                 }
453                 else
454                 {
455                     handled( module );
456                     module.getChild( Constants.PROJECT_PACKAGING_EJB ).setValue( dependency.getArtifactId() + ".jar" );
457                 }
458             }
459             else if ( isWar )
460             {
461                 String contextRootInPom = getContextRootFor( dependency.getArtifactId() );
462                 Xpp3Dom module = findModuleInApplicationXml( applicationXmlDom, mapping );
463                 if ( module == null )
464                 {
465                     module = new Xpp3Dom( APPLICATION_XML_MODULE );
466                     module.setAttribute( ID, getIdFromMapping( mapping ) );
467                     Xpp3Dom web = new Xpp3Dom( APPLICATION_XML_WEB );
468                     Xpp3Dom webUri = new Xpp3Dom( APPLICATION_XML_WEB_URI );
469                     webUri.setValue( dependency.getArtifactId() + ".war" );
470                     Xpp3Dom contextRoot = new Xpp3Dom( APPLICATION_XML_CONTEXT_ROOT );
471                     contextRoot.setValue( contextRootInPom );
472                     web.addChild( webUri );
473                     web.addChild( contextRoot );
474                     module.addChild( web );
475                     applicationXmlDom.addChild( module );
476                 }
477                 else
478                 {
479                     handled( module );
480                     module.getChild( APPLICATION_XML_WEB ).getChild( APPLICATION_XML_WEB_URI ).setValue(
481                                                                                                          dependency.getArtifactId()
482                                                                                                              + ".war" );
483                     module.getChild( APPLICATION_XML_WEB ).getChild( APPLICATION_XML_CONTEXT_ROOT ).setValue(
484                                                                                                               contextRootInPom );
485                 }
486             }
487         }
488     }
489 
490     /**
491      * Find the contextRoot specified in the pom and convert it into contextroot for the application.xml.
492      * 
493      * @param artifactId the artifactid to search
494      * @return string with the context root
495      */
496     private String getContextRootFor( String artifactId )
497     {
498         for ( int index = 0; index < webModulesFromPoms.length; index++ )
499         {
500             if ( webModulesFromPoms[index].getChild( "artifactId" ).getValue().equals( artifactId ) )
501                 return new File( webModulesFromPoms[index].getChild( "contextRoot" ).getValue() ).getName();
502         }
503         return artifactId;
504     }
505 
506     /**
507      * write back a domtree to a xmlfile and use the pretty print for it so that it is human readable.
508      * 
509      * @param xmlFile file to write to
510      * @param xmlDomTree dom-tree to write
511      * @throws MojoExecutionException if the file could not be written
512      */
513     private void writePrettyXmlFile( File xmlFile, Xpp3Dom xmlDomTree )
514         throws MojoExecutionException
515     {
516         Xpp3Dom original = readXMLFile( xmlFile );
517         if ( original != null && original.equals( xmlDomTree ) )
518         {
519             log.info( Messages.getString( "EclipsePlugin.unchangedmanifest", xmlFile.getAbsolutePath() ) );
520             return;
521         }
522         Writer w = null;
523         xmlFile.getParentFile().mkdirs();
524         try
525         {
526             w = new OutputStreamWriter( new FileOutputStream( xmlFile ), "UTF-8" );
527         }
528         catch ( IOException ex )
529         {
530             throw new MojoExecutionException( Messages.getString( "EclipsePlugin.erroropeningfile" ), ex ); //$NON-NLS-1$
531         }
532         XMLWriter writer = new PrettyPrintXMLWriter( w, "UTF-8", null );
533         Xpp3DomWriter.write( writer, xmlDomTree );
534         IOUtil.close( w );
535     }
536 }