Skip to content

Commit 80f0871

Browse files
Fix AXIS2-5972: Preserve namespace for xs:attribute ref= in ADB code generation
When a schema uses <xs:attribute ref="xmime:contentType"/>, the ADB code generator was producing code with a null/empty namespace because the resolved global attribute's getWireName() lost the ref namespace for attributes with inline types. This caused getAttributeValue(null, "contentType") and writeAttribute("", "contentType", ...) instead of using the correct "http://www.w3.org/2005/05/xmlmime" namespace URI. After the recursive processAttribute() call for ref attributes, re-key the metainfo mapping from the empty-namespace QName to the correct ref QName when the namespace was lost during resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b26f550 commit 80f0871

File tree

3 files changed

+182
-5
lines changed

3 files changed

+182
-5
lines changed

modules/adb-codegen/src/org/apache/axis2/schema/BeanWriterMetaInfoHolder.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,62 @@ public String getJavaName(String xmlName){
961961

962962
public QName getRestrictionBaseType() {
963963
return restrictionBaseType;
964-
}
964+
}
965+
966+
/**
967+
* Re-keys all map entries from oldKey to newKey across all internal maps.
968+
* This is used when an attribute ref= resolves to a global attribute whose
969+
* wireName has lost the original namespace, so we need to re-register
970+
* the mapping under the correct QName (AXIS2-5972).
971+
*
972+
* @param oldKey the QName currently used as key (e.g. with empty namespace)
973+
* @param newKey the correct QName to use (e.g. with the ref namespace)
974+
*/
975+
public void rekeyMapping(QName oldKey, QName newKey) {
976+
if (oldKey.equals(newKey)) {
977+
return;
978+
}
979+
// Re-key elementToJavaClassMap
980+
if (elementToJavaClassMap.containsKey(oldKey)) {
981+
elementToJavaClassMap.put(newKey, elementToJavaClassMap.remove(oldKey));
982+
}
983+
// Re-key elementToSchemaQNameMap
984+
if (elementToSchemaQNameMap.containsKey(oldKey)) {
985+
elementToSchemaQNameMap.put(newKey, elementToSchemaQNameMap.remove(oldKey));
986+
}
987+
// Re-key specialTypeFlagMap
988+
if (specialTypeFlagMap.containsKey(oldKey)) {
989+
specialTypeFlagMap.put(newKey, specialTypeFlagMap.remove(oldKey));
990+
}
991+
// Re-key qNameMaxOccursCountMap
992+
if (qNameMaxOccursCountMap.containsKey(oldKey)) {
993+
qNameMaxOccursCountMap.put(newKey, qNameMaxOccursCountMap.remove(oldKey));
994+
}
995+
// Re-key qNameMinOccursCountMap
996+
if (qNameMinOccursCountMap.containsKey(oldKey)) {
997+
qNameMinOccursCountMap.put(newKey, qNameMinOccursCountMap.remove(oldKey));
998+
}
999+
// Re-key elementQNameToDefulatValueMap
1000+
if (elementQNameToDefulatValueMap.containsKey(oldKey)) {
1001+
elementQNameToDefulatValueMap.put(newKey, elementQNameToDefulatValueMap.remove(oldKey));
1002+
}
1003+
// Re-key nillableQNameList
1004+
int nillIdx = nillableQNameList.indexOf(oldKey);
1005+
if (nillIdx >= 0) {
1006+
nillableQNameList.set(nillIdx, newKey);
1007+
}
1008+
// Re-key fixedQNameList
1009+
int fixedIdx = fixedQNameList.indexOf(oldKey);
1010+
if (fixedIdx >= 0) {
1011+
fixedQNameList.set(fixedIdx, newKey);
1012+
}
1013+
// Re-key qNameOrderMap (maps Integer -> QName, so replace values)
1014+
for (Map.Entry<Integer, QName> entry : qNameOrderMap.entrySet()) {
1015+
if (oldKey.equals(entry.getValue())) {
1016+
entry.setValue(newKey);
1017+
}
1018+
}
1019+
}
9651020

