Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PathEditor does not handle absolute path on Windows #22511

Closed
aymeric-soubrouillard opened this issue Mar 5, 2019 · 7 comments
Closed

PathEditor does not handle absolute path on Windows #22511

aymeric-soubrouillard opened this issue Mar 5, 2019 · 7 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: duplicate A duplicate of another issue

Comments

@aymeric-soubrouillard
Copy link

aymeric-soubrouillard commented Mar 5, 2019

Consider using a windows absolute path in a property file:
repository.directory= C:some_path
We want to inject directly a Path using @value:

    public MyRepository(@Value("${repository.directory}") Path repositoryDirectory) {
        this.repositoryDirectory = repositoryDirectory;
    }
  1. If we are using the original Windows path, with back-slash:
    repository.directory=C:\Users\lacasoub\myUser\Local\Temp
    Some class (Properties.load() i guess) trims the slash and we end up with the following string: C:UsersmyUserAppDataLocalTemp.
    Which is then concat with the Sprint boot server relative path, ending with a String like this: C:\Users\myUser\AppData\Local\Temp\undertow-docbase.2615281017312972350.8080\C:UsersmyUserAppDataLocalTemp

Its a normal behavior for properties file.

  1. If we are using double back-slash, it works.

  2. If we are using slash instead:
    repository.directory=C:/Users/myUser/AppData/Local/Temp

PathEditor has the following behavior:

  • Converting this string to an URI, which is strange because URI and Windows Path are not compliant.
  • Adding a "/" because it don't understand that's it's a Windows path
  • Concat the relative path /C:/Users/myUser/AppData/Local/Temp with the Sprint boot server relative path, ending with a String like this: C:\Users\myUser\AppData\Local\Temp\undertow-docbase.2615281017312972350.8080\C:\Users\myUser\AppData\Local\Temp
  • Java path API throws an exception because the path creating by Sprint Boot PathEditor class is invalid:
    Caused by: java.nio.file.InvalidPathException: Illegal char <:> at index

The solution 2 works but the solution 3 should also works as the Java Path class allows Windows absolute path with back-slash (Linux style) and not the PathEditor.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 5, 2019
@jhoeller jhoeller self-assigned this Mar 5, 2019
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Mar 5, 2019
@lgxbslgx
Copy link
Contributor

lgxbslgx commented Apr 7, 2019

@aymeric-soubrouillard I don't quite understand your question. It confuse me in many aspects so that I can't reproduce your problem.

Sprint boot server

Do you mean spring boot?
And you would better to indicate the version of the spring you use and the version of the spring boot if used.

Which is then concat with the Sprint boot server relative path

Why do you want to concatenate a absolute path with another path? I can't understand what you want to do.

Maybe we have trouble communicating in English?
And could you give more information, such as a demo, about your problem? A runnable program could make your description clearer.

@nkjackzhang
Copy link
Contributor

nkjackzhang commented Apr 22, 2019

'\' means LF in properties files, so Properties.load() will omit it, the result of course is C:UsersmyUserAppDataLocalTemp. The usage of '\\' is the right way.

@Antoniossss
Copy link

Antoniossss commented Jun 25, 2019

The case is simple:

PathEditor#setAsText is used to convert String into Path.

On widows, flow will go the way that path will be treated as RESOURCE and will fail to resolve.

It will work, if provided path will start with file:// uri scheme.

Following block breaks functionality wit hexample of text="c:/widows/someFile

			URI uri = new URI(text);
				if (uri.getScheme() != null) {
					nioPathCandidate = false;
					// Let's try NIO file system providers via Paths.get(URI)
					setValue(Paths.get(uri).normalize());
					return;
				}
			}

