001/* 002 * Copyright 2002-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.format.support; 018 019import java.lang.annotation.Annotation; 020import java.util.Collections; 021import java.util.Map; 022import java.util.Set; 023import java.util.concurrent.ConcurrentHashMap; 024 025import org.springframework.context.EmbeddedValueResolverAware; 026import org.springframework.context.i18n.LocaleContextHolder; 027import org.springframework.core.DecoratingProxy; 028import org.springframework.core.GenericTypeResolver; 029import org.springframework.core.convert.ConversionService; 030import org.springframework.core.convert.TypeDescriptor; 031import org.springframework.core.convert.converter.ConditionalGenericConverter; 032import org.springframework.core.convert.converter.GenericConverter; 033import org.springframework.core.convert.support.GenericConversionService; 034import org.springframework.format.AnnotationFormatterFactory; 035import org.springframework.format.Formatter; 036import org.springframework.format.FormatterRegistry; 037import org.springframework.format.Parser; 038import org.springframework.format.Printer; 039import org.springframework.util.StringUtils; 040import org.springframework.util.StringValueResolver; 041 042/** 043 * A {@link org.springframework.core.convert.ConversionService} implementation 044 * designed to be configured as a {@link FormatterRegistry}. 045 * 046 * @author Keith Donald 047 * @author Juergen Hoeller 048 * @since 3.0 049 */ 050public class FormattingConversionService extends GenericConversionService 051 implements FormatterRegistry, EmbeddedValueResolverAware { 052 053 private StringValueResolver embeddedValueResolver; 054 055 private final Map<AnnotationConverterKey, GenericConverter> cachedPrinters = 056 new ConcurrentHashMap<AnnotationConverterKey, GenericConverter>(64); 057 058 private final Map<AnnotationConverterKey, GenericConverter> cachedParsers = 059 new ConcurrentHashMap<AnnotationConverterKey, GenericConverter>(64); 060 061 062 @Override 063 public void setEmbeddedValueResolver(StringValueResolver resolver) { 064 this.embeddedValueResolver = resolver; 065 } 066 067 068 @Override 069 public void addFormatter(Formatter<?> formatter) { 070 addFormatterForFieldType(getFieldType(formatter), formatter); 071 } 072 073 @Override 074 public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { 075 addConverter(new PrinterConverter(fieldType, formatter, this)); 076 addConverter(new ParserConverter(fieldType, formatter, this)); 077 } 078 079 @Override 080 public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) { 081 addConverter(new PrinterConverter(fieldType, printer, this)); 082 addConverter(new ParserConverter(fieldType, parser, this)); 083 } 084 085 @Override 086 public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) { 087 Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory); 088 if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) { 089 ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver); 090 } 091 Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes(); 092 for (Class<?> fieldType : fieldTypes) { 093 addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType)); 094 addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType)); 095 } 096 } 097 098 099 static Class<?> getFieldType(Formatter<?> formatter) { 100 Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); 101 if (fieldType == null && formatter instanceof DecoratingProxy) { 102 fieldType = GenericTypeResolver.resolveTypeArgument( 103 ((DecoratingProxy) formatter).getDecoratedClass(), Formatter.class); 104 } 105 if (fieldType == null) { 106 throw new IllegalArgumentException("Unable to extract the parameterized field type from Formatter [" + 107 formatter.getClass().getName() + "]; does the class parameterize the <T> generic type?"); 108 } 109 return fieldType; 110 } 111 112 @SuppressWarnings("unchecked") 113 static Class<? extends Annotation> getAnnotationType(AnnotationFormatterFactory<? extends Annotation> factory) { 114 Class<? extends Annotation> annotationType = (Class<? extends Annotation>) 115 GenericTypeResolver.resolveTypeArgument(factory.getClass(), AnnotationFormatterFactory.class); 116 if (annotationType == null) { 117 throw new IllegalArgumentException("Unable to extract parameterized Annotation type argument from " + 118 "AnnotationFormatterFactory [" + factory.getClass().getName() + 119 "]; does the factory parameterize the <A extends Annotation> generic type?"); 120 } 121 return annotationType; 122 } 123 124 125 private static class PrinterConverter implements GenericConverter { 126 127 private final Class<?> fieldType; 128 129 private final TypeDescriptor printerObjectType; 130 131 @SuppressWarnings("rawtypes") 132 private final Printer printer; 133 134 private final ConversionService conversionService; 135 136 public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) { 137 this.fieldType = fieldType; 138 this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer)); 139 this.printer = printer; 140 this.conversionService = conversionService; 141 } 142 143 @Override 144 public Set<ConvertiblePair> getConvertibleTypes() { 145 return Collections.singleton(new ConvertiblePair(this.fieldType, String.class)); 146 } 147 148 @Override 149 @SuppressWarnings("unchecked") 150 public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 151 if (source == null) { 152 return ""; 153 } 154 if (!sourceType.isAssignableTo(this.printerObjectType)) { 155 source = this.conversionService.convert(source, sourceType, this.printerObjectType); 156 } 157 return this.printer.print(source, LocaleContextHolder.getLocale()); 158 } 159 160 private Class<?> resolvePrinterObjectType(Printer<?> printer) { 161 return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class); 162 } 163 164 @Override 165 public String toString() { 166 return (this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer); 167 } 168 } 169 170 171 private static class ParserConverter implements GenericConverter { 172 173 private final Class<?> fieldType; 174 175 private final Parser<?> parser; 176 177 private final ConversionService conversionService; 178 179 public ParserConverter(Class<?> fieldType, Parser<?> parser, ConversionService conversionService) { 180 this.fieldType = fieldType; 181 this.parser = parser; 182 this.conversionService = conversionService; 183 } 184 185 @Override 186 public Set<ConvertiblePair> getConvertibleTypes() { 187 return Collections.singleton(new ConvertiblePair(String.class, this.fieldType)); 188 } 189 190 @Override 191 public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 192 String text = (String) source; 193 if (!StringUtils.hasText(text)) { 194 return null; 195 } 196 Object result; 197 try { 198 result = this.parser.parse(text, LocaleContextHolder.getLocale()); 199 } 200 catch (IllegalArgumentException ex) { 201 throw ex; 202 } 203 catch (Throwable ex) { 204 throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex); 205 } 206 if (result == null) { 207 throw new IllegalStateException("Parsers are not allowed to return null: " + this.parser); 208 } 209 TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass()); 210 if (!resultType.isAssignableTo(targetType)) { 211 result = this.conversionService.convert(result, resultType, targetType); 212 } 213 return result; 214 } 215 216 @Override 217 public String toString() { 218 return (String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser); 219 } 220 } 221 222 223 private class AnnotationPrinterConverter implements ConditionalGenericConverter { 224 225 private final Class<? extends Annotation> annotationType; 226 227 @SuppressWarnings("rawtypes") 228 private final AnnotationFormatterFactory annotationFormatterFactory; 229 230 private final Class<?> fieldType; 231 232 public AnnotationPrinterConverter(Class<? extends Annotation> annotationType, 233 AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) { 234 235 this.annotationType = annotationType; 236 this.annotationFormatterFactory = annotationFormatterFactory; 237 this.fieldType = fieldType; 238 } 239 240 @Override 241 public Set<ConvertiblePair> getConvertibleTypes() { 242 return Collections.singleton(new ConvertiblePair(this.fieldType, String.class)); 243 } 244 245 @Override 246 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { 247 return sourceType.hasAnnotation(this.annotationType); 248 } 249 250 @Override 251 @SuppressWarnings("unchecked") 252 public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 253 Annotation ann = sourceType.getAnnotation(this.annotationType); 254 if (ann == null) { 255 throw new IllegalStateException( 256 "Expected [" + this.annotationType.getName() + "] to be present on " + sourceType); 257 } 258 AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, sourceType.getObjectType()); 259 GenericConverter converter = cachedPrinters.get(converterKey); 260 if (converter == null) { 261 Printer<?> printer = this.annotationFormatterFactory.getPrinter( 262 converterKey.getAnnotation(), converterKey.getFieldType()); 263 converter = new PrinterConverter(this.fieldType, printer, FormattingConversionService.this); 264 cachedPrinters.put(converterKey, converter); 265 } 266 return converter.convert(source, sourceType, targetType); 267 } 268 269 @Override 270 public String toString() { 271 return ("@" + this.annotationType.getName() + " " + this.fieldType.getName() + " -> " + 272 String.class.getName() + ": " + this.annotationFormatterFactory); 273 } 274 } 275 276 277 private class AnnotationParserConverter implements ConditionalGenericConverter { 278 279 private final Class<? extends Annotation> annotationType; 280 281 @SuppressWarnings("rawtypes") 282 private final AnnotationFormatterFactory annotationFormatterFactory; 283 284 private final Class<?> fieldType; 285 286 public AnnotationParserConverter(Class<? extends Annotation> annotationType, 287 AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) { 288 289 this.annotationType = annotationType; 290 this.annotationFormatterFactory = annotationFormatterFactory; 291 this.fieldType = fieldType; 292 } 293 294 @Override 295 public Set<ConvertiblePair> getConvertibleTypes() { 296 return Collections.singleton(new ConvertiblePair(String.class, fieldType)); 297 } 298 299 @Override 300 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { 301 return targetType.hasAnnotation(this.annotationType); 302 } 303 304 @Override 305 @SuppressWarnings("unchecked") 306 public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 307 Annotation ann = targetType.getAnnotation(this.annotationType); 308 if (ann == null) { 309 throw new IllegalStateException( 310 "Expected [" + this.annotationType.getName() + "] to be present on " + targetType); 311 } 312 AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType()); 313 GenericConverter converter = cachedParsers.get(converterKey); 314 if (converter == null) { 315 Parser<?> parser = this.annotationFormatterFactory.getParser( 316 converterKey.getAnnotation(), converterKey.getFieldType()); 317 converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this); 318 cachedParsers.put(converterKey, converter); 319 } 320 return converter.convert(source, sourceType, targetType); 321 } 322 323 @Override 324 public String toString() { 325 return (String.class.getName() + " -> @" + this.annotationType.getName() + " " + 326 this.fieldType.getName() + ": " + this.annotationFormatterFactory); 327 } 328 } 329 330 331 private static class AnnotationConverterKey { 332 333 private final Annotation annotation; 334 335 private final Class<?> fieldType; 336 337 public AnnotationConverterKey(Annotation annotation, Class<?> fieldType) { 338 this.annotation = annotation; 339 this.fieldType = fieldType; 340 } 341 342 public Annotation getAnnotation() { 343 return this.annotation; 344 } 345 346 public Class<?> getFieldType() { 347 return this.fieldType; 348 } 349 350 @Override 351 public boolean equals(Object other) { 352 if (this == other) { 353 return true; 354 } 355 AnnotationConverterKey otherKey = (AnnotationConverterKey) other; 356 return (this.fieldType == otherKey.fieldType && this.annotation.equals(otherKey.annotation)); 357 } 358 359 @Override 360 public int hashCode() { 361 return (this.fieldType.hashCode() * 29 + this.annotation.hashCode()); 362 } 363 } 364 365}