Spring - Path variable truncate after dot - annotation - java

I am trying to set up a REST endpoint that allows querying a user by their email address. The email address is the last portion of the path so Spring is treating foo#example.com as the value foo#example and truncating the extension .com.
I found a similar question here Spring MVC #PathVariable with dot (.) is getting truncated
However, I have an annotation based configuration using AbstractAnnotationConfigDispatcherServletInitializer and WebMvcConfigurerAdapter. Since I have no xml configuration, this solution will not work for me:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="useDefaultSuffixPattern" value="false" />
</bean>
I have also tried this solution which uses regex but it has not worked either.
#RequestMapping(value = "user/by-email/{email:.+}")
Does anyone know how to turn off the suffix pattern truncation without xml?

The dot in the path variable at the end of the URI causes two unexpected behaviours (unexpected for the majority of users, except those familiar with the huge number of Spring configuration properties).
The first (which can be fixed using the {email:.+} regex) is that the default Spring configuration matches all path extensions. So setting up a mapping for /api/{file} will mean that Spring maps a call to /api/myfile.html to the String argument myfile. This is useful when you want /api/myfile.html, /api/myfile.md, /api/myfile.txt and others to all point to the same resource. However, we can turn this behaviour off globally, without having to resort to a regex hack on every endpoint.
The second problem is related to the first and correctly fixed by #masstroy. When /api/myfile.* points to the myfile resource, Spring assumes the path extension (.html, .txt, etc.) indicates that the resource should be returned with a specific format. This behaviour can also be very useful in some situations. But often, it will mean that the object returned by a method mapping cannot be converted into this format, and Spring will throw a HttpMediaTypeNotAcceptableException.
We can turn both off with the following (assuming Spring Boot):
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// turn off all suffix pattern matching
configurer.setUseSuffixPatternMatch(false);
// OR
// turn on suffix pattern matching ONLY for suffixes
// you explicitly register using
// configureContentNegotiation(...)
configurer.setUseRegisteredSuffixPatternMatch(true);
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}
More about Content Negotiation.

You have to add trailing slash at the end of the path variable after name like
#RequestMapping(value ="/test/{name}/")
The Request like
http://localhost:8080/utooa/service/api/admin/test/Takeoff.Java#gmail.com/

I've found the solution to this using the ContentNegotiationConfigurer bean from this article: http://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc
I added the following configuration to my WebConfig class:
#EnableWebMvc
#Configuration
#ComponentScan(basePackageClasses = { RestAPIConfig.class })
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
By setting .favorPathExtension(false), Spring will no longer use the file extension to override the accepts mediaType of the request. The Javadoc for that method reads Indicate whether the extension of the request path should be used to determine the requested media type with the highest priority.
Then I set up my #RequestMapping using the regex
#RequestMapping(value = "/user/by-email/{email:.+}")

For the Java-Config folks:
With Spring 4 you can simply turn this feature off by:
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
Then in the whole application dots will treated as dots.

Related

Excluding A URL Pattern From A Wildcard in Spring MVC #RequestMapping

I have a wildcard Spring MVC mapping that goes something like this:
#RequestMapping(value="**", method={RequestMethod.GET, RequestMethod.POST})
public void doSomething() {
}
However, defined in a Spring #Configuration file, I have this:
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(pushHandler(), "/websocket").withSockJS();
}
Unfortunately, Spring MVC's wildcard handler ignores the registration of /websocket, meaning my wildcard handler tries to handle my websocket as well.
I tried to change the wildcard pattern so that the #RequestHandler used a negating regex:
#RequestMapping(value="(?!websocket)", method={RequestMethod.GET, RequestMethod.POST}
public void doSomething() {
}
This now excludes the websocket path, but all other paths that I expect the wildcard to pick up now are no longer mapped.
Does anyone have a workable way to exclude a path from a #RequestMapping wildcard?
Jason

Is it possible to make Spring #Import or #Configuration parametrized?

