SPR-7912 Introduce FormatterRegistrar interface and FormattingConversionServiceFactoryBean enhancements.
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3927 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
228a10ccdc
commit
27df774e38
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2011 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.format;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers {@link Converter Converters} and {@link Formatter Formatters} with
|
||||||
|
* a FormattingConversionService through the {@link FormatterRegistry} SPI.
|
||||||
|
*
|
||||||
|
* @author Keith Donald
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public interface FormatterRegistrar {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register Formatters and Converters with a FormattingConversionService
|
||||||
|
* through a FormatterRegistry SPI.
|
||||||
|
* @param registry the FormatterRegistry instance to use.
|
||||||
|
*/
|
||||||
|
void registerFormatters(FormatterRegistry registry);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2009 the original author or authors.
|
* Copyright 2002-2011 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.format.datetime.joda;
|
package org.springframework.format.datetime.joda;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
@ -27,7 +26,7 @@ import org.joda.time.ReadableInstant;
|
||||||
import org.joda.time.format.DateTimeFormat;
|
import org.joda.time.format.DateTimeFormat;
|
||||||
import org.joda.time.format.DateTimeFormatter;
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
import org.joda.time.format.ISODateTimeFormat;
|
import org.joda.time.format.ISODateTimeFormat;
|
||||||
|
import org.springframework.format.FormatterRegistrar;
|
||||||
import org.springframework.format.FormatterRegistry;
|
import org.springframework.format.FormatterRegistry;
|
||||||
import org.springframework.format.Parser;
|
import org.springframework.format.Parser;
|
||||||
import org.springframework.format.Printer;
|
import org.springframework.format.Printer;
|
||||||
|
|
@ -37,14 +36,14 @@ import org.springframework.format.Printer;
|
||||||
*
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 3.0
|
* @since 3.1
|
||||||
* @see #setDateStyle
|
* @see #setDateStyle
|
||||||
* @see #setTimeStyle
|
* @see #setTimeStyle
|
||||||
* @see #setDateTimeStyle
|
* @see #setDateTimeStyle
|
||||||
* @see #setUseIsoFormat
|
* @see #setUseIsoFormat
|
||||||
* @see #installJodaTimeFormatting
|
* @see #installJodaTimeFormatting
|
||||||
*/
|
*/
|
||||||
public class JodaTimeFormattingConfigurer {
|
public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
|
||||||
|
|
||||||
private String dateStyle;
|
private String dateStyle;
|
||||||
|
|
||||||
|
|
@ -54,7 +53,6 @@ public class JodaTimeFormattingConfigurer {
|
||||||
|
|
||||||
private boolean useIsoFormat;
|
private boolean useIsoFormat;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the default format style of Joda {@link LocalDate} objects.
|
* Set the default format style of Joda {@link LocalDate} objects.
|
||||||
* Default is {@link DateTimeFormat#shortDate()}.
|
* Default is {@link DateTimeFormat#shortDate()}.
|
||||||
|
|
@ -89,33 +87,28 @@ public class JodaTimeFormattingConfigurer {
|
||||||
this.useIsoFormat = useIsoFormat;
|
this.useIsoFormat = useIsoFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void registerFormatters(FormatterRegistry registry) {
|
||||||
/**
|
JodaTimeConverters.registerConverters(registry);
|
||||||
* Install Joda Time formatters given the current configuration of this {@link JodaTimeFormattingConfigurer}.
|
|
||||||
*/
|
|
||||||
public void installJodaTimeFormatting(FormatterRegistry formatterRegistry) {
|
|
||||||
JodaTimeConverters.registerConverters(formatterRegistry);
|
|
||||||
|
|
||||||
DateTimeFormatter jodaDateFormatter = getJodaDateFormatter();
|
DateTimeFormatter jodaDateFormatter = getJodaDateFormatter();
|
||||||
formatterRegistry.addFormatterForFieldType(LocalDate.class,
|
registry.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(jodaDateFormatter),
|
||||||
new ReadablePartialPrinter(jodaDateFormatter), new DateTimeParser(jodaDateFormatter));
|
new DateTimeParser(jodaDateFormatter));
|
||||||
|
|
||||||
DateTimeFormatter jodaTimeFormatter = getJodaTimeFormatter();
|
DateTimeFormatter jodaTimeFormatter = getJodaTimeFormatter();
|
||||||
formatterRegistry.addFormatterForFieldType(LocalTime.class,
|
registry.addFormatterForFieldType(LocalTime.class, new ReadablePartialPrinter(jodaTimeFormatter),
|
||||||
new ReadablePartialPrinter(jodaTimeFormatter), new DateTimeParser(jodaTimeFormatter));
|
new DateTimeParser(jodaTimeFormatter));
|
||||||
|
|
||||||
DateTimeFormatter jodaDateTimeFormatter = getJodaDateTimeFormatter();
|
DateTimeFormatter jodaDateTimeFormatter = getJodaDateTimeFormatter();
|
||||||
Parser<DateTime> dateTimeParser = new DateTimeParser(jodaDateTimeFormatter);
|
Parser<DateTime> dateTimeParser = new DateTimeParser(jodaDateTimeFormatter);
|
||||||
formatterRegistry.addFormatterForFieldType(LocalDateTime.class,
|
registry.addFormatterForFieldType(LocalDateTime.class, new ReadablePartialPrinter(jodaDateTimeFormatter),
|
||||||
new ReadablePartialPrinter(jodaDateTimeFormatter), dateTimeParser);
|
dateTimeParser);
|
||||||
|
|
||||||
Printer<ReadableInstant> readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter);
|
Printer<ReadableInstant> readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter);
|
||||||
formatterRegistry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser);
|
registry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser);
|
||||||
|
|
||||||
formatterRegistry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
|
registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// internal helpers
|
// internal helpers
|
||||||
|
|
||||||
private DateTimeFormatter getJodaDateFormatter() {
|
private DateTimeFormatter getJodaDateFormatter() {
|
||||||
|
|
@ -125,8 +118,7 @@ public class JodaTimeFormattingConfigurer {
|
||||||
if (this.dateStyle != null) {
|
if (this.dateStyle != null) {
|
||||||
return DateTimeFormat.forStyle(this.dateStyle + "-");
|
return DateTimeFormat.forStyle(this.dateStyle + "-");
|
||||||
|
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return DateTimeFormat.shortDate();
|
return DateTimeFormat.shortDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,8 +129,7 @@ public class JodaTimeFormattingConfigurer {
|
||||||
}
|
}
|
||||||
if (this.timeStyle != null) {
|
if (this.timeStyle != null) {
|
||||||
return DateTimeFormat.forStyle("-" + this.timeStyle);
|
return DateTimeFormat.forStyle("-" + this.timeStyle);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return DateTimeFormat.shortTime();
|
return DateTimeFormat.shortTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,8 +140,7 @@ public class JodaTimeFormattingConfigurer {
|
||||||
}
|
}
|
||||||
if (this.dateTimeStyle != null) {
|
if (this.dateTimeStyle != null) {
|
||||||
return DateTimeFormat.forStyle(this.dateTimeStyle);
|
return DateTimeFormat.forStyle(this.dateTimeStyle);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return DateTimeFormat.shortDateTime();
|
return DateTimeFormat.shortDateTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,22 +28,34 @@ import org.springframework.context.EmbeddedValueResolverAware;
|
||||||
import org.springframework.core.convert.support.ConversionServiceFactory;
|
import org.springframework.core.convert.support.ConversionServiceFactory;
|
||||||
import org.springframework.format.AnnotationFormatterFactory;
|
import org.springframework.format.AnnotationFormatterFactory;
|
||||||
import org.springframework.format.Formatter;
|
import org.springframework.format.Formatter;
|
||||||
|
import org.springframework.format.FormatterRegistrar;
|
||||||
import org.springframework.format.FormatterRegistry;
|
import org.springframework.format.FormatterRegistry;
|
||||||
import org.springframework.format.Parser;
|
import org.springframework.format.Parser;
|
||||||
import org.springframework.format.Printer;
|
import org.springframework.format.Printer;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.format.datetime.joda.JodaTimeFormattingConfigurer;
|
import org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar;
|
||||||
import org.springframework.format.number.NumberFormatAnnotationFormatterFactory;
|
import org.springframework.format.number.NumberFormatAnnotationFormatterFactory;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.StringValueResolver;
|
import org.springframework.util.StringValueResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for a {@link FormattingConversionService} that installs default
|
* <p>A factory for a {@link FormattingConversionService} that installs default
|
||||||
* and custom converters and formatters for common types such as numbers
|
* converters and formatters for common types such as numbers and datetimes.
|
||||||
* and datetimes .
|
*
|
||||||
|
* <p>Converters and formatters can be registered declaratively through
|
||||||
|
* {@link #setConverters(Set)} and {@link #setFormatters(Set)}. Another option
|
||||||
|
* is to register converters and formatters in code by implementing the
|
||||||
|
* {@link FormatterRegistrar} interface. You can then configure provide the set
|
||||||
|
* of registrars to use through {@link #setFormatterRegistrars(Set)}.
|
||||||
|
*
|
||||||
|
* <p>A good example for registering converters and formatters in code is
|
||||||
|
* <code>JodaTimeFormatterRegistrar</code>, which registers a number of
|
||||||
|
* date-related formatters and converters. For a more detailed list of cases
|
||||||
|
* see {@link #setFormatterRegistrars(Set)}
|
||||||
*
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class FormattingConversionServiceFactoryBean
|
public class FormattingConversionServiceFactoryBean
|
||||||
|
|
@ -56,15 +68,19 @@ public class FormattingConversionServiceFactoryBean
|
||||||
|
|
||||||
private Set<?> formatters;
|
private Set<?> formatters;
|
||||||
|
|
||||||
|
private Set<FormatterRegistrar> formatterRegistrars;
|
||||||
|
|
||||||
private StringValueResolver embeddedValueResolver;
|
private StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
private FormattingConversionService conversionService;
|
private FormattingConversionService conversionService;
|
||||||
|
|
||||||
|
private boolean registerDefaultFormatters = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the set of custom converter objects that should be added.
|
* Configure the set of custom converter objects that should be added.
|
||||||
* @param converters instances of
|
* @param converters instances of any of the following:
|
||||||
* {@link org.springframework.core.convert.converter.Converter},
|
* {@link org.springframework.core.convert.converter.Converter},
|
||||||
* {@link org.springframework.core.convert.converter.ConverterFactory} or
|
* {@link org.springframework.core.convert.converter.ConverterFactory},
|
||||||
* {@link org.springframework.core.convert.converter.GenericConverter}.
|
* {@link org.springframework.core.convert.converter.GenericConverter}.
|
||||||
*/
|
*/
|
||||||
public void setConverters(Set<?> converters) {
|
public void setConverters(Set<?> converters) {
|
||||||
|
|
@ -73,22 +89,53 @@ public class FormattingConversionServiceFactoryBean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the set of custom formatter objects that should be added.
|
* Configure the set of custom formatter objects that should be added.
|
||||||
* @param formatters instances of {@link Formatter} or {@link AnnotationFormatterFactory}.
|
* @param formatters instances of {@link Formatter} or
|
||||||
|
* {@link AnnotationFormatterFactory}.
|
||||||
*/
|
*/
|
||||||
public void setFormatters(Set<?> formatters) {
|
public void setFormatters(Set<?> formatters) {
|
||||||
this.formatters = formatters;
|
this.formatters = formatters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Configure the set of FormatterRegistrars to invoke to register
|
||||||
|
* Converters and Formatters in addition to those added declaratively
|
||||||
|
* via {@link #setConverters(Set)} and {@link #setFormatters(Set)}.
|
||||||
|
* <p>FormatterRegistrars are useful when registering multiple related
|
||||||
|
* converters and formatters for a formatting category, such as Date
|
||||||
|
* formatting. All types related needed to support the formatting
|
||||||
|
* category can be registered from one place.
|
||||||
|
* <p>FormatterRegistrars can also be used to register Formatters
|
||||||
|
* indexed under a specific field type different from its own <T>,
|
||||||
|
* or when registering a Formatter from a Printer/Parser pair.
|
||||||
|
* @see FormatterRegistry#addFormatterForFieldType(Class, Formatter)
|
||||||
|
* @see FormatterRegistry#addFormatterForFieldType(Class, Printer, Parser)
|
||||||
|
*/
|
||||||
|
public void setFormatterRegistrars(Set<FormatterRegistrar> formatterRegistrars) {
|
||||||
|
this.formatterRegistrars = formatterRegistrars;
|
||||||
|
}
|
||||||
|
|
||||||
public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
|
public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
|
||||||
this.embeddedValueResolver = embeddedValueResolver;
|
this.embeddedValueResolver = embeddedValueResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether default formatters should be registered or not. By
|
||||||
|
* default built-in formatters are registered. This flag can be used to
|
||||||
|
* turn that off and rely on explicitly registered formatters only.
|
||||||
|
* @see #setFormatters(Set)
|
||||||
|
* @see #setFormatterRegistrars(Set)
|
||||||
|
*/
|
||||||
|
public void setRegisterDefaultFormatters(boolean registerDefaultFormatters) {
|
||||||
|
this.registerDefaultFormatters = registerDefaultFormatters;
|
||||||
|
}
|
||||||
|
|
||||||
public void afterPropertiesSet() {
|
public void afterPropertiesSet() {
|
||||||
this.conversionService = new FormattingConversionService();
|
this.conversionService = new FormattingConversionService();
|
||||||
this.conversionService.setEmbeddedValueResolver(this.embeddedValueResolver);
|
this.conversionService.setEmbeddedValueResolver(this.embeddedValueResolver);
|
||||||
ConversionServiceFactory.addDefaultConverters(this.conversionService);
|
ConversionServiceFactory.addDefaultConverters(this.conversionService);
|
||||||
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
|
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
|
||||||
installFormatters(this.conversionService);
|
addDefaultFormatters();
|
||||||
|
registerFormatters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -110,17 +157,30 @@ public class FormattingConversionServiceFactoryBean
|
||||||
// subclassing hooks
|
// subclassing hooks
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install Formatters and Converters into the new FormattingConversionService using the FormatterRegistry SPI.
|
* Subclasses may override this method to register formatters and/or converters.
|
||||||
* Subclasses may override to customize the set of formatters and/or converters that are installed.
|
* Starting with Spring 3.1 however the recommended way of doing that is to
|
||||||
|
* through FormatterRegistrars.
|
||||||
|
* @see #setFormatters(Set)
|
||||||
|
* @see #setFormatterRegistrars(Set)
|
||||||
*/
|
*/
|
||||||
protected void installFormatters(FormatterRegistry registry) {
|
protected void installFormatters(FormatterRegistry registry) {
|
||||||
registry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
|
}
|
||||||
if (jodaTimePresent) {
|
|
||||||
new JodaTimeFormattingConfigurer().installJodaTimeFormatting(registry);
|
// private helper methods
|
||||||
}
|
|
||||||
else {
|
private void addDefaultFormatters() {
|
||||||
registry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory());
|
if (registerDefaultFormatters) {
|
||||||
|
this.conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
|
||||||
|
if (jodaTimePresent) {
|
||||||
|
new JodaTimeFormatterRegistrar().registerFormatters(this.conversionService);
|
||||||
|
} else {
|
||||||
|
this.conversionService
|
||||||
|
.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerFormatters() {
|
||||||
if (this.formatters != null) {
|
if (this.formatters != null) {
|
||||||
for (Object formatter : this.formatters) {
|
for (Object formatter : this.formatters) {
|
||||||
if (formatter instanceof Formatter<?>) {
|
if (formatter instanceof Formatter<?>) {
|
||||||
|
|
@ -133,9 +193,14 @@ public class FormattingConversionServiceFactoryBean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.formatterRegistrars != null) {
|
||||||
|
for (FormatterRegistrar registrar : this.formatterRegistrars) {
|
||||||
|
registrar.registerFormatters(this.conversionService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installFormatters(this.conversionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used
|
* Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used
|
||||||
* without the JodaTime library being present.
|
* without the JodaTime library being present.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.format.datetime.joda;
|
package org.springframework.format.datetime.joda;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
@ -28,10 +30,8 @@ import org.joda.time.LocalDate;
|
||||||
import org.joda.time.LocalDateTime;
|
import org.joda.time.LocalDateTime;
|
||||||
import org.joda.time.LocalTime;
|
import org.joda.time.LocalTime;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.beans.MutablePropertyValues;
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
import org.springframework.core.convert.support.ConversionServiceFactory;
|
import org.springframework.core.convert.support.ConversionServiceFactory;
|
||||||
|
|
@ -54,8 +54,8 @@ public class JodaTimeFormattingTests {
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
ConversionServiceFactory.addDefaultConverters(conversionService);
|
ConversionServiceFactory.addDefaultConverters(conversionService);
|
||||||
|
|
||||||
JodaTimeFormattingConfigurer configurer = new JodaTimeFormattingConfigurer();
|
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
|
||||||
configurer.installJodaTimeFormatting(conversionService);
|
registrar.registerFormatters(conversionService);
|
||||||
|
|
||||||
JodaTimeBean bean = new JodaTimeBean();
|
JodaTimeBean bean = new JodaTimeBean();
|
||||||
bean.getChildren().add(new JodaTimeBean());
|
bean.getChildren().add(new JodaTimeBean());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue