View Javadoc
1   package org.apache.maven.archetype.common.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.archetype.exception.InvalidPackaging;
23  import org.apache.maven.archetype.old.ArchetypeTemplateProcessingException;
24  import org.w3c.dom.Document;
25  import org.w3c.dom.Element;
26  import org.w3c.dom.Node;
27  import org.w3c.dom.NodeList;
28  import org.xml.sax.InputSource;
29  import org.xml.sax.SAXException;
30  
31  import javax.xml.XMLConstants;
32  import javax.xml.parsers.DocumentBuilder;
33  import javax.xml.parsers.DocumentBuilderFactory;
34  import javax.xml.parsers.ParserConfigurationException;
35  import javax.xml.transform.OutputKeys;
36  import javax.xml.transform.Transformer;
37  import javax.xml.transform.TransformerException;
38  import javax.xml.transform.TransformerFactory;
39  import javax.xml.transform.dom.DOMSource;
40  import javax.xml.transform.stream.StreamResult;
41  import java.io.IOException;
42  import java.io.Reader;
43  import java.io.Writer;
44  
45  /**
46   * POM helper class.
47   */
48  public final class PomUtils
49  {
50      private PomUtils()
51      {
52          throw new IllegalStateException( "no instantiable constructor" );
53      }
54  
55      /**
56       * Adds module {@code artifactId} unless the module already presents in {@code fileReader}.
57       *
58       * @param artifactId artifactId of module to add
59       * @param fileReader source POM XML
60       * @param fileWriter target XML
61       * @return {@code true} if modules section in POM is empty or does not exist or {@code artifactId} does not appear
62       * a module in {@code fileReader} XML.
63       * @throws IOException if I/O error
64       * @throws InvalidPackaging if packaging is not "pom" or not exist in POM
65       * @throws ArchetypeTemplateProcessingException if "project" does not exist or "modules" element is duplicated
66       * @throws ParserConfigurationException if parser error
67       * @throws SAXException if parser error
68       * @throws TransformerException if an error writing to {@code fileWriter}
69       */
70      public static boolean addNewModule( String artifactId, Reader fileReader, Writer fileWriter )
71              throws ArchetypeTemplateProcessingException, InvalidPackaging, IOException,
72              ParserConfigurationException, SAXException, TransformerException
73      {
74          DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
75          dbf.setFeature( "http://apache.org/xml/features/disallow-doctype-decl", true );
76          dbf.setFeature( "http://xml.org/sax/features/external-general-entities", false );
77          dbf.setFeature( "http://xml.org/sax/features/external-parameter-entities", false );
78          dbf.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false );
79          dbf.setXIncludeAware( false );
80          dbf.setExpandEntityReferences( false );
81  
82          DocumentBuilder db = dbf.newDocumentBuilder();
83          InputSource inputSource = new InputSource();
84          inputSource.setCharacterStream( fileReader );
85          Document document = db.parse( inputSource );
86  
87          Element project = document.getDocumentElement();
88          if ( !"project".equals( project.getNodeName() ) )
89          {
90              throw new ArchetypeTemplateProcessingException( "Unable to find root element 'project'." );
91          }
92  
93          String packaging = null;
94          NodeList packagingElement = project.getElementsByTagName( "packaging" );
95          if ( packagingElement != null && packagingElement.getLength() == 1 )
96          {
97              packaging = packagingElement.item( 0 ).getTextContent();
98          }
99          if ( !"pom".equals( packaging ) )
100         {
101             throw new InvalidPackaging(
102                     "Unable to add module to the current project as it is not of packaging type 'pom'" );
103         }
104 
105         Node modules = getModulesNode( project );
106 
107         if ( !hasArtifactIdInModules( artifactId, modules ) )
108         {
109             Element module = document.createElement( "module" );
110             module.setTextContent( artifactId );
111             if ( modules == null )
112             {
113                 modules = document.createElement( "modules" );
114                 project.appendChild( modules );
115             }
116 
117             // shift the child node by next two spaces after the parent node spaces
118             modules.appendChild( document.createTextNode( "  " ) );
119 
120             modules.appendChild( module );
121 
122             // shift the end tag </modules>
123             modules.appendChild( document.createTextNode( "\n  " ) );
124 
125             TransformerFactory tf = TransformerFactory.newInstance();
126             tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_DTD, "" );
127             tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "" );
128 
129             tf.setAttribute( "indent-number", 2 );
130             Transformer tr = tf.newTransformer();
131             tr.setOutputProperty( OutputKeys.INDENT, "yes" );
132             tr.setOutputProperty( OutputKeys.METHOD, "xml" );
133             tr.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
134             tr.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "2" );
135             document.getDomConfig().setParameter( "infoset", Boolean.TRUE );
136             document.getDocumentElement().normalize();
137             tr.transform( new DOMSource( document ), new StreamResult( fileWriter ) );
138             return true;
139         }
140         else
141         {
142             return false;
143         }
144     }
145 
146     private static Node getModulesNode( Element project )
147     {
148         Node modules = null;
149         NodeList nodes = project.getChildNodes();
150         for ( int len = nodes.getLength(), i = 0; i < len; i++ )
151         {
152             Node node = nodes.item( i );
153             if ( node.getNodeType() == Node.ELEMENT_NODE && "modules".equals( node.getNodeName() ) )
154             {
155                 modules = node;
156                 break;
157             }
158         }
159         return modules;
160     }
161 
162     private static boolean hasArtifactIdInModules( String artifactId, Node modules )
163     {
164         if ( modules != null )
165         {
166             Node module = modules.getFirstChild();
167             while ( module != null )
168             {
169                 if ( module.getNodeType() == Node.ELEMENT_NODE && artifactId.equals( module.getTextContent() ) )
170                 {
171                     return true;
172                 }
173                 module = module.getNextSibling();
174             }
175         }
176         return false;
177     }
178 }