I've created a lot of common small bean-definition containers (#Configuration) which I use to rapidly develop applications with Spring Boot like:
#Import({
FreemarkerViewResolver.class, // registers freemarker that auto appends <#escape etc.
ConfigurationFromPropertiesFile.class, // loads conf/configuration.properties
UtfContentTypeResponse.class, // sets proper Content-language and Content-type
LocaleResolverWithLanguageSwitchController // Locale resolver + switch controller
);
class MySpringBootApp ...
For example, one of such #Configurations can set up session storage for locale cookie with web controller to switch to selected language etc.
They are very fun to work with and reuse, but it would be really great to make it parametrized, which could allow lot more reusege. I mean something like:
Pseudo code:
#Imports( imports = {
#FreemarkerViewResolver( escapeHtml = true, autoIncludeSpringMacros = true),
#ConfigurationFromProperties( path = "conf/configuration.properties" ),
#ContentTypeResponse( encoding = "UTF-8" ),
#LocaleResolver( switchLocaleUrl = "/locale/{loc}", defaultLocale = "en"
})
So, I basically mean "configurable #Configurations". What would be the best way to make the configuration that way?
Maybe something more like this (again, pseudo code):
#Configuration
public class MyAppConfiguration {
#Configuration
public FreemarkerConfiguration freemarkerConfiguration() {
return FreemarkerConfigurationBuilder.withEscpeAutoAppend();
}
#Configuration
public ConfigurationFromPropertiesFile conf() {
return ConfigurationFromPropertiesFile.fromPath("...");
}
#Configuration
public LocaleResolverConfigurator loc() {
return LocaleResolverConfigurator.trackedInCookie().withDefaultLocale("en").withSwitchUrl("/switchlocale/{loc}");
}
Let me quote Spring Boot Reference Guide - Externalized Configuration:
"Spring Boot allows you to externalize your configuration so you can work with the same application code in different environments."
In my opinion the customization is not done at import time via annotation parameters like in your 2nd pseudo code block, instead the customization happens at run time e.g. in the configuration classes. Let me adapt your 3rd code block (only one function):
#Configuration
public class MyAppConfiguration {
#Autowired
private Environment env;
// Provide a default implementation for FreeMarkerConfigurer only
// if the user of our config doesn't define her own configurer.
#Bean
#ConditionalOnMissingBean(FreeMarkerConfigurer.class)
public FreeMarkerConfigurer freemarkerConfig() {
FreeMarkerConfigurer result = new FreeMarkerConfigurer();
result.setTemplateLoaderPath("/WEB-INF/views/");
return result;
}
...
#Bean
public LocaleResolverConfigurator loc() {
String defaultLocale = env.getProperty("my.app.config.defaultlocale", "en");
String switchLocale = env.getProperty("my.app.config.switchlocale", "/switchlocale/{loc}");
return LocaleResolverConfigurator.trackedInCookie().withDefaultLocale(defaultLocale).withSwitchUrl(switchLocale);
}
For LocaleResolverConfigurator the configuration is read from the environment, meaningful default values are defined. It is easy to change the default value(s) by providing a different value for a config parameter in any of the supported ways (documented in the first link) - via command line or a yaml file. The advantage over annotation parameters is that you can change the behavior at run time instead of compile time.
You could also inject the config parameters (if you prefer to have them as instance variable) or use a lot of other conditions, e.g. #ConditionalOnMissingBean, #ConditionalOnClass, #ConditionalOnExpression and so on. For example with #ConditionalOnClass you could check if a particular class is on your class path and provide a setting for the library identified by this class. With #ConditionalOnMissingClass you could provide an alternative implementation. In the example above I used ConditionalOnMissingBean to provide a default implementation for the FreeMarkerConfigurer. This implementation is only used when no FreeMarkerConfigurer bean is available thus can be overridden easily.
Take a look at the starters provided by Spring Boot or the community. A good read is also this blog entry. I learned a lot from spring-boot-starter-batch-web, they had an article series in a German Java magazine, but parts are also online, see Boot your own infrastructure – Extending Spring Boot in five steps (MUST READ) and especially the paragraph "Make your starter configurable by using properties".
Though I like the idea of having imports be parameterized, I think that as it stands now using #Import and #Configuration not a good fit.
I can think of two ways to use dynamic configurations, that don't rely on PropertySource style configuration.
Create a custom #ImportConfig annotation and annotation processor that accepts configuration properties that are hard-coded into the generated source files.
Use a BeanFactoryPostProcessor or BeanPostProcessor to add or manipulate your included beans respectively.
Neither is particularly simple IMO, but since it looks like you have a particular way of working. So it could be worth the time invested.

Java Spring: need wildcard #RequestMapping to match everything BUT /images/* with access to raw URL

I'm new to Spring taking over existing code that uses #RequestMapping for various routes. However, due to complexities of a new feature request, it would be much easier to bypass the Spring routing mechanism to have a single wildcard action method that matches all possible URLs EXCEPT for the asset directories:
match these:
(empty)
/
/anything/you/can/throw/at/it?a=b&c=d
but NOT:
/images/arrow.gif
/css/project.css
My various attempts either don't match at all or match but only capture a single word rather than the entire raw URL:
#RequestMapping(value="{wildcard:^(?!.*(?:images|css)).*\$}", method=RequestMethod.GET)
public String index(#PathVariable("wildcard") String wildcard,
Model model) {
log(wildcard); // => /anything/you/can/throw/at/it?a=b&c=d
}
(Various Google searches and Stackoverflow searches of "[spring] requestmapping wildcard" haven't helped so far.)
I would recomment the first approach involving access to static resources.
1) Since typically images/css are static resources, one approach is:
You can make good use of the mvc:resources element to point to the location of resources with a specific public URL pattern. Enter following in spring config xml file:
<mvc:resources mapping="/images/**" location="/images/" />
2) Another approach to acheive this is:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<exclude-mapping path="/images/**"/>
<bean class="com.example.MyCustomInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
And the Java Configuration:
#Configuration
#EnableWebMvc
public class MyWebConfig extends WebMvcConfigurerAdapter
{
#Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(new MyCustomInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/images/**");
}
}

Spring Looking at Default Value Before Resolving Placeholder

I've been searching forever for an issue similar to mine, but wasn't able to find one. Thus, I hope it's not a duplicate post.
Well,
I'm using spring integration to search for documents in my mongodb. Once it finds one, it sends the payload to another method.
I have a property file that I want to be resolved on this find and send configuration, so I use placeholders instead of static values.
Here's my xml:
<int:inbound-channel-adapter id="sendClientMailInboundAdapter"
expression="#repository.findClient()"
channel="sendClientMailChannel"
auto-startup="${send.mail.active:false}">
<int:poller fixed-rate="${send.mail.poller.time:60}" time-unit="SECONDS" max-messages-per-poll="1" />
</int:inbound-channel-adapter>
<int:channel id="sendClientMailChannel" />
<int:service-activator input-channel="sendClientMailChannel" expression="#service.sendClientMail(payload)" />
Right.
Now.. I've got an AppConfig class which loads the property file.
#Configuration
#PropertySource("file://${appconfig.root}/appconfig.properties")
public class AppConfig {
public static Environment env;
...
#Bean
public static PropertySourcesPlaceholderConfigurer appConfigConfigurer(Environment env) {
AppConfig.env = env;
PropertySourcesPlaceholderConfigurer appConfigConfigurer = new PropertySourcesPlaceholderConfigurer();
appConfigConfigurer.setIgnoreUnresolvablePlaceholders(true);
appConfigConfigurer.setIgnoreResourceNotFound(true);
return appConfigConfigurer;
}
}
So.. My problem is the default value.
If I do NOT specify the default value, spring resolves the placeholder. If it's specified, though, it seems spring ignores the placeholder (maybe it doesn't wait for it to resolve) and set the default value instead!
I could use context:property-placeholder location. I've done that and it worked, but since I'm already loading the file on my configuration class, I'd rather have spring read properties from there so I would not have to remember to adjust two files in case property file changes its folder, for instance.
My question: Is there a way to tell spring to wait for the placeholder to resolve before looking at the default value?
edit: Just so people know.. I changed xml to java config, using placeholder / default value like before and it worked perfectly.
Here is the equivalent snippet:
#InboundChannelAdapter(value = "sendClientMailChannel", autoStartup="${send.mail.active:false}",
poller = #Poller(fixedRate="${send.mail.poller.time.in.millis:60000}", maxMessagesPerPoll="1"))
public Client findClient() {
return repository.findClient();
}
#ServiceActivator(inputChannel="sendClientMailChannel")
public void sendToClient(Client payload) {
service.sendClientMail(payload);
}
#Bean
public DirectChannel sendClientMailChannel() {
return new DirectChannel();
}
note: I didn't have to ref the property file.

Specific url for controller using #PathVariable in RequestMapping issue

I am facing some difficulties when trying to create a certain url for #RequestMapping in a spring controller.
First of all, because of the framework the project uses, there is a controller method mapped to the following url: "/{pageLabelOrId}"
Second, I have another controller method mapped to "/{pageName}.html". This works fine, meaning that if I try to access from browser "www.applicationUrl/something.html" this url if captured by the second method as is intended.
Now here is my problem. I must handle a somehow different but also similar url in a distinct method as follows: "/something-{parameter1}_{parameter2}_{parameter3}"
Trying to access "www.applicationUrl/something-1_2_3" will trigger the first controller: "/{pageLabelOrId}" instead of the desired one.
Handling this kind of url("/something-1_2_3") is a requirement and I cannot change it to something like "/something/{param1}/{param2}/{param3}" which I am sure it will work.
I have observed however, that writing a controller method mapped to "/something-{param}" will work and will capture my three parameters in one PathVariable(like "1_2_3") that I can parse afterwards by "_".
Does anyone have any idea of why does spring has this behavior and if I can somehow make it to work using three different path variables?
Spring 3.1+ uses a RequestMappingHandlerMapping to create RequestMappingInfo objects which map your controller handler methods to their corresponding #RequestMapping paths. When the DispatcherServlet receives a request, it uses the RequestMappingHandlerMapping again to determine which handler method should be used.
The way it is currently implemented, it finds all the registered RequestMappingInfo objects that match the request and then sorts them based on a number of rules, basically those defined in AntPathMatcher and AntPatternComparator.
You'll have to configure your RequestMappingHandlerMapping bean to use a custom PathMatcher with your own rules for comparing.
How you do this depends on how you are doing your configuration. If you use a #Configuration class, you can do
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
...
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = super.requestMappingHandlerMapping();
mapping.setPathMatcher(pathMatcher()); // some PathMatcher bean
return mapping;
}
...
}
The RequestMappingInfo objects created by this RequestMappingHandlerMapping will internally use this PathMatcher. They will be sorted based on its Comparator.

Categories

Resources