View Javadoc
1   package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement;
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.codehaus.mojo.jaxb2.schemageneration.postprocessing.NodeProcessor;
23  import org.w3c.dom.Attr;
24  import org.w3c.dom.Element;
25  import org.w3c.dom.Node;
26  
27  import javax.xml.XMLConstants;
28  
29  /**
30   * <p><code>NodeProcessor</code> which alters the namespace prefix for all relevant Nodes within an XML
31   * document Node. It alters namespace prefixes in the following logical places:</p>
32   * <dl>
33   * <dt>Schema Namespace Definition</dt>
34   * <dd>xmlns:oldPrefix="http://some/namespace" is altered to xmlns:newPrefix="http://some/namespace"</dd>
35   * <dt>Elements Namespace Prefix</dt>
36   * <dd>&lt;oldPrefix:someElement ... &gt; is altered to &lt;newPrefix:someElement ... &gt;</dd>
37   * <dt>Element Reference</dt>
38   * <dd><code>&lt;xs:element ref="oldPrefix:aRequiredElementInTheOldPrefixNamespace"/&gt;</code> is altered to
39   * <code>&lt;xs:element ref="newPrefix:aRequiredElementInTheOldPrefixNamespace"/&gt;</code></dd>
40   * <dt>Type Attribute</dt>
41   * <dd><code>&lt;xs:element type="oldPrefix:something"/&gt;</code> is altered to
42   * <code>&lt;xs:element type="newPrefix:something"/&gt;</code></dd>
43   * <dt>Type Extension</dt>
44   * <dd><code>&lt;xs:extension base="oldPrefix:something"/&gt;</code> is altered to
45   * <code>&lt;xs:extension base="newPrefix:something"/&gt;</code></dd>
46   * </dl>
47   *
48   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>
49   * @since 1.4
50   */
51  public class ChangeNamespacePrefixProcessor implements NodeProcessor {
52  
53      // Constants
54      // <xs:extension base="tns:importItem">
55      private static final String EXTENSION_ELEMENT_NAME = "extension";
56      private static final String EXTENSION_BASE_ATTRIBUTE_NAME = "base";
57      private static final String REFERENCE_ATTRIBUTE_NAME = "ref";
58      private static final String TYPE_ATTRIBUTE_NAME = "type";
59      private static final String SCHEMA = "schema";
60      private static final String XMLNS = "xmlns:";
61  
62      // <xs:element name="someOtherImportItem" type="tns:someOtherImportItem"/>
63      // private static final String ELEMENT_NAME = "element";
64  
65      // Internal state
66      private String oldPrefix;
67      private String newPrefix;
68  
69      /**
70       * Creates a new ChangeNamespacePrefixProcessor providing the oldPrefix which should be replaced by the newPrefix.
71       *
72       * @param oldPrefix The old/current namespace prefix
73       * @param newPrefix The new/substituted namespace prefix
74       */
75      public ChangeNamespacePrefixProcessor(final String oldPrefix, final String newPrefix) {
76          this.oldPrefix = oldPrefix;
77          this.newPrefix = newPrefix;
78      }
79  
80      /**
81       * {@inheritDoc}
82       */
83      public boolean accept(final Node aNode) {
84  
85          if (oldPrefix.equals(aNode.getPrefix())) {
86              // Process any nodes on the form [oldPrefix]:something.
87              return true;
88          }
89  
90          if (aNode instanceof Attr) {
91  
92              // These cases are defined by attribute properties.
93              final Attr attribute = (Attr) aNode;
94  
95              if (isNamespaceDefinition(attribute)
96                      || isElementReference(attribute)
97                      || isTypeAttributeWithPrefix(attribute)
98                      || isExtension(attribute)) {
99                  return true;
100             }
101         }
102 
103         // Nopes.
104         return false;
105     }
106 
107     /**
108      * {@inheritDoc}
109      */
110     public void process(final Node aNode) {
111 
112         if (aNode instanceof Attr) {
113 
114             final Attr attribute = (Attr) aNode;
115             final Element parentElement = attribute.getOwnerElement();
116 
117             if (isNamespaceDefinition(attribute)) {
118 
119                 // Use the incredibly smooth DOM way to rename an attribute...
120                 parentElement.setAttributeNS(attribute.getNamespaceURI(), XMLNS + newPrefix, aNode.getNodeValue());
121                 parentElement.removeAttribute(XMLNS + oldPrefix);
122 
123             } else if (isElementReference(attribute)
124                     || isTypeAttributeWithPrefix(attribute)
125                     || isExtension(attribute)) {
126 
127                 // Simply alter the value of the reference
128                 final String value = attribute.getValue();
129                 final String elementName = value.substring(value.indexOf(":") + 1);
130                 attribute.setValue(newPrefix + ":" + elementName);
131             }
132         }
133 
134         if (oldPrefix.equals(aNode.getPrefix())) {
135             // Simply change the prefix to the new one.
136             aNode.setPrefix(newPrefix);
137         }
138     }
139 
140     //
141     // Private helpers
142     //
143 
144     /**
145      * Discovers if the provided attribute is the oldPrefix namespace definition, i.e. if the given attribute is the
146      * xmlns:[oldPrefix] within the schema Element.
147      *
148      * @param attribute the attribute to test.
149      * @return <code>true</code> if the provided attribute is the oldPrefix namespace definition, i.e. if the given
150      * attribute is the xmlns:[oldPrefix] within the schema Element.
151      */
152     private boolean isNamespaceDefinition(final Attr attribute) {
153 
154         final Element parent = attribute.getOwnerElement();
155 
156         return XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(parent.getNamespaceURI())
157                 && SCHEMA.equalsIgnoreCase(parent.getLocalName())
158                 && oldPrefix.equals(attribute.getLocalName());
159     }
160 
161     /**
162      * Discovers if the provided attribute is a namespace reference to the oldPrefix namespace, on the form
163      * <code>&lt;xs:element ref="oldPrefix:anElementInTheOldPrefixNamespace"/&gt;</code>
164      *
165      * @param attribute the attribute to test.
166      * @return <code>true</code> if the provided attribute is named "ref" and starts with <code>[oldPrefix]:</code>, in
167      * which case it is a reference to the oldPrefix namespace.
168      */
169     private boolean isElementReference(final Attr attribute) {
170         return REFERENCE_ATTRIBUTE_NAME.equals(attribute.getName())
171                 && attribute.getValue().startsWith(oldPrefix + ":");
172     }
173 
174     /**
175      * Discovers if the provided attribute is a type attribute using the oldPrefix namespace, on the form
176      * <code>&lt;xs:element type="oldPrefix:anElementInTheOldPrefixNamespace"/&gt;</code>
177      *
178      * @param attribute the attribute to test.
179      * @return <code>true</code> if the provided attribute is named "type" and starts with <code>[oldPrefix]:</code>, in
180      * which case it is a type in the oldPrefix namespace.
181      */
182     private boolean isTypeAttributeWithPrefix(final Attr attribute) {
183         return TYPE_ATTRIBUTE_NAME.equals(attribute.getName()) && attribute.getValue().startsWith(oldPrefix + ":");
184     }
185 
186     /**
187      * Discovers if the provided attribute is a namespace reference to the oldPrefix namespace, on the form
188      * <p/>
189      * <pre>
190      *     <code>&lt;xs:extension base="[oldPrefix]:importItem"&gt;</code>
191      * </pre>
192      *
193      * @param attribute the attribute to test.
194      * @return <code>true</code> if the provided attribute is named "extension" and starts with
195      * <code>[oldPrefix]:</code>, in which case it is a reference to the oldPrefix namespace.
196      */
197     private boolean isExtension(final Attr attribute) {
198 
199         final Element parent = attribute.getOwnerElement();
200 
201         return XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(parent.getNamespaceURI())
202                 && EXTENSION_ELEMENT_NAME.equalsIgnoreCase(parent.getLocalName())
203                 && EXTENSION_BASE_ATTRIBUTE_NAME.equalsIgnoreCase(attribute.getName())
204                 && attribute.getValue().startsWith(oldPrefix + ":");
205     }
206 }