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