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.getDeps();
161             for (IdeDependency dep : deps) {
162                 updateApplicationXml(applicationXmlDom, modulemapsXmlDom, dep);
163             }
164 
165             removeUnusedEntries( applicationXmlDom, modulemapsXmlDom );
166 
167             writePrettyXmlFile( applicationXmlFile, applicationXmlDom );
168             writePrettyXmlFile( modulemapsXmlFile, modulemapsXmlDom );
169         }
170     }
171 
172     /**
173      * there is no existing application.xml file so create a new one.
174      * 
175      * @return the domtree representing the contents of application.xml
176      */
177     private Xpp3Dom createNewApplicationXml()
178     {
179         String jeeVersion;
180         if ( config.getJeeVersion() != null )
181         {
182             jeeVersion = JeeUtils.getJeeDescriptorFromJeeVersion( config.getJeeVersion() ).getJeeVersion();
183         }
184         else
185         {
186             jeeVersion = JeeUtils.resolveJeeVersion( config.getProject() );
187         }
188         // By default J2EE version is in the format X.X
189         // Must be fixed for JEE < 1.4. Schemas didn't exist
190         Xpp3Dom result = new Xpp3Dom( APPLICATION_XML_APPLICATION );
191         result.setAttribute( ID, "Application_ID" );
192         result.setAttribute( VERSION, jeeVersion );
193         result.setAttribute( XMLNS, "http://java.sun.com/xml/ns/j2ee" );
194         result.setAttribute( XMLNS_XSI, "http://www.w3.org/2001/XMLSchema-instance" );
195         result.setAttribute( XMLNS_SCHEMA_LOCATION,
196                              "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application_"
197                                  + jeeVersion.charAt( 0 ) + "_" + jeeVersion.charAt( 2 ) + ".xsd" );
198         result.addChild( new Xpp3Dom( APPLICATION_XML_DESCRIPTION ) );
199         Xpp3Dom name = new Xpp3Dom( APPLICATION_XML_DISPLAY_NAME );
200         name.setValue( config.getProject().getArtifactId() );
201         result.addChild( name );
202         return result;
203     }
204 
205     /**
206      * there is no existing .modulemaps file so create a new one.
207      * 
208      * @return the domtree representing the contents of the .modulemaps file
209      */
210     private Xpp3Dom createNewModulemaps()
211     {
212         Xpp3Dom result = new Xpp3Dom( MODULEMAP_EARPROJECT_MAP );
213         result.setAttribute( XMI_VERSION, "2.0" );
214         result.setAttribute( XMLNS_XMI, "http://www.omg.org/XMI" );
215         result.setAttribute( XMLNS_APPLICATION, "application.xmi" );
216         result.setAttribute( XMLNS_MODULEMAP, "modulemap.xmi" );
217         result.setAttribute( XMI_ID, "EARProjectMap_" + ( this.baseId++ ) );
218         return result;
219     }
220 
221     /**
222      * find an existing module entry in the application.xml file by looking up the id in the modulemaps file and then
223      * using that to locate the entry in the application.xml file.
224      * 
225      * @param applicationXmlDom application.xml dom tree
226      * @param mapping .modulemaps dom tree
227      * @return dom tree representing the module
228      */
229     private Xpp3Dom findModuleInApplicationXml( Xpp3Dom applicationXmlDom, Xpp3Dom mapping )
230     {
231         String id = getIdFromMapping( mapping );
232         Xpp3Dom[] children = applicationXmlDom.getChildren();
233         for (Xpp3Dom aChildren : children) {
234             String childId = aChildren.getAttribute(ID);
235             if (childId != null && childId.equals(id)) {
236                 return aChildren;
237             }
238         }
239         return null;
240     }
241 
242     /**
243      * find an artifact in the modulemaps dom tree, if it is missing create a new entry in the modulemaps dom tree.
244      * 
245      * @param dependency dependency to find
246      * @param modulemapXmlDom dom-tree of modulemaps
247      * @return dom-tree representing the artifact
248      */
249     private Xpp3Dom findOrCreateArtifact( IdeDependency dependency, Xpp3Dom modulemapXmlDom )
250     {
251         // first try to find it
252         Xpp3Dom[] children = modulemapXmlDom.getChildren();
253         for ( int index = 0; index < children.length; index++ )
254         {
255             if ( children[index].getAttribute( MODULEMAPS_PROJECT_NAME ).equals( dependency.getArtifactId() ) )
256             {
257                 if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_EJB )
258                     && children[index].getName().equals( MODULEMAPS_MAPPINGS )
259                     && children[index].getChild( APPLICATION_XML_MODULE ).getAttribute( XMI_TYPE ).equals(
260                                                                                                            MODULEMAPS_APPLICATION_EJB_MODULE ) )
261                 {
262                     return children[index];
263                 }
264                 else if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_WAR )
265                     && children[index].getName().equals( MODULEMAPS_MAPPINGS )
266                     && children[index].getChild( APPLICATION_XML_MODULE ).getAttribute( XMI_TYPE ).equals(
267                                                                                                            MODULEMAPS_APPLICATION_WEB_MODULE ) )
268                 {
269                     return children[index];
270                 }
271                 else if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_JAR )
272                     && children[index].getName().equals( MODULEMAPS_UTILITY_JARMAPPINGS ) )
273                 {
274                     return children[index];
275                 }
276                 else
277                 {
278                     modulemapXmlDom.removeChild( index );
279                     break;
280                 }
281             }
282         }
283         // ok, its missing (or it changed type). create a new one based on its
284         // type
285         long id = this.baseId++;
286         if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_EJB ) )
287         {
288             Xpp3Dom mapping = new Xpp3Dom( MODULEMAPS_MAPPINGS );
289             mapping.setAttribute( XMI_ID, "ModuleMapping_" + id );
290             mapping.setAttribute( MODULEMAPS_PROJECT_NAME, dependency.getArtifactId() );
291             Xpp3Dom module = new Xpp3Dom( APPLICATION_XML_MODULE );
292             module.setAttribute( XMI_TYPE, MODULEMAPS_APPLICATION_EJB_MODULE );
293             module.setAttribute( HREF, "META-INF/application.xml#EjbModule_" + id );
294             mapping.addChild( module );
295             modulemapXmlDom.addChild( mapping );
296             return mapping;
297         }
298         else if ( dependency.getType().equals( Constants.PROJECT_PACKAGING_WAR ) )
299         {
300             Xpp3Dom mapping = new Xpp3Dom( MODULEMAPS_MAPPINGS );
301             mapping.setAttribute( XMI_ID, "ModuleMapping_" + id );
302             mapping.setAttribute( MODULEMAPS_PROJECT_NAME, dependency.getArtifactId() );
303             Xpp3Dom module = new Xpp3Dom( APPLICATION_XML_MODULE );
304             module.setAttribute( XMI_TYPE, MODULEMAPS_APPLICATION_WEB_MODULE );
305             module.setAttribute( HREF, "META-INF/application.xml#WebModule_" + id );
306             mapping.addChild( module );
307             modulemapXmlDom.addChild( mapping );
308             return mapping;
309         }
310         else
311         {
312             Xpp3Dom utilityJARMapping = new Xpp3Dom( MODULEMAPS_UTILITY_JARMAPPINGS );
313             utilityJARMapping.setAttribute( XMI_ID, "UtilityJARMapping_" + id );
314             utilityJARMapping.setAttribute( MODULEMAPS_PROJECT_NAME, dependency.getArtifactId() );
315             utilityJARMapping.setAttribute( URI, dependency.getArtifactId() + ".jar" );
316             modulemapXmlDom.addChild( utilityJARMapping );
317             return utilityJARMapping;
318         }
319     }
320 
321     /**
322      * get the id from the href of a modulemap.
323      * 
324      * @param mapping the dom-tree of modulemaps
325      * @return module identifier
326      */
327     private String getIdFromMapping( Xpp3Dom mapping )
328     {
329         if ( mapping.getChildCount() < 1 )
330         {
331             return "";
332         }
333         String href = mapping.getChild( 0 ).getAttribute( HREF );
334         String id = href.substring( href.indexOf( '#' ) + 1 );
335         return id;
336     }
337 
338     /**
339      * mark the domtree entry as handled (all not handled ones will be deleted).
340      * 
341      * @param xpp3Dom dom element to mark handled
342      */
343     private void handled( Xpp3Dom xpp3Dom )
344     {
345         for ( int index = 0; index < this.applicationXmlDomChildren.length; index++ )
346         {
347             if ( this.applicationXmlDomChildren[index] == xpp3Dom )
348             {
349                 this.applicationXmlDomChildren[index] = null;
350             }
351         }
352         for ( int index = 0; index < this.modulemapsXmlDomChildren.length; index++ )
353         {
354             if ( this.modulemapsXmlDomChildren[index] == xpp3Dom )
355             {
356                 this.modulemapsXmlDomChildren[index] = null;
357             }
358         }
359     }
360 
361     /**
362      * read an xml file (application.xml or .modulemaps).
363      * 
364      * @param xmlFile an xmlfile
365      * @return dom-tree representing the file contents
366      */
367     private Xpp3Dom readXMLFile( File xmlFile )
368     {
369         try
370         {
371             Reader reader = new InputStreamReader( new FileInputStream( xmlFile ), "UTF-8" );
372             Xpp3Dom applicationXmlDom = Xpp3DomBuilder.build( reader );
373             return applicationXmlDom;
374         }
375         catch ( FileNotFoundException e )
376         {
377             return null;
378         }
379         catch ( Exception e )
380         {
381             log.error( Messages.getString( "EclipsePlugin.cantreadfile", xmlFile.getAbsolutePath() ) );
382             // this will trigger creating a new file
383             return null;
384         }
385     }
386 
387     /**
388      * delete all unused entries from the dom-trees.
389      * 
390      * @param applicationXmlDom dom-tree of application.xml
391      * @param modulemapsXmlDom dom-tree of modulemaps
392      */
393     private void removeUnusedEntries( Xpp3Dom applicationXmlDom, Xpp3Dom modulemapsXmlDom )
394     {
395         for (Xpp3Dom aModulemapsXmlDomChildren : this.modulemapsXmlDomChildren) {
396             if (aModulemapsXmlDomChildren != null) {
397                 Xpp3Dom[] newModulemapsXmlDomChildren = modulemapsXmlDom.getChildren();
398                 for (int newIndex = 0; newIndex < newModulemapsXmlDomChildren.length; newIndex++) {
399                     if ((newModulemapsXmlDomChildren[newIndex] != null)
400                             && (newModulemapsXmlDomChildren[newIndex] == aModulemapsXmlDomChildren)) {
401                         modulemapsXmlDom.removeChild(newIndex);
402                         break;
403                     }
404                 }
405             }
406         }
407         for (Xpp3Dom anApplicationXmlDomChildren : this.applicationXmlDomChildren) {
408             if (anApplicationXmlDomChildren != null) {
409                 Xpp3Dom[] newApplicationXmlDomChildren = applicationXmlDom.getChildren();
410                 for (int newIndex = 0; newIndex < newApplicationXmlDomChildren.length; newIndex++) {
411                     if (newApplicationXmlDomChildren[newIndex] == anApplicationXmlDomChildren) {
412                         applicationXmlDom.removeChild(newIndex);
413                         break;
414                     }
415                 }
416             }
417         }
418     }
419 
420     /**
421      * update the application.xml and the .modulemaps file for a specified dependency.all WAR an EJB dependencies will
422      * go in both files all others only in the modulemaps files. Webapplications contextroots are corrected to the
423      * contextRoot specified in the pom.
424      * 
425      * @param applicationXmlDom dom-tree of application.xml
426      * @param modulemapXmlDom dom-tree of modulemaps
427      * @param dependency the eclipse dependency to handle
428      */
429     private void updateApplicationXml( Xpp3Dom applicationXmlDom, Xpp3Dom modulemapXmlDom, IdeDependency dependency )
430     {
431         boolean isEar = Constants.PROJECT_PACKAGING_EJB.equals( dependency.getType() );
432         boolean isWar = Constants.PROJECT_PACKAGING_WAR.equals( dependency.getType() );
433 
434         if ( dependency.isReferencedProject() || isEar || isWar )
435         {
436             Xpp3Dom mapping = findOrCreateArtifact( dependency, modulemapXmlDom );
437             handled( mapping );
438             if ( isEar )
439             {
440                 Xpp3Dom module = findModuleInApplicationXml( applicationXmlDom, mapping );
441                 if ( module == null )
442                 {
443                     module = new Xpp3Dom( APPLICATION_XML_MODULE );
444                     module.setAttribute( ID, getIdFromMapping( mapping ) );
445                     Xpp3Dom ejb = new Xpp3Dom( Constants.PROJECT_PACKAGING_EJB );
446                     ejb.setValue( dependency.getArtifactId() + ".jar" );
447                     module.addChild( ejb );
448                     applicationXmlDom.addChild( module );
449                 }
450                 else
451                 {
452                     handled( module );
453                     module.getChild( Constants.PROJECT_PACKAGING_EJB ).setValue( dependency.getArtifactId() + ".jar" );
454                 }
455             }
456             else if ( isWar )
457             {
458                 String contextRootInPom = getContextRootFor( dependency.getArtifactId() );
459                 Xpp3Dom module = findModuleInApplicationXml( applicationXmlDom, mapping );
460                 if ( module == null )
461                 {
462                     module = new Xpp3Dom( APPLICATION_XML_MODULE );
463                     module.setAttribute( ID, getIdFromMapping( mapping ) );
464                     Xpp3Dom web = new Xpp3Dom( APPLICATION_XML_WEB );
465                     Xpp3Dom webUri = new Xpp3Dom( APPLICATION_XML_WEB_URI );
466                     webUri.setValue( dependency.getArtifactId() + ".war" );
467                     Xpp3Dom contextRoot = new Xpp3Dom( APPLICATION_XML_CONTEXT_ROOT );
468                     contextRoot.setValue( contextRootInPom );
469                     web.addChild( webUri );
470                     web.addChild( contextRoot );
471                     module.addChild( web );
472                     applicationXmlDom.addChild( module );
473                 }
474                 else
475                 {
476                     handled( module );
477                     module.getChild( APPLICATION_XML_WEB ).getChild( APPLICATION_XML_WEB_URI ).setValue(
478                                                                                                          dependency.getArtifactId()
479                                                                                                              + ".war" );
480                     module.getChild( APPLICATION_XML_WEB ).getChild( APPLICATION_XML_CONTEXT_ROOT ).setValue(
481                                                                                                               contextRootInPom );
482                 }
483             }
484         }
485     }
486 
487     /**
488      * Find the contextRoot specified in the pom and convert it into contextroot for the application.xml.
489      * 
490      * @param artifactId the artifactid to search
491      * @return string with the context root
492      */
493     private String getContextRootFor( String artifactId )
494     {
495         for (Xpp3Dom webModulesFromPom : webModulesFromPoms) {
496             if (webModulesFromPom.getChild("artifactId").getValue().equals(artifactId))
497                 return new File(webModulesFromPom.getChild("contextRoot").getValue()).getName();
498         }
499         return artifactId;
500     }
501 
502     /**
503      * write back a domtree to a xmlfile and use the pretty print for it so that it is human readable.
504      * 
505      * @param xmlFile file to write to
506      * @param xmlDomTree dom-tree to write
507      * @throws MojoExecutionException if the file could not be written
508      */
509     private void writePrettyXmlFile( File xmlFile, Xpp3Dom xmlDomTree )
510         throws MojoExecutionException
511     {
512         Xpp3Dom original = readXMLFile( xmlFile );
513         if ( original != null && original.equals( xmlDomTree ) )
514         {
515             log.info( Messages.getString( "EclipsePlugin.unchangedmanifest", xmlFile.getAbsolutePath() ) );
516             return;
517         }
518         Writer w;
519         xmlFile.getParentFile().mkdirs();
520         try
521         {
522             w = new OutputStreamWriter( new FileOutputStream( xmlFile ), "UTF-8" );
523         }
524         catch ( IOException ex )
525         {
526             throw new MojoExecutionException( Messages.getString( "EclipsePlugin.erroropeningfile" ), ex ); //$NON-NLS-1$
527         }
528         XMLWriter writer = new PrettyPrintXMLWriter( w, "UTF-8", null );
529         Xpp3DomWriter.write( writer, xmlDomTree );
530         IOUtil.close( w );
531     }
532 }