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.archetype.common.util;
20  
21  import javax.xml.XMLConstants;
22  import javax.xml.parsers.DocumentBuilder;
23  import javax.xml.parsers.DocumentBuilderFactory;
24  import javax.xml.parsers.ParserConfigurationException;
25  import javax.xml.transform.OutputKeys;
26  import javax.xml.transform.Transformer;
27  import javax.xml.transform.TransformerException;
28  import javax.xml.transform.TransformerFactory;
29  import javax.xml.transform.dom.DOMSource;
30  import javax.xml.transform.stream.StreamResult;
31  import javax.xml.transform.stream.StreamSource;
32  
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.Reader;
36  import java.io.Writer;
37  
38  import org.apache.maven.archetype.exception.InvalidPackaging;
39  import org.apache.maven.archetype.old.ArchetypeTemplateProcessingException;
40  import org.w3c.dom.Document;
41  import org.w3c.dom.Element;
42  import org.w3c.dom.Node;
43  import org.w3c.dom.NodeList;
44  import org.xml.sax.InputSource;
45  import org.xml.sax.SAXException;
46  
47  /**
48   * POM helper class.
49   */
50  public final class PomUtils {
51  
52      private PomUtils() {
53          throw new IllegalStateException("no instantiable constructor");
54      }
55  
56      /**
57       * Adds module {@code artifactId} unless the module already presents in {@code fileReader}.
58       *
59       * @param artifactId artifactId of module to add
60       * @param fileReader source POM XML
61       * @param fileWriter target XML
62       * @return {@code true} if modules section in POM is empty or does not exist or {@code artifactId} does not appear
63       * a module in {@code fileReader} XML.
64       * @throws IOException if I/O error
65       * @throws InvalidPackaging if packaging is not "pom" or not exist in POM
66       * @throws ArchetypeTemplateProcessingException if "project" does not exist or "modules" element is duplicated
67       * @throws ParserConfigurationException if parser error
68       * @throws SAXException if parser error
69       * @throws TransformerException if an error writing to {@code fileWriter}
70       */
71      public static boolean addNewModule(String artifactId, Reader fileReader, Writer fileWriter)
72              throws ArchetypeTemplateProcessingException, InvalidPackaging, IOException, ParserConfigurationException,
73                      SAXException, TransformerException {
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              throw new ArchetypeTemplateProcessingException("Unable to find root element 'project'.");
90          }
91  
92          String packaging = null;
93          Node packagingNode = getChildNode(project, "packaging");
94          if (packagingNode != null) {
95              packaging = packagingNode.getTextContent();
96          }
97          if (!"pom".equals(packaging)) {
98              throw new InvalidPackaging(
99                      "Unable to add module to the current project as it is not of packaging type 'pom'");
100         }
101 
102         Node modules = getChildNode(project, "modules");
103 
104         if (!hasArtifactIdInModules(artifactId, modules)) {
105             Element module = document.createElement("module");
106             module.setTextContent(artifactId);
107             if (modules == null) {
108                 modules = document.createElement("modules");
109                 project.appendChild(modules);
110             }
111 
112             // shift the child node by next two spaces after the parent node spaces
113             modules.appendChild(document.createTextNode("  "));
114 
115             modules.appendChild(module);
116 
117             // shift the end tag </modules>
118             modules.appendChild(document.createTextNode("\n  "));
119 
120             TransformerFactory tf = TransformerFactory.newInstance();
121             tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
122             tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
123 
124             tf.setAttribute("indent-number", 2);
125             try (InputStream xsl = PomUtils.class.getResourceAsStream("/prettyprint.xsl")) {
126                 final Transformer tr = tf.newTransformer(new StreamSource(xsl));
127                 tr.setOutputProperty(OutputKeys.INDENT, "yes");
128                 tr.setOutputProperty(OutputKeys.METHOD, "xml");
129                 tr.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
130                 tr.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
131                 document.getDomConfig().setParameter("infoset", Boolean.TRUE);
132                 document.getDocumentElement().normalize();
133                 tr.transform(new DOMSource(document), new StreamResult(fileWriter));
134             }
135             return true;
136         } else {
137             return false;
138         }
139     }
140 
141     private static Node getChildNode(Element parent, String name) {
142         Node namedNode = null;
143         NodeList nodes = parent.getChildNodes();
144         for (int len = nodes.getLength(), i = 0; i < len; i++) {
145             Node node = nodes.item(i);
146             if (node.getNodeType() == Node.ELEMENT_NODE && name.equals(node.getNodeName())) {
147                 namedNode = node;
148                 break;
149             }
150         }
151         return namedNode;
152     }
153 
154     private static boolean hasArtifactIdInModules(String artifactId, Node modules) {
155         if (modules != null) {
156             Node module = modules.getFirstChild();
157             while (module != null) {
158                 if (module.getNodeType() == Node.ELEMENT_NODE && artifactId.equals(module.getTextContent())) {
159                     return true;
160                 }
161                 module = module.getNextSibling();
162             }
163         }
164         return false;
165     }
166 }