SWS-351 - Arbitrary parameter injection for @Endpoints

This commit is contained in:
Arjen Poutsma
2010-05-17 12:27:32 +00:00
parent 5ec05e28d5
commit 15ef03965e
9 changed files with 615 additions and 19 deletions

View File

@@ -33,6 +33,7 @@ import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResol
import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler;
import org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.StaxPayloadMethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.XPathParamMethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.dom.Dom4jPayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.dom.JDomPayloadMethodProcessor;
@@ -114,6 +115,7 @@ public class DefaultMethodEndpointAdapter extends AbstractMethodEndpointAdapter
methodArgumentResolvers.add(new DomPayloadMethodProcessor());
methodArgumentResolvers.add(new MessageContextMethodArgumentResolver());
methodArgumentResolvers.add(new SourcePayloadMethodProcessor());
methodArgumentResolvers.add(new XPathParamMethodArgumentResolver());
try {
Class<MethodArgumentResolver> soapMethodArgumentResolverClass =
(Class<MethodArgumentResolver>) ClassUtils

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.adapter.method;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.annotation.XPathParam;
import org.springframework.ws.server.endpoint.support.NamespaceUtils;
import org.springframework.xml.transform.TransformerObjectSupport;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Implementation of {@link MethodArgumentResolver} that supports the {@link XPathParam @XPathParam} annotation.
* <p/>
* This resolver supports parameters annotated with {@link XPathParam @XPathParam} that specifies the XPath expression
* that should be bound to that parameter. The parameter can either a "natively supported" XPath type ({@link Boolean
* boolean}, {@link Double double}, {@link String}, {@link Node}, or {@link NodeList}), or a type that is {@linkplain
* ConversionService#canConvert(Class, Class) supported} by the {@link ConversionService}.
*
* @author Arjen Poutsma
* @since 2.0
*/
public class XPathParamMethodArgumentResolver extends TransformerObjectSupport implements MethodArgumentResolver {
private final XPathFactory xpathFactory = createXPathFactory();
private ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
/**
* Sets the conversion service to use.
* <p/>
* Defaults to the {@linkplain ConversionServiceFactory#createDefaultConversionService() default conversion
* service}.
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.getParameterAnnotation(XPathParam.class) == null) {
return false;
}
Class<?> parameterType = parameter.getParameterType();
if (Boolean.class.equals(parameterType) || Boolean.TYPE.equals(parameterType) ||
Double.class.equals(parameterType) || Double.TYPE.equals(parameterType) ||
Node.class.isAssignableFrom(parameterType) || NodeList.class.isAssignableFrom(parameterType) ||
String.class.isAssignableFrom(parameterType)) {
return true;
}
else {
return conversionService.canConvert(String.class, parameterType);
}
}
public Object resolveArgument(MessageContext messageContext, MethodParameter parameter)
throws TransformerException, XPathExpressionException {
Class<?> parameterType = parameter.getParameterType();
QName evaluationReturnType = getReturnType(parameterType);
boolean useConversionService = false;
if (evaluationReturnType == null) {
evaluationReturnType = XPathConstants.STRING;
useConversionService = true;
}
XPath xpath = createXPath();
xpath.setNamespaceContext(NamespaceUtils.getNamespaceContext(parameter.getMethod()));
Element rootElement = getRootElement(messageContext.getRequest().getPayloadSource());
String expression = parameter.getParameterAnnotation(XPathParam.class).value();
Object result = xpath.evaluate(expression, rootElement, evaluationReturnType);
return useConversionService ? conversionService.convert(result, parameterType) : result;
}
private QName getReturnType(Class<?> parameterType) {
if (Boolean.class.equals(parameterType) || Boolean.TYPE.equals(parameterType)) {
return XPathConstants.BOOLEAN;
}
else if (Double.class.equals(parameterType) || Double.TYPE.equals(parameterType)) {
return XPathConstants.NUMBER;
}
else if (Node.class.equals(parameterType)) {
return XPathConstants.NODE;
}
else if (NodeList.class.equals(parameterType)) {
return XPathConstants.NODESET;
}
else if (String.class.equals(parameterType)) {
return XPathConstants.STRING;
}
else {
return null;
}
}
private XPath createXPath() {
synchronized (xpathFactory) {
return xpathFactory.newXPath();
}
}
private Element getRootElement(Source source) throws TransformerException {
DOMResult domResult = new DOMResult();
transform(source, domResult);
Document document = (Document) domResult.getNode();
return document.getDocumentElement();
}
/**
* Create a {@code XPathFactory} that this resolver will use to create {@link XPath} objects.
* <p/>
* Can be overridden in subclasses, adding further initialization of the factory. The resulting factory is cached,
* so this method will only be called once.
*
* @return the created factory
*/
protected XPathFactory createXPathFactory() {
return XPathFactory.newInstance();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.xml.XMLConstants;
/**
* Sets up a namespace to be used in an {@link Endpoint @Endpoint} method, class, or package.
* <p/>
* Typically used in combination with {@link XPathParam @XPathParam}, or {@link PayloadRoot @PayloadRoot}.
*
* @author Arjen Poutsma
* @see XPathParam
* @see PayloadRoot
* @since 2.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
public @interface Namespace {
/**
* Signifies the prefix of the namespace.
*
* @see #uri()
*/
String prefix() default XMLConstants.DEFAULT_NS_PREFIX;
/**
* Signifies the URI of the namespace.
*
* @see #prefix()
*/
String uri();
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Sets the namespaces to be used in an {@link Endpoint @Endpoint} method, class, or package.
*
* @author Arjen Poutsma
* @see Namespace
* @since 2.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
public @interface Namespaces {
Namespace[] value();
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.support;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import javax.xml.namespace.NamespaceContext;
import org.springframework.util.Assert;
import org.springframework.ws.server.endpoint.annotation.Namespace;
import org.springframework.ws.server.endpoint.annotation.Namespaces;
import org.springframework.xml.namespace.SimpleNamespaceContext;
/**
* Helper class for handling {@link Namespace @Namespace} annotations.
*
* @author Arjen Poutsma
* @since 2.0
*/
public abstract class NamespaceUtils {
private NamespaceUtils() {
}
/**
* Creates a {@code NamespaceContext} for the specified method, based on {@link Namespaces @Namespaces} and {@link
* Namespace @Namespace} annotations.
* <p/>
* This method will search for {@link Namespaces @Namespaces} and {@link Namespace @Namespace} annotation in the
* given method, its class, and its package, in reverse order. That is: package-level annotations are overridden by
* class-level annotations, which again are overridden by method-level annotations.
*
* @param method the method to create the namespace context for
* @return the namespace context
*/
public static NamespaceContext getNamespaceContext(Method method) {
Assert.notNull(method, "'method' must not be null");
SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
Class<?> endpointClass = method.getDeclaringClass();
Package endpointPackage = endpointClass.getPackage();
if (endpointPackage != null) {
addNamespaceAnnotations(endpointPackage, namespaceContext);
}
addNamespaceAnnotations(endpointClass, namespaceContext);
addNamespaceAnnotations(method, namespaceContext);
return namespaceContext;
}
private static void addNamespaceAnnotations(AnnotatedElement annotatedElement,
SimpleNamespaceContext namespaceContext) {
if (annotatedElement.isAnnotationPresent(Namespaces.class)) {
Namespaces namespacesAnn = annotatedElement.getAnnotation(Namespaces.class);
for (Namespace namespaceAnn : namespacesAnn.value()) {
namespaceContext.bindNamespaceUri(namespaceAnn.prefix(), namespaceAnn.uri());
}
}
if (annotatedElement.isAnnotationPresent(Namespace.class)) {
Namespace namespaceAnn = annotatedElement.getAnnotation(Namespace.class);
namespaceContext.bindNamespaceUri(namespaceAnn.prefix(), namespaceAnn.uri());
}
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.adapter.method;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
import org.springframework.ws.MockWebServiceMessage;
import org.springframework.ws.MockWebServiceMessageFactory;
import org.springframework.ws.context.DefaultMessageContext;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.annotation.Namespace;
import org.springframework.ws.server.endpoint.annotation.Namespaces;
import org.springframework.ws.server.endpoint.annotation.XPathParam;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import static org.junit.Assert.*;
@Namespaces(@Namespace(prefix = "tns", uri = "http://springframework.org/spring-ws"))
public class XPathParamMethodArgumentResolverTest {
private static final String CONTENTS = "<root><child><text>text</text><number>42</number></child></root>";
private XPathParamMethodArgumentResolver resolver;
private MethodParameter booleanParameter;
private MethodParameter doubleParameter;
private MethodParameter nodeParameter;
private MethodParameter nodeListParameter;
private MethodParameter stringParameter;
private MethodParameter convertedParameter;
private MethodParameter unsupportedParameter;
private MethodParameter namespaceMethodParameter;
private MethodParameter namespaceClassParameter;
@Before
public void setUp() throws Exception {
resolver = new XPathParamMethodArgumentResolver();
Method supportedTypes = getClass()
.getMethod("supportedTypes", Boolean.TYPE, Double.TYPE, Node.class, NodeList.class, String.class);
booleanParameter = new MethodParameter(supportedTypes, 0);
doubleParameter = new MethodParameter(supportedTypes, 1);
nodeParameter = new MethodParameter(supportedTypes, 2);
nodeListParameter = new MethodParameter(supportedTypes, 3);
stringParameter = new MethodParameter(supportedTypes, 4);
convertedParameter = new MethodParameter(getClass().getMethod("convertedType", Integer.TYPE), 0);
unsupportedParameter = new MethodParameter(getClass().getMethod("unsupported", String.class), 0);
namespaceMethodParameter = new MethodParameter(getClass().getMethod("namespacesMethod", String.class), 0);
namespaceClassParameter = new MethodParameter(getClass().getMethod("namespacesClass", String.class), 0);
}
@Test
public void supportsParameter() {
assertTrue("resolver does not support boolean parameter", resolver.supportsParameter(booleanParameter));
assertTrue("resolver does not support double parameter", resolver.supportsParameter(doubleParameter));
assertTrue("resolver does not support Node parameter", resolver.supportsParameter(nodeParameter));
assertTrue("resolver does not support NodeList parameter", resolver.supportsParameter(nodeListParameter));
assertTrue("resolver does not support String parameter", resolver.supportsParameter(stringParameter));
assertTrue("resolver does not support String parameter", resolver.supportsParameter(convertedParameter));
assertFalse("resolver supports parameter without @XPathParam", resolver.supportsParameter(unsupportedParameter));
}
@Test
public void resolveBoolean() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(CONTENTS);
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, booleanParameter);
assertTrue("resolver does not return boolean", result instanceof Boolean);
Boolean b = (Boolean) result;
assertTrue("Invalid boolean value", b);
}
@Test
public void resolveDouble() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(CONTENTS);
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, doubleParameter);
assertTrue("resolver does not return double", result instanceof Double);
Double d = (Double) result;
assertEquals("Invalid double value", 42D, d, 0D);
}
@Test
public void resolveNode() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(CONTENTS);
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, nodeParameter);
assertTrue("resolver does not return Node", result instanceof Node);
Node node = (Node) result;
assertEquals("Invalid node value", "child", node.getLocalName());
}
@Test
public void resolveNodeList() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(CONTENTS);
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, nodeListParameter);
assertTrue("resolver does not return NodeList", result instanceof NodeList);
NodeList nodeList = (NodeList) result;
assertEquals("Invalid NodeList value", 1, nodeList.getLength());
assertEquals("Invalid Node value", "child", nodeList.item(0).getLocalName());
}
@Test
public void resolveString() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(CONTENTS);
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, stringParameter);
assertTrue("resolver does not return String", result instanceof String);
String s = (String) result;
assertEquals("Invalid string value", "text", s);
}
@Test
public void resolveConvertedType() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(CONTENTS);
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, convertedParameter);
assertTrue("resolver does not return String", result instanceof Integer);
Integer i = (Integer) result;
assertEquals("Invalid integer value", new Integer(42), i);
}
@Test
public void resolveNamespacesMethod() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(
"<root xmlns=\"http://springframework.org/spring-ws\">text</root>");
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, namespaceMethodParameter);
assertTrue("resolver does not return String", result instanceof String);
String s = (String) result;
assertEquals("Invalid string value", "text", s);
}
@Test
public void resolveNamespacesClass() throws Exception {
MockWebServiceMessage request = new MockWebServiceMessage(
"<root xmlns=\"http://springframework.org/spring-ws\">text</root>");
MessageContext messageContext = new DefaultMessageContext(request, new MockWebServiceMessageFactory());
Object result = resolver.resolveArgument(messageContext, namespaceClassParameter);
assertTrue("resolver does not return String", result instanceof String);
String s = (String) result;
assertEquals("Invalid string value", "text", s);
}
public void unsupported(String s) {
}
public void supportedTypes(@XPathParam("/root/child")boolean param1,
@XPathParam("/root/child/number")double param2,
@XPathParam("/root/child") Node param3,
@XPathParam("/root/*") NodeList param4,
@XPathParam("/root/child/text")String param5) {
}
public void convertedType(@XPathParam("/root/child/number")int param) {
}
@Namespaces(@Namespace(prefix = "tns", uri = "http://springframework.org/spring-ws"))
public void namespacesMethod(@XPathParam("/tns:root")String s) {
}
public void namespacesClass(@XPathParam("/tns:root")String s) {
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.server.endpoint.support;
import java.lang.reflect.Method;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import org.springframework.ws.server.endpoint.annotation.Namespace;
import org.springframework.ws.server.endpoint.annotation.Namespaces;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@Namespaces({@Namespace(prefix = "prefix1", uri = "class1"), @Namespace(uri = "class2")})
public class NamespaceUtilsTest {
@Test
public void getNamespaceContextMethod() throws NoSuchMethodException {
Method method = getClass().getMethod("method");
NamespaceContext namespaceContext = NamespaceUtils.getNamespaceContext(method);
assertEquals("method1", namespaceContext.getNamespaceURI("prefix1"));
assertEquals("method2", namespaceContext.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
}
@Test
public void getNamespaceContextClass() throws NoSuchMethodException {
Method method = getClass().getMethod("getNamespaceContextClass");
NamespaceContext namespaceContext = NamespaceUtils.getNamespaceContext(method);
assertEquals("class1", namespaceContext.getNamespaceURI("prefix1"));
assertEquals("class2", namespaceContext.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
}
@Namespaces({@Namespace(prefix = "prefix1", uri = "method1"), @Namespace(uri = "method2")})
public void method() {
}
}

View File

@@ -16,20 +16,17 @@
package org.springframework.xml.namespace;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Simple <code>javax.xml.namespace.NamespaceContext</code> implementation. Follows the standard
@@ -41,9 +38,9 @@ import org.springframework.util.MultiValueMap;
*/
public class SimpleNamespaceContext implements NamespaceContext {
private Map<String, String> prefixToNamespaceUri = new HashMap<String, String>();
private Map<String, String> prefixToNamespaceUri = new LinkedHashMap<String, String>();
private MultiValueMap<String, String> namespaceUriToPrefixes = new LinkedMultiValueMap<String, String>();
private Map<String, Set<String>> namespaceUriToPrefixes = new LinkedHashMap<String, Set<String>>();
public String getNamespaceURI(String prefix) {
Assert.notNull(prefix, "prefix is null");
@@ -60,12 +57,14 @@ public class SimpleNamespaceContext implements NamespaceContext {
}
public String getPrefix(String namespaceUri) {
List<String> prefixes = getPrefixesInternal(namespaceUri);
return prefixes.isEmpty() ? null : prefixes.get(0);
Iterator<String> iterator = getPrefixes(namespaceUri);
return iterator.hasNext() ? iterator.next() : null;
}
public Iterator<String> getPrefixes(String namespaceUri) {
return getPrefixesInternal(namespaceUri).iterator();
Set<String> prefixes = getPrefixesInternal(namespaceUri);
prefixes = Collections.unmodifiableSet(prefixes);
return prefixes.iterator();
}
/**
@@ -125,23 +124,24 @@ public class SimpleNamespaceContext implements NamespaceContext {
public Iterator<String> getBoundPrefixes() {
Set<String> prefixes = new HashSet<String>(prefixToNamespaceUri.keySet());
prefixes.remove(XMLConstants.DEFAULT_NS_PREFIX);
prefixes = Collections.unmodifiableSet(prefixes);
return prefixes.iterator();
}
private List<String> getPrefixesInternal(String namespaceUri) {
private Set<String> getPrefixesInternal(String namespaceUri) {
if (XMLConstants.XML_NS_URI.equals(namespaceUri)) {
return Collections.singletonList(XMLConstants.XML_NS_PREFIX);
return Collections.singleton(XMLConstants.XML_NS_PREFIX);
}
else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceUri)) {
return Collections.singletonList(XMLConstants.XMLNS_ATTRIBUTE);
return Collections.singleton(XMLConstants.XMLNS_ATTRIBUTE);
}
else {
List<String> list = namespaceUriToPrefixes.get(namespaceUri);
if (list == null) {
list = new ArrayList<String>();
namespaceUriToPrefixes.put(namespaceUri, list);
Set<String> set = namespaceUriToPrefixes.get(namespaceUri);
if (set == null) {
set = new LinkedHashSet<String>();
namespaceUriToPrefixes.put(namespaceUri, set);
}
return list;
return set;
}
}
@@ -152,7 +152,7 @@ public class SimpleNamespaceContext implements NamespaceContext {
*/
public void removeBinding(String prefix) {
String namespaceUri = prefixToNamespaceUri.get(prefix);
List<String> prefixes = getPrefixesInternal(namespaceUri);
Set<String> prefixes = getPrefixesInternal(namespaceUri);
prefixes.remove(prefix);
}

View File

@@ -72,6 +72,17 @@ public class SimpleNamespaceContextTest {
assertPrefixes(XMLConstants.XML_NS_URI, XMLConstants.XML_NS_PREFIX);
assertPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE);
}
@Test(expected = UnsupportedOperationException.class)
public void unmodifiableGetPrefixes() {
String namespaceUri = "namespaceUri";
context.bindNamespaceUri("prefix1", namespaceUri);
context.bindNamespaceUri("prefix2", namespaceUri);
Iterator<String> prefixes = context.getPrefixes(namespaceUri);
prefixes.next();
prefixes.remove();
}
@Test
public void testMultiplePrefixes() {