From 96b1dc9db408f663831ba39f1f4a356a543a9a42 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 14 Jun 2010 23:23:49 +0000 Subject: [PATCH] ConversionService fully supports conversion from String to MediaType now (through 'valueOf'; SPR-7282); revised exception handling in ObjectToObjectConverter, avoiding InvocationTargetExceptions --- .../convert/ConversionFailedException.java | 2 +- .../support/ObjectToObjectConverter.java | 44 ++++----- .../org/springframework/http/MediaType.java | 90 ++++++++++--------- .../springframework/http/MediaTypeTests.java | 18 +++- 4 files changed, 78 insertions(+), 76 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java index 2b91c588df..d3f0839d9b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionFailedException.java @@ -40,7 +40,7 @@ public final class ConversionFailedException extends ConversionException { * @param cause the cause of the conversion failure */ public ConversionFailedException(TypeDescriptor sourceType, TypeDescriptor targetType, Object value, Throwable cause) { - super("Unable to convert value " + value + " from type '" + sourceType.getName() + + super("Unable to convert value \"" + value + "\" from type '" + sourceType.getName() + "' to type '" + targetType.getName() + "'", cause); this.sourceType = sourceType; this.targetType = targetType; diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java index 3a0bfa150a..87a11716e3 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-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. @@ -54,37 +54,27 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { Class sourceClass = sourceType.getObjectType(); Class targetClass = targetType.getObjectType(); - Object target; Method method = getValueOfMethodOn(targetClass, sourceClass); - if (method != null) { - ReflectionUtils.makeAccessible(method); - target = ReflectionUtils.invokeMethod(method, null, source); - } - else { - Constructor constructor = getConstructor(targetClass, sourceClass); - if (constructor != null) { - try { - target = constructor.newInstance(source); - } - catch (IllegalArgumentException ex) { - throw new ConversionFailedException(sourceType, targetType, source, ex); - } - catch (InstantiationException ex) { - throw new ConversionFailedException(sourceType, targetType, source, ex); - } - catch (IllegalAccessException ex) { - throw new ConversionFailedException(sourceType, targetType, source, ex); - } - catch (InvocationTargetException ex) { - throw new ConversionFailedException(sourceType, targetType, source, ex); - } + try { + if (method != null) { + ReflectionUtils.makeAccessible(method); + return method.invoke(null, source); } else { - throw new IllegalStateException("No static valueOf(" + sourceClass.getName() + - ") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName()); + Constructor constructor = getConstructor(targetClass, sourceClass); + if (constructor != null) { + return constructor.newInstance(source); + } } } - return target; + catch (InvocationTargetException ex) { + throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException()); + } + catch (Throwable ex) { + throw new ConversionFailedException(sourceType, targetType, source, ex); + } + throw new IllegalStateException("No static valueOf(" + sourceClass.getName() + + ") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName()); } diff --git a/org.springframework.web/src/main/java/org/springframework/http/MediaType.java b/org.springframework.web/src/main/java/org/springframework/http/MediaType.java index 24d36aee1a..83715d8ff9 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/MediaType.java +++ b/org.springframework.web/src/main/java/org/springframework/http/MediaType.java @@ -37,14 +37,14 @@ import org.springframework.util.StringUtils; /** * Represents an Internet Media Type, as defined in the HTTP specification. * - *

Consists of a {@linkplain #getType() type} and a {@linkplain #getSubtype() subtype}. Also has functionality to - * parse media types from a string using {@link #parseMediaType(String)}, or multiple comma-separated media types using - * {@link #parseMediaTypes(String)}. + *

Consists of a {@linkplain #getType() type} and a {@linkplain #getSubtype() subtype}. + * Also has functionality to parse media types from a string using {@link #parseMediaType(String)}, + * or multiple comma-separated media types using {@link #parseMediaTypes(String)}. * * @author Arjen Poutsma * @author Juergen Hoeller - * @see HTTP 1.1, section 3.7 * @since 3.0 + * @see HTTP 1.1, section 3.7 */ public class MediaType implements Comparable { @@ -118,6 +118,7 @@ public class MediaType implements Comparable { * */ public final static MediaType TEXT_XML; + private static final BitSet TOKEN; private static final String WILDCARD_TYPE = "*"; @@ -184,11 +185,10 @@ public class MediaType implements Comparable { TEXT_XML = new MediaType("text","xml"); } + /** * Create a new {@link MediaType} for the given primary type. - * *

The {@linkplain #getSubtype() subtype} is set to *, parameters empty. - * * @param type the primary type * @throws IllegalArgumentException if any of the parameters contain illegal characters */ @@ -197,8 +197,8 @@ public class MediaType implements Comparable { } /** - * Create a new {@link MediaType} for the given primary type and subtype.

The parameters are empty. - * + * Create a new {@link MediaType} for the given primary type and subtype. + *

The parameters are empty. * @param type the primary type * @param subtype the subtype * @throws IllegalArgumentException if any of the parameters contain illegal characters @@ -209,7 +209,6 @@ public class MediaType implements Comparable { /** * Create a new {@link MediaType} for the given type, subtype, and character set. - * * @param type the primary type * @param subtype the subtype * @param charSet the character set @@ -232,9 +231,8 @@ public class MediaType implements Comparable { } /** - * Copy-constructor that copies the type and subtype of the given {@link MediaType}, and allows for different - * parameter. - * + * Copy-constructor that copies the type and subtype of the given {@link MediaType}, + * and allows for different parameter. * @param other the other media type * @param parameters the parameters, may be null * @throws IllegalArgumentException if any of the parameters contain illegal characters @@ -245,7 +243,6 @@ public class MediaType implements Comparable { /** * Create a new {@link MediaType} for the given type, subtype, and parameters. - * * @param type the primary type * @param subtype the subtype * @param parameters the parameters, may be null @@ -275,7 +272,6 @@ public class MediaType implements Comparable { /** * Checks the given token string for illegal characters, as defined in RFC 2616, section 2.2. - * * @throws IllegalArgumentException in case of illegal characters * @see HTTP 1.1, section 2.2 */ @@ -295,7 +291,8 @@ public class MediaType implements Comparable { if (PARAM_QUALITY_FACTOR.equals(attribute)) { value = unquote(value); double d = Double.parseDouble(value); - Assert.isTrue(d >= 0D && d <= 1D, "Invalid quality value \"" + value + "\": should be between 0.0 and 1.0"); + Assert.isTrue(d >= 0D && d <= 1D, + "Invalid quality value \"" + value + "\": should be between 0.0 and 1.0"); } else if (PARAM_CHARSET.equals(attribute)) { value = unquote(value); @@ -317,24 +314,29 @@ public class MediaType implements Comparable { return isQuotedString(s) ? s.substring(1, s.length() - 1) : s; } - /** Return the primary type. */ + /** + * Return the primary type. + */ public String getType() { return this.type; } - /** Indicate whether the {@linkplain #getType() type} is the wildcard character * or not. */ + /** + * Indicate whether the {@linkplain #getType() type} is the wildcard character * or not. + */ public boolean isWildcardType() { return WILDCARD_TYPE.equals(type); } - /** Return the subtype. */ + /** + * Return the subtype. + */ public String getSubtype() { return this.subtype; } /** * Indicate whether the {@linkplain #getSubtype() subtype} is the wildcard character * or not. - * * @return whether the subtype is * */ public boolean isWildcardSubtype() { @@ -343,7 +345,6 @@ public class MediaType implements Comparable { /** * Return the character set, as indicated by a charset parameter, if any. - * * @return the character set; or null if not available */ public Charset getCharSet() { @@ -352,8 +353,8 @@ public class MediaType implements Comparable { } /** - * Return the quality value, as indicated by a q parameter, if any. Defaults to 1.0. - * + * Return the quality value, as indicated by a q parameter, if any. + * Defaults to 1.0. * @return the quality factory */ public double getQualityValue() { @@ -363,7 +364,6 @@ public class MediaType implements Comparable { /** * Return a generic parameter value, given a parameter name. - * * @param name the parameter name * @return the parameter value; or null if not present */ @@ -373,10 +373,8 @@ public class MediaType implements Comparable { /** * Indicate whether this {@link MediaType} includes the given media type. - * *

For instance, {@code text/*} includes {@code text/plain}, {@code text/html}, and {@code application/*+xml} * includes {@code application/soap+xml}, etc. This method is non-symmetic. - * * @param other the reference media type with which to compare * @return true if this media type includes the given media type; false otherwise */ @@ -410,10 +408,8 @@ public class MediaType implements Comparable { /** * Indicate whether this {@link MediaType} is compatible with the given media type. - * - *

For instance, {@code text/*} is compatible with {@code text/plain}, {@code text/html}, and vice versa. In - * effect, this method is similar to {@link #includes(MediaType)}, except that it's symmetric. - * + *

For instance, {@code text/*} is compatible with {@code text/plain}, {@code text/html}, and vice versa. + * In effect, this method is similar to {@link #includes(MediaType)}, except that it's symmetric. * @param other the reference media type with which to compare * @return true if this media type is compatible with the given media type; false otherwise */ @@ -449,7 +445,6 @@ public class MediaType implements Comparable { /** * Compares this {@link MediaType} to another alphabetically. - * * @param other media type to compare to * @see #sortBySpecificity(List) */ @@ -527,7 +522,7 @@ public class MediaType implements Comparable { appendTo(this.parameters, builder); } - private static void appendTo(Map map, StringBuilder builder) { + private void appendTo(Map map, StringBuilder builder) { for (Map.Entry entry : map.entrySet()) { builder.append(';'); builder.append(entry.getKey()); @@ -536,9 +531,19 @@ public class MediaType implements Comparable { } } + + /** + * Parse the given String value into a {@link MediaType} object, + * with this method name following the 'valueOf' naming convention + * (as supported by {@link org.springframework.core.convert.ConversionService}. + * @see #parseMediaType(String) + */ + public static MediaType valueOf(String value) { + return parseMediaType(value); + } + /** * Parse the given String into a single {@link MediaType}. - * * @param mediaType the string to parse * @return the media type * @throws IllegalArgumentException if the string cannot be parsed @@ -581,9 +586,8 @@ public class MediaType implements Comparable { /** - * Parse the given, comma-seperated string into a list of {@link MediaType} objects.

This method can be used to - * parse an Accept or Content-Type header. - * + * Parse the given, comma-seperated string into a list of {@link MediaType} objects. + *

This method can be used to parse an Accept or Content-Type header. * @param mediaTypes the string to parse * @return the list of media types * @throws IllegalArgumentException if the string cannot be parsed @@ -602,9 +606,7 @@ public class MediaType implements Comparable { /** * Return a string representation of the given list of {@link MediaType} objects. - * *

This method can be used to for an {@code Accept} or {@code Content-Type} header. - * * @param mediaTypes the string to parse * @return the list of media types * @throws IllegalArgumentException if the String cannot be parsed @@ -623,7 +625,6 @@ public class MediaType implements Comparable { /** * Sorts the given list of {@link MediaType} objects by specificity. - * *

Given two media types: *

    *
  1. if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the @@ -639,14 +640,12 @@ public class MediaType implements Comparable { *
  2. if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the * media type with the most parameters is ordered before the other.
  3. *
- * *

For example: *

audio/basic < audio/* < */*
*
audio/* < audio/*;q=0.7; audio/*;q=0.3
*
audio/basic;level=1 < audio/basic
*
audio/basic == text/html
*
audio/basic == audio/wave
- * * @param mediaTypes the list of media types to be sorted * @see HTTP 1.1, section 14.1 */ @@ -659,7 +658,6 @@ public class MediaType implements Comparable { /** * Sorts the given list of {@link MediaType} objects by quality value. - * *

Given two media types: *

    *
  1. if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type @@ -675,7 +673,6 @@ public class MediaType implements Comparable { *
  2. if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the * media type with the most parameters is ordered before the other.
  3. *
- * * @param mediaTypes the list of media types to be sorted * @see #getQualityValue() */ @@ -686,6 +683,7 @@ public class MediaType implements Comparable { } } + static final Comparator SPECIFICITY_COMPARATOR = new Comparator() { public int compare(MediaType mediaType1, MediaType mediaType2) { @@ -714,7 +712,8 @@ public class MediaType implements Comparable { int qualityComparison = Double.compare(quality2, quality1); if (qualityComparison != 0) { return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3 - } else { + } + else { int paramsSize1 = mediaType1.parameters.size(); int paramsSize2 = mediaType2.parameters.size(); return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic @@ -724,6 +723,7 @@ public class MediaType implements Comparable { } }; + static final Comparator QUALITY_VALUE_COMPARATOR = new Comparator() { public int compare(MediaType mediaType1, MediaType mediaType2) { @@ -751,7 +751,8 @@ public class MediaType implements Comparable { } else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave return 0; - } else { + } + else { int paramsSize1 = mediaType1.parameters.size(); int paramsSize2 = mediaType2.parameters.size(); return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic @@ -759,4 +760,5 @@ public class MediaType implements Comparable { } } }; + } diff --git a/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java b/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java index 3b177b3068..dab8d8ed78 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java +++ b/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java @@ -26,7 +26,13 @@ import java.util.Random; import static org.junit.Assert.*; import org.junit.Test; -/** @author Arjen Poutsma */ +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.ConversionServiceFactory; + +/** + * @author Arjen Poutsma + * @author Juergen Hoeller + */ public class MediaTypeTests { @Test @@ -480,10 +486,14 @@ public class MediaTypeTests { for (int i = 0; i < result.size(); i++) { assertSame("Invalid media type at " + i, expected.get(i), result.get(i)); } - } - - + @Test + public void testWithConversionService() { + ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); + assertTrue(conversionService.canConvert(String.class, MediaType.class)); + MediaType mediaType = MediaType.parseMediaType("application/xml"); + assertEquals(mediaType, conversionService.convert("application/xml", MediaType.class)); + } }