diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java index 2223f789ae1c..4bd6ef58e966 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -64,24 +64,24 @@ public ParserContext(XmlReaderContext readerContext, BeanDefinitionParserDelegat } - public final XmlReaderContext getReaderContext() { + public XmlReaderContext getReaderContext() { return this.readerContext; } - public final BeanDefinitionRegistry getRegistry() { + public BeanDefinitionRegistry getRegistry() { return this.readerContext.getRegistry(); } - public final BeanDefinitionParserDelegate getDelegate() { + public BeanDefinitionParserDelegate getDelegate() { return this.delegate; } @Nullable - public final BeanDefinition getContainingBeanDefinition() { + public BeanDefinition getContainingBeanDefinition() { return this.containingBeanDefinition; } - public final boolean isNested() { + public boolean isNested() { return (this.containingBeanDefinition != null); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 5be6cb914b44..b9b684ce74d6 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -424,40 +425,38 @@ private Set scanCandidateComponents(String basePackage) { if (traceEnabled) { logger.trace("Scanning " + resource); } - if (resource.isReadable()) { - try { - MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); - if (isCandidateComponent(metadataReader)) { - ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); - sbd.setSource(resource); - if (isCandidateComponent(sbd)) { - if (debugEnabled) { - logger.debug("Identified candidate component class: " + resource); - } - candidates.add(sbd); - } - else { - if (debugEnabled) { - logger.debug("Ignored because not a concrete top-level class: " + resource); - } + try { + MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); + if (isCandidateComponent(metadataReader)) { + ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); + sbd.setSource(resource); + if (isCandidateComponent(sbd)) { + if (debugEnabled) { + logger.debug("Identified candidate component class: " + resource); } + candidates.add(sbd); } else { - if (traceEnabled) { - logger.trace("Ignored because not matching any filter: " + resource); + if (debugEnabled) { + logger.debug("Ignored because not a concrete top-level class: " + resource); } } } - catch (Throwable ex) { - throw new BeanDefinitionStoreException( - "Failed to read candidate component class: " + resource, ex); + else { + if (traceEnabled) { + logger.trace("Ignored because not matching any filter: " + resource); + } } } - else { + catch (FileNotFoundException ex) { if (traceEnabled) { - logger.trace("Ignored because not readable: " + resource); + logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage()); } } + catch (Throwable ex) { + throw new BeanDefinitionStoreException( + "Failed to read candidate component class: " + resource, ex); + } } } catch (IOException ex) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java index 3af71481c27d..6581cdd61fe0 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2021 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. @@ -105,11 +105,11 @@ public Date nextExecutionTime(TriggerContext triggerContext) { } } else { - date = new Date(); + date = new Date(triggerContext.getClock().millis()); } ZonedDateTime dateTime = ZonedDateTime.ofInstant(date.toInstant(), this.zoneId); ZonedDateTime next = this.expression.next(dateTime); - return next != null ? Date.from(next.toInstant()) : null; + return (next != null ? Date.from(next.toInstant()) : null); } diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index 958b3babce7c..ac9ea29a5dca 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -88,7 +88,15 @@ else if (code == HttpURLConnection.HTTP_NOT_FOUND) { @Override public boolean isReadable() { try { - URL url = getURL(); + return checkReadable(getURL()); + } + catch (IOException ex) { + return false; + } + } + + boolean checkReadable(URL url) { + try { if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution File file = getFile(); diff --git a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java index 6374f2768b9d..f99adce06ce4 100644 --- a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java @@ -142,6 +142,18 @@ public boolean exists() { return (resolveURL() != null); } + /** + * This implementation checks for the resolution of a resource URL upfront, + * then proceeding with {@link AbstractFileResolvingResource}'s length check. + * @see java.lang.ClassLoader#getResource(String) + * @see java.lang.Class#getResource(String) + */ + @Override + public boolean isReadable() { + URL url = resolveURL(); + return (url != null && checkReadable(url)); + } + /** * Resolves a URL for the underlying class path resource. * @return the resolved URL, or {@code null} if not resolvable diff --git a/spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java b/spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java index b769895ece77..8d72c9cd8bbc 100644 --- a/spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java +++ b/spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 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. @@ -48,8 +48,9 @@ public interface InputStreamSource { * creating mail attachments. For such a use case, it is required * that each {@code getInputStream()} call returns a fresh stream. * @return the input stream for the underlying resource (must not be {@code null}) - * @throws java.io.FileNotFoundException if the underlying resource doesn't exist + * @throws java.io.FileNotFoundException if the underlying resource does not exist * @throws IOException if the content stream could not be opened + * @see Resource#isReadable() */ InputStream getInputStream() throws IOException; diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java index 0ddf08bb8683..e2346f1088b3 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java @@ -16,6 +16,7 @@ package org.springframework.orm.hibernate5; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; @@ -319,7 +320,7 @@ public LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws Resource[] resources = this.resourcePatternResolver.getResources(pattern); MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); for (Resource resource : resources) { - if (resource.isReadable()) { + try { MetadataReader reader = readerFactory.getMetadataReader(resource); String className = reader.getClassMetadata().getClassName(); if (matchesEntityTypeFilter(reader, readerFactory)) { @@ -332,6 +333,9 @@ else if (className.endsWith(PACKAGE_INFO_SUFFIX)) { packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length())); } } + catch (FileNotFoundException ex) { + // Ignore non-readable resource + } } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java index 7d51f1bfa293..af39d7ea125c 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java @@ -16,6 +16,7 @@ package org.springframework.orm.jpa.persistenceunit; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.util.ArrayList; @@ -583,7 +584,7 @@ private void scanPackage(SpringPersistenceUnitInfo scannedUnit, String pkg) { Resource[] resources = this.resourcePatternResolver.getResources(pattern); MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); for (Resource resource : resources) { - if (resource.isReadable()) { + try { MetadataReader reader = readerFactory.getMetadataReader(resource); String className = reader.getClassMetadata().getClassName(); if (matchesFilter(reader, readerFactory)) { @@ -600,6 +601,9 @@ else if (className.endsWith(PACKAGE_INFO_SUFFIX)) { className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length())); } } + catch (FileNotFoundException ex) { + // Ignore non-readable resource + } } } catch (IOException ex) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java index e17cca7906e4..60caf7c19e38 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -72,7 +72,7 @@ public Mono apply(ServerRequest request) { try { Resource resource = this.location.createRelative(path); - if (resource.exists() && resource.isReadable() && isResourceUnderLocation(resource)) { + if (resource.isReadable() && isResourceUnderLocation(resource)) { return Mono.just(resource); } else { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/PathResourceLookupFunction.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/PathResourceLookupFunction.java index 7079290f30a0..c37bbfdce919 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/PathResourceLookupFunction.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/PathResourceLookupFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -71,7 +71,7 @@ public Optional apply(ServerRequest request) { try { Resource resource = this.location.createRelative(path); - if (resource.exists() && resource.isReadable() && isResourceUnderLocation(resource)) { + if (resource.isReadable() && isResourceUnderLocation(resource)) { return Optional.of(resource); } else { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java index cce273a24937..fccde83979de 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java @@ -27,7 +27,6 @@ import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; import org.springframework.http.CacheControl; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.context.support.GenericWebApplicationContext; @@ -64,7 +63,7 @@ public class ResourceHandlerRegistryTests { @BeforeEach - public void setUp() { + public void setup() { GenericWebApplicationContext appContext = new GenericWebApplicationContext(); appContext.refresh(); @@ -76,8 +75,14 @@ public void setUp() { this.response = new MockHttpServletResponse(); } + private ResourceHttpRequestHandler getHandler(String pathPattern) { + SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping(); + return (ResourceHttpRequestHandler) hm.getUrlMap().get(pathPattern); + } + + @Test - public void noResourceHandlers() throws Exception { + public void noResourceHandlers() { this.registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(), new MockServletContext()); assertThat((Object) this.registry.getHandlerMapping()).isNull(); } @@ -126,7 +131,7 @@ public void hasMappingForPattern() { } @Test - public void resourceChain() throws Exception { + public void resourceChain() { ResourceResolver mockResolver = Mockito.mock(ResourceResolver.class); ResourceTransformer mockTransformer = Mockito.mock(ResourceTransformer.class); this.registration.resourceChain(true).addResolver(mockResolver).addTransformer(mockTransformer); @@ -148,7 +153,7 @@ public void resourceChain() throws Exception { } @Test - public void resourceChainWithoutCaching() throws Exception { + public void resourceChainWithoutCaching() { this.registration.resourceChain(false); ResourceHttpRequestHandler handler = getHandler("/resources/**"); @@ -163,7 +168,7 @@ public void resourceChainWithoutCaching() throws Exception { @Test @SuppressWarnings("deprecation") - public void resourceChainWithVersionResolver() throws Exception { + public void resourceChainWithVersionResolver() { VersionResourceResolver versionResolver = new VersionResourceResolver() .addFixedVersionStrategy("fixed", "/**/*.js") .addContentVersionStrategy("/**"); @@ -188,7 +193,7 @@ public void resourceChainWithVersionResolver() throws Exception { @Test @SuppressWarnings("deprecation") - public void resourceChainWithOverrides() throws Exception { + public void resourceChainWithOverrides() { CachingResourceResolver cachingResolver = Mockito.mock(CachingResourceResolver.class); VersionResourceResolver versionResolver = Mockito.mock(VersionResourceResolver.class); WebJarsResourceResolver webjarsResolver = Mockito.mock(WebJarsResourceResolver.class); @@ -224,13 +229,11 @@ public void resourceChainWithOverrides() throws Exception { } @Test - public void urlResourceWithCharset() throws Exception { + public void urlResourceWithCharset() { this.registration.addResourceLocations("[charset=ISO-8859-1]file:///tmp"); this.registration.resourceChain(true); ResourceHttpRequestHandler handler = getHandler("/resources/**"); - UrlResource resource = (UrlResource) handler.getLocations().get(1); - assertThat(resource.getURL().toString()).isEqualTo("file:/tmp"); assertThat(handler.getUrlPathHelper()).isNotNull(); List resolvers = handler.getResourceResolvers(); @@ -241,15 +244,10 @@ public void urlResourceWithCharset() throws Exception { } @Test - void lastModifiedDisabled() { + public void lastModifiedDisabled() { this.registration.setUseLastModified(false); ResourceHttpRequestHandler handler = getHandler("/resources/**"); assertThat(handler.isUseLastModified()).isFalse(); } - private ResourceHttpRequestHandler getHandler(String pathPattern) { - SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping(); - return (ResourceHttpRequestHandler) hm.getUrlMap().get(pathPattern); - } - } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java index 76c19d77a06e..04895cfcc89d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java @@ -289,7 +289,6 @@ public void getMediaTypeWithFavorPathExtensionOff() throws Exception { @Test // SPR-14368 public void getResourceWithMediaTypeResolvedThroughServletContext() throws Exception { - MockServletContext servletContext = new MockServletContext() { @Override public String getMimeType(String filePath) { @@ -311,7 +310,7 @@ public String getMimeType(String filePath) { assertThat(this.response.getContentAsString()).isEqualTo("h1 { color:red; }"); } - @Test // gh-27538 + @Test // gh-27538 public void filterNonExistingLocations() throws Exception { List inputLocations = Arrays.asList( new ClassPathResource("test/", getClass()), @@ -331,7 +330,6 @@ public void filterNonExistingLocations() throws Exception { @Test public void testInvalidPath() throws Exception { - // Use mock ResourceResolver: i.e. we're only testing upfront validations... Resource resource = mock(Resource.class); @@ -677,7 +675,7 @@ public void partialContentMultipleByteRanges() throws Exception { assertThat(ranges[11]).isEqualTo("t."); } - @Test // gh-25976 + @Test // gh-25976 public void partialContentByteRangeWithEncodedResource(GzipSupport.GzippedFiles gzippedFiles) throws Exception { String path = "js/foo.js"; gzippedFiles.create(path); @@ -706,7 +704,7 @@ public void partialContentByteRangeWithEncodedResource(GzipSupport.GzippedFiles assertThat(this.response.getHeaderValues("Vary")).containsExactly("Accept-Encoding"); } - @Test // gh-25976 + @Test // gh-25976 public void partialContentWithHttpHead() throws Exception { this.request.setMethod("HEAD"); this.request.addHeader("Range", "bytes=0-1");