uri.getScheme() will return C as scheme, causing nioPathCandidate to be set to false and fails later conversion at
else if (!resource.exists() && nioPathCandidate) {

since it is no longer nioPathCandidate and Paths.get(String) call is ommited - which is required

Antoniossss added a commit to Antoniossss/spring-framework that referenced this issue Jun 25, 2019
@sbrannen sbrannen changed the title org.springframework.beans.propertyeditors.PathEditor doesn't handle windows absolute path PathEditor does not handle absolute path on Windows Jun 26, 2019
@xak2000
Copy link
Contributor

xak2000 commented May 26, 2020

I want to add some additional info to this issue.

Maybe it is not directly related to the PathEditor, but the problem still worth to mention.

The problem is related to integration testing.

Even if I write Windows path "correctly" in test properties file, when I run spring integration test, it binds it wrongly, adding src/main/webapp/ at the start of my path for some reason and the test fails to start...

Example:

appliation-test.properties:

myapp.local-directory=${java.io.tmpdir}/subdir/attachments

(This path is intended to become /tmp/subdir/attachments on Unix and C:\Users\myuser\AppData\Local\Temp\subdir\attachments on Windows. The Path methods are pretty tolerant to forward/back slash replacement on Windows. Even duplicate slash is not a problem. So to be honest the String version of this path on Windows will be C:\Users\myuser\AppData\Local\Temp\/subdir/attachments, but Paths.get(..) is smart enough to correct all these slashes to right version for the OS and remove duplicates)

@ConfigurationProperties class:

@Component
@ConfigurationProperties(prefix = "myapp")
public class MyProps {
    private Path localDirectory;
}

When I run the app on Windows machine it correctly binds path C:\Users\myuser\AppData\Local\Temp\subdir\attachments to localDirectory field.

When I run integration test on Windows machine it fails to bind the path.
The exception thrown:

2020-05-26 15:20:59.574  WARN 9024 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Could not get URL for resource src/main/webapp/C:/Users/myuser/AppData/Local/Temp//subdir/attachments

java.nio.file.InvalidPathException: Illegal char <:> at index 17: src\main\webapp\C:\Users\myuser\AppData\Local\Temp\subdir\attachments
	at sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182) ~[na:1.8.0_222]
	at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153) ~[na:1.8.0_222]
	at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77) ~[na:1.8.0_222]
	at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94) ~[na:1.8.0_222]
	at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255) ~[na:1.8.0_222]
	at java.io.File.toPath(File.java:2234) ~[na:1.8.0_222]
	at org.springframework.core.io.FileSystemResource.<init>(FileSystemResource.java:82) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.core.io.FileSystemResourceLoader$FileSystemContextResource.<init>(FileSystemResourceLoader.java:65) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.core.io.FileSystemResourceLoader.getResourceByPath(FileSystemResourceLoader.java:54) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.core.io.DefaultResourceLoader.getResource(DefaultResourceLoader.java:168) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.mock.web.MockServletContext.getResource(MockServletContext.java:332) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.boot.test.mock.web.SpringBootMockServletContext.getResource(SpringBootMockServletContext.java:89) [spring-boot-test-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.web.context.support.ServletContextResource.exists(ServletContextResource.java:104) [spring-web-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.propertyeditors.PathEditor.setAsText(PathEditor.java:100) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:429) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:402) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:155) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:73) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:45) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.boot.context.properties.bind.BindConverter$TypeConverterConverter.convert(BindConverter.java:230) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) [spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:191) [spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.boot.context.properties.bind.BindConverter$CompositeConversionService.convert(BindConverter.java:164) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:96) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:88) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindProperty(Binder.java:313) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:258) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:322) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_222]
	at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) ~[na:1.8.0_222]
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_222]
	at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_222]
	at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531) ~[na:1.8.0_222]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:322) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_222]
	at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) ~[na:1.8.0_222]
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_222]
	at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_222]
	at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531) ~[na:1.8.0_222]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:322) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_222]
	at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) ~[na:1.8.0_222]
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_222]
	at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_222]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_222]
	at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531) ~[na:1.8.0_222]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:202) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:185) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:78) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:101) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:89) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:414) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1770) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1255) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:218) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1341) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1187) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:847) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120) ~[spring-boot-test-2.1.10.RELEASE.jar:2.1.10.RELEASE]
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) ~[junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) ~[junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) ~[junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) ~[junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) ~[junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) ~[junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363) ~[junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110) ~[na:na]
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58) ~[na:na]
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38) ~[na:na]
	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62) ~[na:na]
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) ~[na:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_222]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_222]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na]
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na]
	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source) ~[na:na]
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:118) ~[na:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_222]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_222]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) ~[na:na]
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) ~[na:na]
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:412) ~[na:na]
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) ~[na:na]
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) ~[na:na]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_222]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_222]
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) ~[na:na]
	at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_222]

