View Javadoc
1   package org.apache.maven.shared.release.transform.jdom2;
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 java.io.File;
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.io.StringWriter;
26  import java.io.Writer;
27  import java.util.Iterator;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  import org.apache.maven.model.Model;
32  import org.apache.maven.project.MavenProject;
33  import org.apache.maven.shared.release.ReleaseExecutionException;
34  import org.apache.maven.shared.release.config.ReleaseDescriptor;
35  import org.apache.maven.shared.release.transform.ModelETL;
36  import org.apache.maven.shared.release.util.ReleaseUtil;
37  import org.codehaus.plexus.util.WriterFactory;
38  import org.jdom2.CDATA;
39  import org.jdom2.Comment;
40  import org.jdom2.Document;
41  import org.jdom2.Element;
42  import org.jdom2.JDOMException;
43  import org.jdom2.Namespace;
44  import org.jdom2.filter.ContentFilter;
45  import org.jdom2.filter.ElementFilter;
46  import org.jdom2.input.SAXBuilder;
47  import org.jdom2.output.Format;
48  import org.jdom2.output.XMLOutputter;
49  
50  /**
51   * JDOM2 implementation for extracting, transform, loading the Model (pom.xml)
52   *
53   * @author Robert Scholte
54   * @since 3.0
55   */
56  public class JDomModelETL implements ModelETL
57  {
58      private ReleaseDescriptor releaseDescriptor;
59  
60      private MavenProject project;
61  
62      private Document document;
63  
64      private String intro = null;
65      private String outtro = null;
66  
67      private String ls = ReleaseUtil.LS;
68  
69      /**
70       * <p>Setter for the field <code>ls</code>.</p>
71       *
72       * @param ls a {@link java.lang.String} object
73       */
74      public void setLs( String ls )
75      {
76          this.ls = ls;
77      }
78  
79      /**
80       * <p>Setter for the field <code>releaseDescriptor</code>.</p>
81       *
82       * @param releaseDescriptor a {@link org.apache.maven.shared.release.config.ReleaseDescriptor} object
83       */
84      public void setReleaseDescriptor( ReleaseDescriptor releaseDescriptor )
85      {
86          this.releaseDescriptor = releaseDescriptor;
87      }
88  
89      /**
90       * <p>Setter for the field <code>project</code>.</p>
91       *
92       * @param project a {@link org.apache.maven.project.MavenProject} object
93       */
94      public void setProject( MavenProject project )
95      {
96          this.project = project;
97      }
98  
99      @Override
100     public void extract( File pomFile ) throws ReleaseExecutionException
101     {
102         try
103         {
104             String content = ReleaseUtil.readXmlFile( pomFile, ls );
105             // we need to eliminate any extra whitespace inside elements, as JDOM2 will nuke it
106             content = content.replaceAll( "<([^!][^>]*?)\\s{2,}([^>]*?)>", "<$1 $2>" );
107             content = content.replaceAll( "(\\s{2,})/>", "$1 />" );
108 
109             SAXBuilder builder = new SAXBuilder();
110             document = builder.build( new StringReader( content ) );
111 
112             // Normalize line endings to platform's style (XML processors like JDOM2 normalize line endings to "\n" as
113             // per section 2.11 of the XML spec)
114             normaliseLineEndings( document );
115 
116             // rewrite DOM as a string to find differences, since text outside the root element is not tracked
117             StringWriter w = new StringWriter();
118             Format format = Format.getRawFormat();
119             format.setLineSeparator( ls );
120             XMLOutputter out = new XMLOutputter( format );
121             out.output( document.getRootElement(), w );
122 
123             int index = content.indexOf( w.toString() );
124             if ( index >= 0 )
125             {
126                 intro = content.substring( 0, index );
127                 outtro = content.substring( index + w.toString().length() );
128             }
129             else
130             {
131                 /*
132                  * NOTE: Due to whitespace, attribute reordering or entity expansion the above indexOf test can easily
133                  * fail. So let's try harder. Maybe some day, when JDOM2 offers a StaxBuilder and this builder employes
134                  * XMLInputFactory2.P_REPORT_PROLOG_WHITESPACE, this whole mess can be avoided.
135                  */
136                 // CHECKSTYLE_OFF: LocalFinalVariableName
137                 final String SPACE = "\\s++";
138                 final String XML = "<\\?(?:(?:[^\"'>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+>";
139                 final String INTSUB = "\\[(?:(?:[^\"'\\]]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+\\]";
140                 final String DOCTYPE =
141                     "<!DOCTYPE(?:(?:[^\"'\\[>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+')|(?:" + INTSUB + "))*+>";
142                 final String PI = XML;
143                 final String COMMENT = "<!--(?:[^-]|(?:-[^-]))*+-->";
144 
145                 final String INTRO =
146                     "(?:(?:" + SPACE + ")|(?:" + XML + ")|(?:" + DOCTYPE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
147                 final String OUTRO = "(?:(?:" + SPACE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
148                 final String POM = "(?s)(" + INTRO + ")(.*?)(" + OUTRO + ")";
149                 // CHECKSTYLE_ON: LocalFinalVariableName
150 
151                 Matcher matcher = Pattern.compile( POM ).matcher( content );
152                 if ( matcher.matches() )
153                 {
154                     intro = matcher.group( 1 );
155                     outtro = matcher.group( matcher.groupCount() );
156                 }
157             }
158         }
159         catch ( JDOMException | IOException e )
160         {
161             throw new ReleaseExecutionException( "Error reading POM: " + e.getMessage(), e );
162         }
163     }
164 
165     @Override
166     public void transform()
167     {
168 
169     }
170 
171     @Override
172     public void load( File targetFile ) throws ReleaseExecutionException
173     {
174         writePom( targetFile, document, releaseDescriptor, project.getModelVersion(), intro, outtro );
175     }
176 
177     @Override
178     public Model getModel()
179     {
180         return new JDomModel( document );
181     }
182 
183     private void normaliseLineEndings( Document document )
184     {
185         for ( Iterator<?> i = document.getDescendants( new ContentFilter( ContentFilter.COMMENT ) ); i.hasNext(); )
186         {
187             Comment c = (Comment) i.next();
188             c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
189         }
190         for ( Iterator<?> i = document.getDescendants( new ContentFilter( ContentFilter.CDATA ) ); i.hasNext(); )
191         {
192             CDATA c = (CDATA) i.next();
193             c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
194         }
195     }
196 
197     private void writePom( File pomFile, Document document, ReleaseDescriptor releaseDescriptor, String modelVersion,
198                            String intro, String outtro )
199         throws ReleaseExecutionException
200     {
201         Element rootElement = document.getRootElement();
202 
203         if ( releaseDescriptor.isAddSchema() )
204         {
205             Namespace pomNamespace = Namespace.getNamespace( "", "http://maven.apache.org/POM/" + modelVersion );
206             rootElement.setNamespace( pomNamespace );
207             Namespace xsiNamespace = Namespace.getNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance" );
208             rootElement.addNamespaceDeclaration( xsiNamespace );
209 
210             if ( rootElement.getAttribute( "schemaLocation", xsiNamespace ) == null )
211             {
212                 rootElement.setAttribute( "schemaLocation", "http://maven.apache.org/POM/" + modelVersion
213                     + " https://maven.apache.org/xsd/maven-" + modelVersion + ".xsd", xsiNamespace );
214             }
215 
216             // the empty namespace is considered equal to the POM namespace, so match them up to avoid extra xmlns=""
217             ElementFilter elementFilter = new ElementFilter( Namespace.getNamespace( "" ) );
218             for ( Iterator<?> i = rootElement.getDescendants( elementFilter ); i.hasNext(); )
219             {
220                 Element e = (Element) i.next();
221                 e.setNamespace( pomNamespace );
222             }
223         }
224 
225         
226         try ( Writer writer = WriterFactory.newXmlWriter( pomFile ) )
227         {
228             if ( intro != null )
229             {
230                 writer.write( intro );
231             }
232 
233             Format format = Format.getRawFormat();
234             format.setLineSeparator( ls );
235             XMLOutputter out = new XMLOutputter( format );
236             out.output( document.getRootElement(), writer );
237 
238             if ( outtro != null )
239             {
240                 writer.write( outtro );
241             }
242         }
243         catch ( IOException e )
244         {
245             throw new ReleaseExecutionException( "Error writing POM: " + e.getMessage(), e );
246         }
247     }
248 
249 }