From a71aeeb4d12650ee82302b01cde54b891a44ef6b Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Thu, 18 Jul 2019 14:49:32 +0200 Subject: [PATCH] #1019 - Further optimizations in case not I18N is enabled. Introduced MessageResolver interface and moved all components previously referring to MessageSourceResolvable to it. This allows us to directly shortcut message resolution by providing a no-op instance in case our default resource bundle rest-messages does not exist and thus avoid the overhead of attempted message resolutions in that case. --- .../hateoas/config/HateoasConfiguration.java | 47 +++++++------ .../hateoas/mediatype/MessageResolver.java | 51 ++++++++++++++ .../mediatype/MessageSourceResolver.java | 58 ++++++++++++++++ .../hateoas/mediatype/NoMessageResolver.java | 34 ++++++++++ .../hal/HalMediaTypeConfiguration.java | 14 ++-- .../mediatype/hal/Jackson2HalModule.java | 68 +++++++------------ .../forms/HalFormsMediaTypeConfiguration.java | 6 +- .../hal/forms/HalFormsTemplateBuilder.java | 16 +++-- .../hal/forms/Jackson2HalFormsModule.java | 9 ++- .../hateoas/client/Server.java | 32 ++++----- .../hal/Jackson2HalIntegrationTest.java | 25 +++---- .../HalFormsMessageConverterUnitTest.java | 9 ++- .../HalFormsTemplateBuilderUnitTest.java | 8 +-- .../Jackson2HalFormsIntegrationTest.java | 23 ++++--- .../vnderror/VndErrorsMarshallingTest.java | 8 +-- 15 files changed, 263 insertions(+), 145 deletions(-) create mode 100644 src/main/java/org/springframework/hateoas/mediatype/MessageResolver.java create mode 100644 src/main/java/org/springframework/hateoas/mediatype/MessageSourceResolver.java create mode 100644 src/main/java/org/springframework/hateoas/mediatype/NoMessageResolver.java diff --git a/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java index 5ef19cc8..a609e1d6 100644 --- a/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java @@ -15,15 +15,17 @@ */ package org.springframework.hateoas.config; -import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; -import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.context.support.AbstractResourceBasedMessageSource; import org.springframework.context.support.ReloadableResourceBundleMessageSource; +import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.client.LinkDiscoverer; import org.springframework.hateoas.client.LinkDiscoverers; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.LinkRelationProvider.LookupContext; import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider; @@ -31,6 +33,7 @@ import org.springframework.hateoas.server.core.DefaultLinkRelationProvider; import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider; import org.springframework.hateoas.server.core.EvoInflectorLinkRelationProvider; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.plugin.core.PluginRegistry; import org.springframework.plugin.core.config.EnablePluginRegistries; import org.springframework.plugin.core.support.PluginRegistryFactoryBean; @@ -49,24 +52,9 @@ import org.springframework.util.ClassUtils; @EnablePluginRegistries({ LinkDiscoverer.class }) class HateoasConfiguration { - /** - * The {@link MessageSourceAccessor} to provide messages for {@link ResourceDescription}s being rendered. - * - * @return - */ @Bean - public MessageSourceAccessor linkRelationMessageSource() { - - try { - - ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); - messageSource.setBasename("classpath:rest-messages"); - - return new MessageSourceAccessor(messageSource); - - } catch (Exception o_O) { - throw new BeanCreationException("resourceDescriptionMessageSourceAccessor", "", o_O); - } + public MessageResolver messageResolver() { + return MessageResolver.of(lookupMessageSource()); } // RelProvider @@ -108,4 +96,25 @@ class HateoasConfiguration { LinkDiscoverers linkDiscoverers(PluginRegistry discoverers) { return new LinkDiscoverers(discoverers); } + + /** + * Creates a message source for the {@code rest-messages} resource bundle if the file exists or a + * {@link NoOpMessageSource} otherwise. + * + * @return will never be {@literal null}. + */ + @Nullable + private static final MessageSource lookupMessageSource() { + + ClassPathResource resource = new ClassPathResource("rest-messages"); + + if (!resource.exists()) { + return null; + } + + AbstractResourceBasedMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + messageSource.setBasename("classpath:rest-messages"); + + return messageSource; + } } diff --git a/src/main/java/org/springframework/hateoas/mediatype/MessageResolver.java b/src/main/java/org/springframework/hateoas/mediatype/MessageResolver.java new file mode 100644 index 00000000..e0fd6a26 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mediatype/MessageResolver.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019 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 + * + * https://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.hateoas.mediatype; + +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.lang.Nullable; + +/** + * A simplified variant of {@link MessageSourceAccessor} to allow more direct replacement with a no-op implementation in + * case the target {@link MessageSource} is unavailable to avoid resolution overhead. + * + * @author Oliver Drotbohm + */ +public interface MessageResolver { + + public static final MessageResolver NONE = NoMessageResolver.INSTANCE; + + /** + * Resolve the given {@link MessageSourceResolvable}. Return {@literal null} if no message was found. + * + * @param resolvable must not be {@literal null}. + * @return + */ + @Nullable + String resolve(MessageSourceResolvable resolvable); + + /** + * Obtains a {@link MessageResolver} for the given {@link MessageSource}. + * + * @param messageSource can be {@literal null}. + * @return will never be {@literal null}. + */ + public static MessageResolver of(@Nullable MessageSource messageSource) { + return messageSource == null ? NoMessageResolver.INSTANCE : new MessageSourceResolver(messageSource); + } +} diff --git a/src/main/java/org/springframework/hateoas/mediatype/MessageSourceResolver.java b/src/main/java/org/springframework/hateoas/mediatype/MessageSourceResolver.java new file mode 100644 index 00000000..726151aa --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mediatype/MessageSourceResolver.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 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 + * + * https://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.hateoas.mediatype; + +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A {@link MessageResolver} based on a {@link MessageSource}. + * + * @author Oliver Drotbohm + */ +class MessageSourceResolver implements MessageResolver { + + private final MessageSourceAccessor accessor; + + /** + * Creates a new {@link MessageSourceResolver} for the given {@link MessageSource}. + * + * @param messageSource must not be {@literal null}. + */ + MessageSourceResolver(MessageSource messageSource) { + + Assert.notNull(messageSource, "MessageSource must not be null!"); + + this.accessor = new MessageSourceAccessor(messageSource); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.mediatype.MessageResolver#resolve(org.springframework.context.MessageSourceResolvable) + */ + @Nullable + @Override + public String resolve(MessageSourceResolvable resolvable) { + + String resolved = accessor.getMessage(resolvable); + + return StringUtils.hasText(resolved) ? resolved : null; + } +} diff --git a/src/main/java/org/springframework/hateoas/mediatype/NoMessageResolver.java b/src/main/java/org/springframework/hateoas/mediatype/NoMessageResolver.java new file mode 100644 index 00000000..4d34d5bb --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mediatype/NoMessageResolver.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 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 + * + * https://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.hateoas.mediatype; + +import org.springframework.context.MessageSourceResolvable; +import org.springframework.lang.Nullable; + +enum NoMessageResolver implements MessageResolver { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.mediatype.MessageResolver#resolve(org.springframework.context.MessageSourceResolvable) + */ + @Nullable + @Override + public String resolve(MessageSourceResolvable resolvable) { + return null; + } +} diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java index 8b3ee4a6..a1b2ce61 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java @@ -18,13 +18,12 @@ package org.springframework.hateoas.mediatype.hal; import java.util.List; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.client.LinkDiscoverer; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; import org.springframework.hateoas.config.HypermediaMappingInformation; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.http.MediaType; @@ -43,22 +42,21 @@ public class HalMediaTypeConfiguration implements HypermediaMappingInformation { private final LinkRelationProvider relProvider; private final ObjectProvider curieProvider; private final ObjectProvider halConfiguration; - private final MessageSourceAccessor messageSourceAccessor; + private final MessageResolver resolver; /** * @param relProvider * @param curieProvider * @param halConfiguration - * @param messageSourceAccessor + * @param resolver */ public HalMediaTypeConfiguration(LinkRelationProvider relProvider, ObjectProvider curieProvider, - ObjectProvider halConfiguration, - @Qualifier("linkRelationMessageSource") MessageSourceAccessor messageSourceAccessor) { + ObjectProvider halConfiguration, MessageResolver resolver) { this.relProvider = relProvider; this.curieProvider = curieProvider; this.halConfiguration = halConfiguration; - this.messageSourceAccessor = messageSourceAccessor; + this.resolver = resolver; } @Bean @@ -85,7 +83,7 @@ public class HalMediaTypeConfiguration implements HypermediaMappingInformation { mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerModule(new Jackson2HalModule()); mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, - curieProvider.getIfAvailable(() -> CurieProvider.NONE), messageSourceAccessor, + curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, halConfiguration.getIfAvailable(HalConfiguration::new))); return mapper; diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java index 7a26649d..0b5d46a8 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java @@ -27,14 +27,13 @@ import java.util.Map; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.context.NoSuchMessageException; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLinks; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.lang.NonNull; @@ -113,23 +112,23 @@ public class Jackson2HalModule extends SimpleModule { private final @Nullable BeanProperty property; private final CurieProvider curieProvider; private final EmbeddedMapper mapper; - private final MessageSourceAccessor accessor; + private final MessageResolver resolver; private final HalConfiguration halConfiguration; - public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, MessageSourceAccessor accessor, + public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, MessageResolver resolver, HalConfiguration halConfiguration) { - this(null, curieProvider, mapper, accessor, halConfiguration); + this(null, curieProvider, mapper, resolver, halConfiguration); } public HalLinkListSerializer(@Nullable BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper, - MessageSourceAccessor accessor, HalConfiguration halConfiguration) { + MessageResolver accessor, HalConfiguration halConfiguration) { super(TypeFactory.defaultInstance().constructType(Links.class)); this.property = property; this.curieProvider = curieProvider; this.mapper = mapper; - this.accessor = accessor; + this.resolver = accessor; this.halConfiguration = halConfiguration; } @@ -206,26 +205,7 @@ public class Jackson2HalModule extends SimpleModule { HalLinkRelation rel = HalLinkRelation.of(link.getRel()); - return new HalLink(link, getTitle(rel)); - } - - /** - * Returns the title for the given local link relation resolved through the configured {@link MessageSourceAccessor} - * . - * - * @param relation must not be {@literal null} or empty. - * @return - */ - @Nullable - private String getTitle(HalLinkRelation relation) { - - Assert.notNull(relation, "Local relation must not be null or empty!"); - - try { - return accessor == null ? null : accessor.getMessage(relation); - } catch (NoSuchMessageException o_O) { - return null; - } + return new HalLink(link, resolver.resolve(rel)); } /* @@ -236,7 +216,7 @@ public class Jackson2HalModule extends SimpleModule { @SuppressWarnings("null") public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { - return new HalLinkListSerializer(property, curieProvider, mapper, accessor, halConfiguration); + return new HalLinkListSerializer(property, curieProvider, mapper, resolver, halConfiguration); } /* @@ -719,43 +699,43 @@ public class Jackson2HalModule extends SimpleModule { private final @Nullable AutowireCapableBeanFactory delegate; public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, - MessageSourceAccessor messageSourceAccessor) { - this(provider, curieProvider, messageSourceAccessor, new HalConfiguration()); + MessageResolver resolver) { + this(provider, curieProvider, resolver, new HalConfiguration()); } /** * Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider} - * and {@link MessageSourceAccessor}. Registers a prepared {@link HalResourcesSerializer} and + * and {@link MessageResolver}. Registers a prepared {@link HalResourcesSerializer} and * {@link HalLinkListSerializer} falling back to instantiation expecting a default constructor. * * @param provider must not be {@literal null}. * @param curieProvider can be {@literal null}. - * @param messageSourceAccessor can be {@literal null}. + * @param resolver must not be {@literal null}. */ - public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, - MessageSourceAccessor messageSourceAccessor, HalConfiguration halConfiguration) { - this(provider, curieProvider, messageSourceAccessor, true, halConfiguration); + public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver, + HalConfiguration halConfiguration) { + this(provider, curieProvider, resolver, true, halConfiguration); } /** * Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider} - * and {@link MessageSourceAccessor} and whether to enforce embedded collections. Registers a prepared + * and {@link MessageResolver} and whether to enforce embedded collections. Registers a prepared * {@link HalResourcesSerializer} and {@link HalLinkListSerializer} falling back to instantiation expecting a * default constructor. * * @param provider must not be {@literal null}. * @param curieProvider can be {@literal null} - * @param accessor can be {@literal null}. + * @param resolver must not be {@literal null}.. * @param enforceEmbeddedCollections */ - public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, - MessageSourceAccessor accessor, boolean enforceEmbeddedCollections, HalConfiguration halConfiguration) { - this(provider, curieProvider, accessor, enforceEmbeddedCollections, null, halConfiguration); + public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver, + boolean enforceEmbeddedCollections, HalConfiguration halConfiguration) { + this(provider, curieProvider, resolver, enforceEmbeddedCollections, null, halConfiguration); } - private HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, - MessageSourceAccessor accessor, boolean enforceEmbeddedCollections, - @Nullable AutowireCapableBeanFactory delegate, HalConfiguration halConfiguration) { + private HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver, + boolean enforceEmbeddedCollections, @Nullable AutowireCapableBeanFactory delegate, + HalConfiguration halConfiguration) { Assert.notNull(provider, "RelProvider must not be null!"); Assert.notNull(curieProvider, "CurieProvider must not be null!"); @@ -766,7 +746,7 @@ public class Jackson2HalModule extends SimpleModule { this.serializers.put(HalResourcesSerializer.class, new HalResourcesSerializer(mapper)); this.serializers.put(HalLinkListSerializer.class, - new HalLinkListSerializer(curieProvider, mapper, accessor, halConfiguration)); + new HalLinkListSerializer(curieProvider, mapper, resolver, halConfiguration)); } /* diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java index dc468a28..7aa41bc6 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java @@ -22,10 +22,10 @@ import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.client.LinkDiscoverer; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; import org.springframework.hateoas.config.HypermediaMappingInformation; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.HalConfiguration; import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider; @@ -48,7 +48,7 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation { private final ObjectProvider curieProvider; private final ObjectProvider halFormsConfiguration; private final ObjectProvider halConfiguration; - private final MessageSourceAccessor messageSourceAccessor; + private final MessageResolver resolver; @Bean LinkDiscoverer halFormsLinkDiscoverer() { @@ -77,7 +77,7 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation { mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider, - curieProvider.getIfAvailable(() -> CurieProvider.NONE), messageSourceAccessor, true, configuration)); + curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, true, configuration)); return mapper; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java index 8abe141f..7f47cc15 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilder.java @@ -27,7 +27,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.springframework.context.MessageSourceResolvable; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.Affordance; import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata; import org.springframework.hateoas.AffordanceModel.PropertyMetadata; @@ -35,17 +34,17 @@ import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.http.HttpMethod; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; @RequiredArgsConstructor class HalFormsTemplateBuilder { private final HalFormsConfiguration configuration; - private final MessageSourceAccessor accessor; + private final MessageResolver resolver; /** * Extract template details from a {@link RepresentationModel}'s {@link Affordance}s. @@ -96,8 +95,7 @@ class HalFormsTemplateBuilder { public HalFormsTemplate applyTo(HalFormsTemplate template, HalFormsTemplateBuilder.TemplateTitle templateTitle) { - return Optional.ofNullable(accessor.getMessage(templateTitle)) // - .filter(StringUtils::hasText) // + return Optional.ofNullable(resolver.resolve(templateTitle)) // .map(template::withTitle) // .orElse(template); } @@ -109,8 +107,12 @@ class HalFormsTemplateBuilder { private HalFormsProperty apply(HalFormsProperty property) { - String message = accessor.getMessage(PropertyPrompt.of(metadata, property)); - HalFormsProperty withPrompt = property.withPrompt(message); + String message = resolver.resolve(PropertyPrompt.of(metadata, property)); + + HalFormsProperty withPrompt = Optional.ofNullable(message) // + .map(it -> property.withPrompt(it)) // + .orElse(property); + HalFormsProperty withConfig = metadata.getPropertyMetadata(withPrompt.getName()) // .flatMap(it -> applyConfig(it, withPrompt)) // .orElse(withPrompt); diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java index 279783d1..c9598a92 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java @@ -21,13 +21,13 @@ import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedModel; import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.EmbeddedMapper; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalHandlerInstantiator; @@ -163,7 +163,7 @@ class Jackson2HalFormsModule extends SimpleModule { private final Map, Object> serializers = new HashMap<>(); public HalFormsHandlerInstantiator(LinkRelationProvider resolver, CurieProvider curieProvider, - MessageSourceAccessor accessor, boolean enforceEmbeddedCollections, HalFormsConfiguration configuration) { + MessageResolver accessor, boolean enforceEmbeddedCollections, HalFormsConfiguration configuration) { super(resolver, curieProvider, accessor, enforceEmbeddedCollections, configuration.getHalConfiguration()); @@ -180,10 +180,9 @@ class Jackson2HalFormsModule extends SimpleModule { } public HalFormsHandlerInstantiator(LinkRelationProvider relProvider, CurieProvider curieProvider, - MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections, - AutowireCapableBeanFactory beanFactory) { + MessageResolver resolver, boolean enforceEmbeddedCollections, AutowireCapableBeanFactory beanFactory) { - this(relProvider, curieProvider, messageSource, enforceEmbeddedCollections, + this(relProvider, curieProvider, resolver, enforceEmbeddedCollections, beanFactory.getBean(HalFormsConfiguration.class)); } diff --git a/src/test/java/org/springframework/hateoas/client/Server.java b/src/test/java/org/springframework/hateoas/client/Server.java index f4ffdb97..460405f8 100644 --- a/src/test/java/org/springframework/hateoas/client/Server.java +++ b/src/test/java/org/springframework/hateoas/client/Server.java @@ -15,10 +15,15 @@ */ package org.springframework.hateoas.client; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.MessageSource; -import org.springframework.context.support.MessageSourceAccessor; +import static net.jadler.Jadler.*; +import static org.hamcrest.Matchers.*; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.UUID; + import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.hateoas.CollectionModel; @@ -26,6 +31,7 @@ import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; import org.springframework.hateoas.server.LinkRelationProvider; @@ -35,18 +41,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; -import java.io.Closeable; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.UUID; - -import static net.jadler.Jadler.closeJadler; -import static net.jadler.Jadler.initJadler; -import static net.jadler.Jadler.onRequest; -import static net.jadler.Jadler.port; -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; /** * Helper class for integration tests. @@ -68,8 +64,8 @@ public class Server implements Closeable { this.mapper = new ObjectMapper(); this.mapper.registerModule(new Jackson2HalModule()); - this.mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, CurieProvider.NONE, - new MessageSourceAccessor(mock(MessageSource.class)))); + this.mapper.setHandlerInstantiator( + new Jackson2HalModule.HalHandlerInstantiator(relProvider, CurieProvider.NONE, MessageResolver.NONE)); initJadler() // .withDefaultResponseContentType(MediaTypes.HAL_JSON.toString()) // diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java index 07cc9813..a89802e7 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/Jackson2HalIntegrationTest.java @@ -16,7 +16,6 @@ package org.springframework.hateoas.mediatype.hal; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import java.io.IOException; import java.util.ArrayList; @@ -34,12 +33,14 @@ import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.StaticMessageSource; import org.springframework.hateoas.*; import org.springframework.hateoas.PagedModel.PageMetadata; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLinks; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalHandlerInstantiator; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider; import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider; import org.springframework.hateoas.server.core.EmbeddedWrappers; +import org.springframework.lang.Nullable; import com.fasterxml.jackson.databind.ObjectMapper; @@ -80,18 +81,15 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final String SINGLE_WITH_ONE_EXTRA_ATTRIBUTES = "{\"_links\":{\"self\":{\"href\":\"localhost\",\"title\":\"the title\"}}}"; static final String SINGLE_WITH_ALL_EXTRA_ATTRIBUTES = "{\"_links\":{\"self\":{\"href\":\"localhost\",\"hreflang\":\"en\",\"title\":\"the title\",\"type\":\"the type\",\"deprecation\":\"/customers/deprecated\"}}}"; - MessageSource messageSource = mock(MessageSource.class); - @BeforeEach void setUpModule() { - MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource); LinkRelationProvider provider = new DelegatingLinkRelationProvider(new AnnotationLinkRelationProvider(), DefaultLinkRelationProvider.INSTANCE); mapper.registerModule(new Jackson2HalModule()); mapper.setHandlerInstantiator( - new HalHandlerInstantiator(provider, CurieProvider.NONE, accessor, new HalConfiguration())); + new HalHandlerInstantiator(provider, CurieProvider.NONE, MessageResolver.NONE, new HalConfiguration())); } /** @@ -410,8 +408,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg } }; - assertThat(getCuriedObjectMapper(provider, messageSource).writeValueAsString(resources)) - .isEqualTo(MULTIPLE_CURIES_DOCUMENT); + assertThat(getCuriedObjectMapper(provider).writeValueAsString(resources)).isEqualTo(MULTIPLE_CURIES_DOCUMENT); } /** @@ -450,8 +447,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg void rendersSingleLinkAsArrayWhenConfigured() throws Exception { mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), CurieProvider.NONE, - new MessageSourceAccessor(messageSource), - new HalConfiguration().withRenderSingleLinks(RenderSingleLinks.AS_ARRAY))); + MessageResolver.NONE, new HalConfiguration().withRenderSingleLinks(RenderSingleLinks.AS_ARRAY))); RepresentationModel resourceSupport = new RepresentationModel<>(); resourceSupport.add(new Link("localhost").withSelfRel()); @@ -483,9 +479,8 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg void rendersSpecificRelWithSingleLinkAsArrayIfConfigured() throws Exception { AnnotationLinkRelationProvider provider = new AnnotationLinkRelationProvider(); - MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource); - mapper.setHandlerInstantiator(new HalHandlerInstantiator(provider, CurieProvider.NONE, accessor, + mapper.setHandlerInstantiator(new HalHandlerInstantiator(provider, CurieProvider.NONE, MessageResolver.NONE, new HalConfiguration().withRenderSingleLinksFor("foo", RenderSingleLinks.AS_ARRAY))); RepresentationModel resource = new RepresentationModel<>(); @@ -563,15 +558,15 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg } private ObjectMapper getCuriedObjectMapper(CurieProvider provider) { - return getCuriedObjectMapper(provider, messageSource); + return getCuriedObjectMapper(provider, null); } - private ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) { + private ObjectMapper getCuriedObjectMapper(CurieProvider provider, @Nullable MessageSource messageSource) { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jackson2HalModule()); - mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), provider, - new MessageSourceAccessor(messageSource))); + mapper.setHandlerInstantiator( + new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), provider, MessageResolver.of(messageSource))); return mapper; } diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java index d186c6f1..7f2fca98 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java @@ -16,7 +16,6 @@ package org.springframework.hateoas.mediatype.hal.forms; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -25,12 +24,11 @@ import java.io.OutputStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.context.MessageSource; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider; import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; @@ -58,7 +56,7 @@ class HalFormsMessageConverterUnitTest { this.mapper.registerModule(new Jackson2HalFormsModule()); this.mapper.setHandlerInstantiator( new Jackson2HalFormsModule.HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), CurieProvider.NONE, - new MessageSourceAccessor(mock(MessageSource.class)), true, new HalFormsConfiguration())); + MessageResolver.NONE, true, new HalFormsConfiguration())); TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( RepresentationModel.class); @@ -95,7 +93,8 @@ class HalFormsMessageConverterUnitTest { assertThat(halFormsDocument.getTemplates().keySet()).containsExactly("default"); assertThat(halFormsDocument.getTemplates().get("default").getContentType()).isEqualTo("application/hal+json"); assertThat(halFormsDocument.getTemplates().get("default").getHttpMethod()).isEqualTo(HttpMethod.GET); - assertThat(halFormsDocument.getTemplates().get("default").getMethod()).isEqualTo(HttpMethod.GET.toString().toLowerCase()); + assertThat(halFormsDocument.getTemplates().get("default").getMethod()) + .isEqualTo(HttpMethod.GET.toString().toLowerCase()); } @Test diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java index 9d104e6f..e283bc2c 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java @@ -16,7 +16,6 @@ package org.springframework.hateoas.mediatype.hal.forms; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import lombok.Getter; @@ -28,10 +27,10 @@ import javax.validation.constraints.Pattern; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.Link; import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.mediatype.Affordances; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.http.HttpMethod; /** @@ -46,7 +45,7 @@ public class HalFormsTemplateBuilderUnitTest { HalFormsConfiguration configuration = new HalFormsConfiguration(); configuration.registerPattern(CreditCardNumber.class, "[0-9]{16}"); - HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(configuration, mock(MessageSourceAccessor.class)); + HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(configuration, MessageResolver.NONE); PatternExample resource = new PatternExample(); resource.add(Affordances.of(new Link("/examples")) // @@ -80,8 +79,7 @@ public class HalFormsTemplateBuilderUnitTest { .withName("post") // .toLink()); - HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(new HalFormsConfiguration(), - mock(MessageSourceAccessor.class)); + HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(new HalFormsConfiguration(), MessageResolver.NONE); Map templates = builder.findTemplates(model); diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java index 42094558..5757fee7 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java @@ -16,7 +16,6 @@ package org.springframework.hateoas.mediatype.hal.forms; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import lombok.Getter; import net.minidev.json.JSONArray; @@ -38,7 +37,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.StaticMessageSource; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest; @@ -51,6 +49,7 @@ import org.springframework.hateoas.PagedModel; import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.mediatype.Affordances; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider; import org.springframework.hateoas.mediatype.hal.Jackson2HalIntegrationTest; @@ -64,6 +63,7 @@ import org.springframework.hateoas.server.core.EmbeddedWrappers; import org.springframework.hateoas.support.EmployeeResource; import org.springframework.hateoas.support.MappingUtils; import org.springframework.http.HttpMethod; +import org.springframework.lang.Nullable; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.ObjectMapper; @@ -82,7 +82,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra new Link("bar", IanaLinkRelations.PREV) // ); - MessageSource messageSource = mock(MessageSource.class); + // MessageSource messageSource = mock(MessageSource.class); @BeforeEach void setUpModule() { @@ -94,8 +94,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator( // - provider, CurieProvider.NONE, new MessageSourceAccessor(messageSource, Locale.US), true, - new HalFormsConfiguration())); + provider, CurieProvider.NONE, MessageResolver.NONE, true, new HalFormsConfiguration())); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); } @@ -378,7 +377,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra } }; - assertThat(getCuriedObjectMapper(provider, messageSource).writeValueAsString(resources)) + assertThat(getCuriedObjectMapper(provider).writeValueAsString(resources)) .isEqualTo(MappingUtils.read(new ClassPathResource("multiple-curies-document.json", getClass()))); } @@ -587,18 +586,20 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra private ObjectMapper getCuriedObjectMapper() { - return getCuriedObjectMapper(new DefaultCurieProvider("foo", UriTemplate.of("http://localhost:8080/rels/{rel}")), - messageSource); + return getCuriedObjectMapper(new DefaultCurieProvider("foo", UriTemplate.of("http://localhost:8080/rels/{rel}"))); } - private ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) { + private ObjectMapper getCuriedObjectMapper(CurieProvider provider) { + return getCuriedObjectMapper(provider, null); + } + + private ObjectMapper getCuriedObjectMapper(CurieProvider provider, @Nullable MessageSource messageSource) { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), provider, - messageSource == null ? null : new MessageSourceAccessor(messageSource, Locale.US), true, - new HalFormsConfiguration())); + MessageResolver.of(messageSource), true, new HalFormsConfiguration())); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); mapper.setSerializationInclusion(Include.NON_NULL); diff --git a/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java b/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java index f0e31707..0b72d051 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/vnderror/VndErrorsMarshallingTest.java @@ -16,7 +16,6 @@ package org.springframework.hateoas.mediatype.vnderror; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import java.io.FileInputStream; import java.io.IOException; @@ -26,10 +25,9 @@ import java.nio.charset.Charset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.context.MessageSource; -import org.springframework.context.support.MessageSourceAccessor; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.Link; +import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; import org.springframework.hateoas.mediatype.vnderrors.VndErrors; @@ -67,8 +65,8 @@ class VndErrorsMarshallingTest { jackson2Mapper = new com.fasterxml.jackson.databind.ObjectMapper(); jackson2Mapper.registerModule(new Jackson2HalModule()); - jackson2Mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, CurieProvider.NONE, - new MessageSourceAccessor(mock(MessageSource.class)))); + jackson2Mapper.setHandlerInstantiator( + new Jackson2HalModule.HalHandlerInstantiator(relProvider, CurieProvider.NONE, MessageResolver.NONE)); jackson2Mapper.configure(SerializationFeature.INDENT_OUTPUT, true); VndError error = new VndError("42", "Validation failed!", //