Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion framework-platform/framework-platform.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ dependencies {
api("org.seleniumhq.selenium:selenium-java:4.41.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.testng:testng:7.12.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.4")
api("org.xmlunit:xmlunit-matchers:2.10.4")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ private static MimeType parseMimeTypeInternal(String mimeType) {
break;
}
}
else if (ch == '"') {
else if (ch == '"' && mimeType.charAt(nextIndex - 1) != '\\') {
quoted = !quoted;
}
nextIndex++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ void parseQuotedCharset() {
}

@Test
void parseQuotedSeparator() {
void parseQuotedParameterValue() {
String s = "application/xop+xml;charset=utf-8;type=\"application/soap+xml;action=\\\"https://x.y.z\\\"\"";
MimeType mimeType = MimeType.valueOf(s);
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
Expand All @@ -107,6 +107,15 @@ void parseQuotedSeparator() {
assertThat(mimeType.getParameter("type")).isEqualTo("\"application/soap+xml;action=\\\"https://x.y.z\\\"\"");
}

@Test
void parseParameterWithQuotedPair() {
String s = "text/plain;twelve=\"1\\\"2\"";
MimeType mimeType = MimeType.valueOf(s);
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("text");
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("plain");
assertThat(mimeType.getParameter("twelve")).isEqualTo("\"1\\\"2\"");
}

@Test
void withConversionService() {
ConversionService conversionService = new DefaultConversionService();
Expand Down
2 changes: 1 addition & 1 deletion spring-webflux/spring-webflux.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ dependencies {
testRuntimeOnly("org.glassfish:jakarta.el")
testRuntimeOnly("org.jruby:jruby")
testRuntimeOnly("org.python:jython-standalone")
testRuntimeOnly("org.webjars:underscorejs")
testRuntimeOnly("org.webjars:momentjs:2.29.4")
}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ protected Mono<String> resolveUrlPathInternal(String resourceUrlPath,
.switchIfEmpty(Mono.defer(() -> {
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
if (webJarResourcePath != null) {
return chain.resolveUrlPath(webJarResourcePath, locations);
Mono<String> fallback = (webJarResourcePath.endsWith("/")) ? Mono.just(webJarResourcePath) : Mono.empty();
return chain.resolveUrlPath(webJarResourcePath, locations).switchIfEmpty(fallback);
}
else {
return Mono.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.springframework.web.testfixture.server.MockServerWebExchange;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
Expand Down Expand Up @@ -80,8 +82,8 @@ void resolveUrlExistingNotInJarFile() {

@Test
void resolveUrlWebJarResource() {
String file = "underscorejs/underscore.js";
String expected = "underscorejs/1.8.3/underscore.js";
String file = "momentjs/momentjs.js";
String expected = "momentjs/2.29.4/momentjs.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.just(expected));

Expand All @@ -92,10 +94,24 @@ void resolveUrlWebJarResource() {
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
}

@Test
void resolveUrlWebJarDirectory() {
String folder = "momentjs/locale/";
String expected = "momentjs/2.29.4/locale/";
given(this.chain.resolveUrlPath(folder, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.empty());

String actual = this.resolver.resolveUrlPath(folder, this.locations, this.chain).block(TIMEOUT);

assertThat(actual).isEqualTo(expected);
verify(this.chain, times(1)).resolveUrlPath(folder, this.locations);
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
}

@Test
void resolveUrlWebJarResourceNotFound() {
String file = "something/something.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
String file = "momentjs/locale/unknown.js";
given(this.chain.resolveUrlPath(anyString(), eq(this.locations))).willReturn(Mono.empty());

String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).block(TIMEOUT);

Expand Down Expand Up @@ -134,11 +150,11 @@ void resolveResourceNotFound() {

@Test
void resolveResourceWebJar() {
String file = "underscorejs/underscore.js";
String file = "momentjs/momentjs.js";
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(Mono.empty());

Resource expected = mock();
String expectedPath = "underscorejs/1.8.3/underscore.js";
String expectedPath = "momentjs/2.29.4/momentjs.js";
given(this.chain.resolveResource(this.exchange, expectedPath, this.locations))
.willReturn(Mono.just(expected));

Expand Down
2 changes: 1 addition & 1 deletion spring-webmvc/spring-webmvc.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,5 @@ dependencies {
testRuntimeOnly("org.glassfish:jakarta.el")
testRuntimeOnly("org.jruby:jruby")
testRuntimeOnly("org.python:jython-standalone")
testRuntimeOnly("org.webjars:underscorejs")
testRuntimeOnly("org.webjars:momentjs:2.29.4")
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
package org.springframework.web.servlet;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand All @@ -42,9 +44,14 @@
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
Expand Down Expand Up @@ -513,7 +520,7 @@ private void initHandlerMappings(ApplicationContext context) {
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
sortStrategyBeans(context, matchingBeans, this.handlerMappings);
}
}
else {
Expand Down Expand Up @@ -559,7 +566,7 @@ private void initHandlerAdapters(ApplicationContext context) {
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
sortStrategyBeans(context, matchingBeans, this.handlerAdapters);
}
}
else {
Expand Down Expand Up @@ -598,7 +605,7 @@ private void initHandlerExceptionResolvers(ApplicationContext context) {
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
sortStrategyBeans(context, matchingBeans, this.handlerExceptionResolvers);
}
}
else {
Expand Down Expand Up @@ -663,7 +670,7 @@ private void initViewResolvers(ApplicationContext context) {
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.viewResolvers);
sortStrategyBeans(context, matchingBeans, this.viewResolvers);
}
}
else {
Expand Down Expand Up @@ -712,6 +719,24 @@ else if (logger.isDebugEnabled()) {
}
}

/**
* Sort the given strategy beans using {@link AnnotationAwareOrderComparator}, additionally
* consulting each bean's merged {@link BeanDefinition} for an
* {@link AbstractBeanDefinition#ORDER_ATTRIBUTE order attribute}, factory method or
* declared target type. This mirrors the ordering behavior the bean factory uses when
* resolving sorted dependency injections (see
* {@code DefaultListableBeanFactory.FactoryAwareOrderSourceProvider}), so programmatic
* ordering via {@code BeanRegistrar}, {@code GenericApplicationContext.registerBean(..., order)},
* or a direct {@code ORDER_ATTRIBUTE} on a bean definition is reflected here.
*/
private static <T> void sortStrategyBeans(ApplicationContext context, Map<String, T> matchingBeans, List<T> beans) {
if (beans.size() <= 1) {
return;
}
beans.sort(AnnotationAwareOrderComparator.INSTANCE.withSourceProvider(
new BeanDefinitionOrderSourceProvider(context, matchingBeans)));
}

/**
* Obtain this servlet's MultipartResolver, if any.
* @return the MultipartResolver used by this servlet, or {@code null} if none
Expand Down Expand Up @@ -1405,4 +1430,63 @@ private static String getRequestUri(HttpServletRequest request) {
return uri;
}

/**
* {@link OrderComparator.OrderSourceProvider} that resolves order metadata for a given
* bean instance from its merged {@link BeanDefinition}: an
* {@link AbstractBeanDefinition#ORDER_ATTRIBUTE order attribute}, a factory method, and
* a declared target type when distinct from the bean's runtime class. Mirrors
* {@code DefaultListableBeanFactory.FactoryAwareOrderSourceProvider} so that
* DispatcherServlet's strategy detection sees the same ordering inputs the bean factory
* uses for sorted dependency injection. Standard {@code @Order} / {@link Ordered}
* fallback handling is left to the comparator.
*/
private static class BeanDefinitionOrderSourceProvider implements OrderComparator.OrderSourceProvider {

private final @Nullable ApplicationContext context;

private final Map<Object, String> instancesToBeanNames;

BeanDefinitionOrderSourceProvider(@Nullable ApplicationContext context, Map<String, ?> matchingBeans) {
this.context = context;
this.instancesToBeanNames = new IdentityHashMap<>(matchingBeans.size());
matchingBeans.forEach((name, instance) -> this.instancesToBeanNames.put(instance, name));
}

@Override
public @Nullable Object getOrderSource(Object obj) {
String beanName = this.instancesToBeanNames.get(obj);
if (beanName == null || !(this.context instanceof ConfigurableApplicationContext cac)) {
return null;
}
try {
BeanDefinition beanDefinition = cac.getBeanFactory().getMergedBeanDefinition(beanName);
List<Object> sources = new ArrayList<>(3);
Object orderAttribute = beanDefinition.getAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE);
if (orderAttribute != null) {
if (orderAttribute instanceof Integer order) {
sources.add((Ordered) () -> order);
}
else {
throw new IllegalStateException("Invalid value type for attribute '" +
AbstractBeanDefinition.ORDER_ATTRIBUTE + "': " + orderAttribute.getClass().getName());
}
}
if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
Method factoryMethod = rootBeanDefinition.getResolvedFactoryMethod();
if (factoryMethod != null) {
sources.add(factoryMethod);
}
Class<?> targetType = rootBeanDefinition.getTargetType();
if (targetType != null && targetType != obj.getClass()) {
sources.add(targetType);
}
}
return sources.toArray();
}
catch (NoSuchBeanDefinitionException ex) {
return null;
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ public LiteWebJarsResourceResolver(WebJarVersionLocator webJarVersionLocator) {
if (path == null) {
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
if (webJarResourcePath != null) {
return chain.resolveUrlPath(webJarResourcePath, locations);
path = chain.resolveUrlPath(webJarResourcePath, locations);
if (path == null && webJarResourcePath.endsWith("/")) {
path = webJarResourcePath;
}
}
}
return path;
Expand Down
Loading
Loading