9661021
@Override
9671022
public String toString() {

modules/adb-codegen/src/org/apache/axis2/schema/SchemaCompiler.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,22 +1898,32 @@ public void processAttribute(XmlSchemaAttribute att, BeanWriterMetaInfoHolder me
18981898

18991899
} else if (att.getRef().getTargetQName() != null) {
19001900

1901-
XmlSchema resolvedSchema = getParentSchema(parentSchema, att.getRef().getTargetQName(),
1901+
QName refQName = att.getRef().getTargetQName();
1902+
XmlSchema resolvedSchema = getParentSchema(parentSchema, refQName,
19021903
COMPONENT_ATTRIBUTE);
19031904
if (resolvedSchema == null) {
19041905
throw new SchemaCompilationException("can not find the attribute " +
1905-
att.getRef().getTargetQName() +
1906+
refQName +
19061907
" from the parent schema " +
19071908
parentSchema.getTargetNamespace());
19081909
} else {
19091910
XmlSchemaAttribute xmlSchemaAttribute =
1910-
resolvedSchema.getAttributes().get(att.getRef().getTargetQName());
1911+
resolvedSchema.getAttributes().get(refQName);
19111912
if (xmlSchemaAttribute != null) {
19121913
// call recursively to process the schema
19131914
processAttribute(xmlSchemaAttribute, metainf, resolvedSchema);
1915+
// AXIS2-5972: Preserve the namespace from the ref QName.
1916+
// The resolved attribute's getWireName() may return an empty namespace
1917+
// for global attributes with inline types, losing the ref namespace.
1918+
QName wireName = xmlSchemaAttribute.getWireName();
1919+
if (wireName != null
1920+
&& (wireName.getNamespaceURI() == null || wireName.getNamespaceURI().isEmpty())
1921+
&& refQName.getNamespaceURI() != null && !refQName.getNamespaceURI().isEmpty()) {
1922+
metainf.rekeyMapping(wireName, refQName);
1923+
}
19141924
} else {
19151925
throw new SchemaCompilationException("Attribute QName reference refer to an invalid attribute " +
1916-
att.getRef().getTargetQName());
1926+
refQName);
19171927
}
19181928
}
19191929

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
20+
package org.apache.axis2.schema;
21+
22+
import junit.framework.TestCase;
23+
import org.apache.ws.commons.schema.XmlSchema;
24+
import org.apache.ws.commons.schema.XmlSchemaCollection;
25+
import org.w3c.dom.Document;
26+
27+
import javax.xml.namespace.QName;
28+
import javax.xml.parsers.DocumentBuilder;
29+
import javax.xml.parsers.DocumentBuilderFactory;
30+
import java.io.File;
31+
import java.lang.reflect.Field;
32+
import java.util.HashMap;
33+
34+
/**
35+
* Test for AXIS2-5972: Verifies that xs:attribute ref= preserves the namespace
36+
* from the ref QName in the generated metainfo.
37+
*/
38+
public class SchemaCompilerRefAttributeTest extends TestCase {
39+
40+
private static final String XMLMIME_NS = "http://www.w3.org/2005/05/xmlmime";
41+
42+
/**
43+
* Compiles xmlmime.xsd and verifies that the contentType attribute on the
44+
* base64Binary type is registered with the correct namespace URI from the
45+
* ref QName, not with an empty namespace.
46+
*/
47+
public void testRefAttributeNamespacePreserved() throws Exception {
48+
// Load the xmlmime.xsd schema
49+
String basedir = System.getProperty("basedir", ".");
50+
File schemaFile = new File(basedir + "/test-resources/std/xmlmime.xsd");
51+
assertTrue("xmlmime.xsd not found at " + schemaFile.getAbsolutePath(), schemaFile.exists());
52+
53+
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
54+
dbf.setNamespaceAware(true);
55+
DocumentBuilder builder = dbf.newDocumentBuilder();
56+
Document doc = builder.parse(schemaFile);
57+
58+
XmlSchemaCollection schemaCol = new XmlSchemaCollection();
59+
XmlSchema schema = schemaCol.read(doc, null);
60+
61+
// Compile the schema with generateAll=true since xmlmime.xsd has
62+
// only types (no elements), and types are only compiled when generateAll is set
63+
CompilerOptions compilerOptions = new CompilerOptions();
64+
compilerOptions.setGenerateAll(true);
65+
SchemaCompiler compiler = new SchemaCompiler(compilerOptions);
66+
compiler.compile(schema);
67+
68+
// Access the processedTypeMetaInfoMap via reflection
69+
Field metaInfoMapField = SchemaCompiler.class.getDeclaredField("processedTypeMetaInfoMap");
70+
metaInfoMapField.setAccessible(true);
71+
@SuppressWarnings("unchecked")
72+
HashMap<QName, BeanWriterMetaInfoHolder> metaInfoMap =
73+
(HashMap<QName, BeanWriterMetaInfoHolder>) metaInfoMapField.get(compiler);
74+
75+
// Check the base64Binary type's metainfo
76+
QName base64BinaryQName = new QName(XMLMIME_NS, "base64Binary");
77+
BeanWriterMetaInfoHolder metainf = metaInfoMap.get(base64BinaryQName);
78+
assertNotNull("MetaInfo for base64Binary type should exist", metainf);
79+
80+
// The contentType attribute should be registered with the xmlmime namespace
81+
QName expectedAttrQName = new QName(XMLMIME_NS, "contentType");
82+
QName wrongAttrQName = new QName("", "contentType");
83+
84+
// Verify the attribute is registered with the correct namespace
85+
String javaClass = metainf.getClassNameForQName(expectedAttrQName);
86+
assertNotNull(
87+
"contentType attribute should be registered with namespace " + XMLMIME_NS +
88+
" (AXIS2-5972: ref attribute namespace must be preserved)",
89+
javaClass);
90+
91+
// Verify the attribute is NOT registered with an empty namespace
92+
String wrongJavaClass = metainf.getClassNameForQName(wrongAttrQName);
93+
assertNull(
94+
"contentType attribute should NOT be registered with empty namespace",
95+
wrongJavaClass);
96+
97+
// Verify it's flagged as an attribute type
98+
assertTrue(
99+
"contentType should be flagged as an attribute",
100+
metainf.getAttributeStatusForQName(expectedAttrQName));
101+
102+
// Also check hexBinary type
103+
QName hexBinaryQName = new QName(XMLMIME_NS, "hexBinary");
104+
BeanWriterMetaInfoHolder hexMetainf = metaInfoMap.get(hexBinaryQName);
105+
assertNotNull("MetaInfo for hexBinary type should exist", hexMetainf);
106+
107+
String hexJavaClass = hexMetainf.getClassNameForQName(expectedAttrQName);
108+
assertNotNull(
109+
"contentType attribute on hexBinary should also have correct namespace",
110+
hexJavaClass);
111+
}
112+
}

0 commit comments

Comments
 (0)