I think the root of the problem is the same as the problem of author of this issue: Spring (PathEditor or some other component - I did not digging so deep) wrongly assumes that path, that starts with C:\ is not absolute and tries to prepend it with the context path.

Earlier when I run into this problem I solved it adding file:/// at the start of the path. This way PathEditor tries to resolve it as URI first, then tries to use ResourceEditor. But this solution actually doesn't always work. For some reason (which I don't understand) PathEditor checks the path (resource) for existence before trying to use resolved resource path value. But when I run my tests the target directory can exist or not exist. Both cases are correct. If it exists and my property has file:/// prefix (like: file:///${java.io.tmpdir}/subdir/attachments), then PathEditor successfully resolves it to Path instance. But if the path doesn't exist at the moment, PathEditor cannot resolve it. And the reason why it tries to resolve the path using ResourceEditor instead of simple URI is because the URI file:///C:\Users\myuser\AppData\Local\Temp/subdir/attachments is not correct because URI can't have \ character. But ResourceEditor somehow ignores this problem and correctly resolves the path. But then PathEditor ignores the result just because path doesn't exist at the moment. Very strange.

The last problem is not directly related to absolute path handling of PathEditor on Windows. It's here more to describe the complexity of the problem: absolute path handling on different OSes, checking for existence etc. All this logic should be reconsidered to make PathEditor work in all corner cases.


Edit

Actually, I was wrong. The test doesn't fail to start. It just logs the exception on WARN level, but then PathEditor checks, that this resource (src/main/webapp/C:/Users/myuser/AppData/Local/Temp//subdir/attachments) does not exist and executes:

setValue(Paths.get(text).normalize());

And this last line does exactly what it should: maps C:\Users\myuser\AppData\Local\Temp\/subdir/attachments to C:\Users\myuser\AppData\Local\Temp\subdir\attachments on Windows. So it works as expected.

So, without file:/// prefix it works. The only "problem" is stacktrace in the log. But issue for this already exist I think (I remember I filed it 1-2 years ago, but can't find it now).

Maybe the problem is also the fact that it tries to find such a resource. The logic that adds src/main/webapp prefix before absolute Windows URL is incorrect anyway. The resulting path will always be incorrect.

@bdurrer
Copy link

bdurrer commented Dec 9, 2020

I encountered the exception in the log xak2000 has on spring-boot-starter 2.3.5.RELEASE.

With app.myprop = ${java.io.tmpdir}/myfolder the conversion to java.nio.file.Path prefixes the path with src/main/webapp when loaded from a test (stacktrace same as above). However, it works just fine when booting the app normally.

The SpringBootMockServletContext executes exists(resourceLocation) checks with different locations. These all fail (even when the path exists) and cause the logged exception.
It then falls back to super.getResourceLocation(path), which prefixes the path with resourceBasePath. This is likely wrong, as it explicitly checks that location first using getResourceBasePathLocation(path); which in turn just defaults to super.getResourceLocation.

I think it is an error, but not the one reported by OP.

@candrews
Copy link
Contributor

The combination of #26574 and #26575 fixes this issue.

@jhoeller jhoeller added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Feb 22, 2021
@jhoeller
Copy link
Contributor

Closing this issue in favor of #26575.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: duplicate A duplicate of another issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants