Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

SimpleStorageResource not resolved in spring-boot 2.1.x #348

Closed
JurrianFahner opened this issue Jun 20, 2018 · 31 comments
Closed

SimpleStorageResource not resolved in spring-boot 2.1.x #348

JurrianFahner opened this issue Jun 20, 2018 · 31 comments
Labels
component: s3 S3 integration related issue help wanted type: bug A general bug
Milestone

Comments

@JurrianFahner
Copy link

JurrianFahner commented Jun 20, 2018

In the documentation this snippet can be found:

public class SimpleResourceLoadingBean {

    @Autowired
    private ResourceLoader resourceLoader;

    public void writeResource() throws IOException {
        Resource resource = this.resourceLoader.getResource("s3://myBucket/rootFile.log");
        WritableResource writableResource = (WritableResource) resource;
        try (OutputStream outputStream = writableResource.getOutputStream()) {
            outputStream.write("test".getBytes());
        }
    }
}

This works in Finchley.RC2 as expected. But when I run the same code with only spring cloud updated to Finchley.RELEASE, I get the following class cast exception: org.springframework.web.context.support.ServletContextResource cannot be cast to org.springframework.core.io.WritableResource

In my debugger I see that the Resource should be an instance of a SimpleStorageResource, instead it is an instance of a ServletContextResource.

That is caused by the resourceLoader which shoud be a org.springframework.cloud.aws.core.io.s3.PathMatchingSimpleStorageResourcePatternResolver, instead it is an instance of a AnnotationConfigServletWebServerApplicationContext.

I use spring-boot 2.0.3

@aemruli
Copy link
Contributor

aemruli commented Jun 20, 2018

Thanks @JurrianFahner for reporting. This is unfortunately a regression as we have utilized some new Spring features for loading resources (to solve some other bugs). It seems that this does not work with the Web Application context. I will add test cases with a web application context and see how to solve it.

In the meantime you could inject a org.springframework.cloud.aws.core.io.s3.SimpleStorageProtocolResolver and use that directly (pass in the location and the application context to the resolve method)

@JurrianFahner
Copy link
Author

JurrianFahner commented Jun 21, 2018

Well that's not so easy. Because SimpleStorageProtocolResolver needs an S3 client with credentials.

So I use RC2 in the meantime until this is fixed. My project is in dev phase, so I have some time. I imagine that it will be fixed soon.

@sebastianreloaded
Copy link

This makes the S3 resource loader indeed unusable. RC2 does work though.

Is there a plan to fix this with 2.0.x ?

@sebastianreloaded
Copy link

Is this related to #384 ?

@JurrianFahner
Copy link
Author

Is this related to #384 ?

Nope, because I didn't use devtools. So removing devtools wouldn't fix the issue.

@denis111
Copy link

I have used this inline workaround for now

Resource resource = resourceLoader.getResource(location);

// workaround for https://github.com/spring-cloud/spring-cloud-aws/issues/348
// TODO: wait the bug to be solved and remove the "if" below
if (!resource.getClass().getName().endsWith("SimpleStorageResource") && resourceLoader instanceof DefaultResourceLoader) {
  for (ProtocolResolver protocolResolver : ((DefaultResourceLoader) resourceLoader)
      .getProtocolResolvers()) {
    if (protocolResolver instanceof SimpleStorageProtocolResolver) {
      resource = protocolResolver.resolve(location, resourceLoader);
      break;
    }
  }
}

@JurrianFahner
Copy link
Author

@denis111 It's a nice workaround, but it's fixing the outcome, but not the root of the problem. @aemruli already mentioned that the root of the problem is due to the regression in spring cloud aws. So it should be fixed in 2.x.x branches...

@darioseidl
Copy link

@denis111 Thanks! I just started using Spring Cloud AWS and this was very helpful to me.

In my project, the same workaround looks like this (in Kotlin):

    /**
     * Override the [DefaultResourceLoader] to force resolving resources that start with `s3://` using [SimpleStorageProtocolResolver].
     *
     * Workaround for https://github.com/spring-cloud/spring-cloud-aws/issues/348.
     */
    @Bean
    @Primary
    fun resourceLoader(defaultResourceLoader: DefaultResourceLoader): ResourceLoader =
        object : ResourceLoader {
            override fun getClassLoader(): ClassLoader? =
                defaultResourceLoader.classLoader

            override fun getResource(location: String): Resource {
                return if (location.startsWith(S3_PROTOCOL_PREFIX))
                    defaultResourceLoader.protocolResolvers.findFirstInstance<SimpleStorageProtocolResolver>()!!.resolve(location, this)!!
                else
                    defaultResourceLoader.getResource(location)
            }
        }

However, while this works for a ResourceLoader, I also have this problem in a ResourceResolver,
when using a s3:// path as static resource location (ResourceHandlerRegistry.addResourceLocations). In that case I also get a ServletContextResource instead of a SimpleStorageResource as location parameter in my PathResourceResolver.

My workaround looks like this

/**
 * A [PathResourceResolver] that is able to resolve resources from S3.
 */
open class S3ResourceResolver(
    val resourceLoader: ResourceLoader
) : PathResourceResolver() {

    private val log = KotlinLogging.logger {}

    override fun getResource(resourcePath: String, location: Resource): Resource? =
        // Workaround for https://github.com/spring-cloud/spring-cloud-aws/issues/348
        if (location is ServletContextResource && location.path.startsWith("/$S3_PROTOCOL_PREFIX")) {
            val s3location = location.path.substringAfter("/")

            log.debug { "Resolving resource $resourcePath under $s3location" }
            val resource = resourceLoader.getResource(s3location.appendPath(resourcePath))
            if (resource.exists())
                resource
            else
                null
        }
        else {
            log.debug { "Resolving resource $resourcePath under $location" }
            super.getResource(resourcePath, location)
        }
}

but I believe this is caused by the same bug, and should be fixed as well.

@cjdudleybw
Copy link

Hi, I've also encountered this issue, but the workaround suggested by @denis111 does not work (same error), and doesn't really address the issue. Any other approaches that have worked, or any news on when this may be fixed?

@darioseidl
Copy link

@cjdudleybw Did you register a SimpleStorageProtocolResolver? The workaround will only work if your ResourceResolver has a SimpleStorageProtocolResolver. You can register it with the Spring Cloud AWS Spring Boot auto-configuration, or manually by declaring a SimpleStorageProtocolResolverConfigurer bean, or with configurableApplicationContext.addProtocolResolver.

@spencergibb spencergibb added type: bug A general bug help wanted and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 29, 2019
@JurrianFahner
Copy link
Author

I can confirm that in Greenwich.RELEASE the problem is fixed, and SimpleStorageResource is working.

But that's only true for spring-boot 2.1.x

@derandreasberger
Copy link

Hello,
seeing the same issue - unfortunately quite a lot of spring dependencies/not sure when what got updated (but no devtools) - Anyway: I tried to update to a newer version:

... <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>
...
+
...
 <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
...

Since this was reopened - can we assume that Greenwich.SR1 is not/not completely resolving this issue?
@denis111 the workaround works, very much appreciated, thanks!
Regards,
Andy

@denis111
Copy link

denis111 commented Apr 3, 2019

Works fine for me with Spring Boot 2.1.x...

@annagapuz
Copy link

annagapuz commented Apr 5, 2019

I am experiencing the same issue of the s3 protocol not resolving and getting a FileNotFoundException. Using Spring Boot starter 2.1.3-RELEASE and Spring Cloud Greenwich.RELEASE.

Will try workaround by @denis111 and post back if it works.

@annagapuz
Copy link

annagapuz commented Apr 8, 2019

Update # 2:

Got it working with a different workaround.

Still on Greenwich.RELEASE and Boot 2.1.3.RELEASE.

Despite the s3: protocol, my resource is a ServletContextResource and no protocol resolvers are found in DefaultResourceLoader.

When I try to wire in SimpleStorageProtocolResolver, I get the following:
_

No qualifying bean of type 'org.springframework.cloud.aws.core.io.s3.SimpleStorageProtocolResolver' available
_

I have to manually create SimpleStorageProtocolResolver by auto-wiring in AmazonS3 and passing it in to the constructor:

SimpleStorageProtocolResolver simpleStorageProtocolResolver = new SimpleStorageProtocolResolver(amazonS3);

AWS Context XML:
<aws-context:context-resource-loader/>

POM excerpt:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/>
    </parent>

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>

         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-aws-context</artifactId>
        </dependency>

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

Hopefully a fix will be found soon!

@gysel
Copy link

gysel commented May 20, 2019

I also have this issue with Spring Boot 2.1.5 and Greenwich.

@amgapuz do you have a more detailed example on how to configure the SimpleStorageProtocolResolver? I'm not sure how to do it without XML.

@darioseidl
Copy link

I have upgraded to Spring Boot 2.1.5 and Spring Cloud Context 2.1.1 and I found that this is working now if I remove Spring Boot Devtools from the classpath.

This might explain why it's working for @JurrianFahner now, but not for others. It seems like something has been fixed, but this issue is also caused by #384. This problem has also been reported with Spring Boot, but has not been resolved yet: spring-projects/spring-boot#9331.

When autowiring ResourceLoader (or ApplicationContext), and calling resourceLoader.getResource(location), having Spring Boot Devtools on the classpath, the location is incorrectly resolved as a ServletContextResource with path starting with /s3://. Without Devtools it is correctly resolved to a SimpleStorageResource.

The GenericApplicationContext.getResource method calls it's own ResourceLoader instead of super.getResource. Without Devtools there is no resourceLoader set in GenericApplicationContext, so super.getResource gets called and it works correctly.

When I try to add a S3 resource location in WebMvcConfigurer.addResourceHandlers with ResourceHandlerRegistry.addResourceLocations, the same happens:

ResourceHttpRequestHandler.resolveResourceLocations calls applicationContext.getResource(location) for each location, but with Devtools on the classpath, the GenericApplicationContext.getResource method calls it's own ResourceLoader instead of super.getResource.

@JurrianFahner
Copy link
Author

JurrianFahner commented Jun 29, 2019

@darioseidl We should maybe list projects which aren't working with specific combinations.
But for me it's working with spring-boot 2.1.4 and greenwich.SR1. I don't have devtools on my classpath, so that's no problem for me.

I've changed the title, because the issue applies now to other situations than finley only.

@JurrianFahner JurrianFahner changed the title SimpleStorageResource not resolved in Finley.RELEASE SimpleStorageResource problems in spring-boot 2.1.x Jun 29, 2019
@JurrianFahner JurrianFahner changed the title SimpleStorageResource problems in spring-boot 2.1.x SimpleStorageResource not resolved in spring-boot 2.1.x Jun 29, 2019
@annagapuz
Copy link

annagapuz commented Jul 3, 2019

@gysel

I had to manually create a SimpleStorageProtocolResolver by auto-wiring in AmazonS3 to my bean and passing it in to the constructor:

@Autowired
private AmazonS3 amazonS3;

// Spring Core IO ResourceLoader - AWS context XML sets this correctly
// If you are looking to set and configure the AWS context in code, I have no example for that
@Autowired
private ResourceLoader resourceLoader;

public void myMethodToFetchFromS3() {
SimpleStorageProtocolResolver simpleStorageProtocolResolver = new SimpleStorageProtocolResolver(amazonS3);

Resource aResource = resourceLoader.getResource("s3://bucket/path/to/resource");

// The workaround
if (!aResource.getClass().getName().endsWith("SimpleStorageResource") && 
         resourceLoader instanceof DefaultResourceLoader) {
  aResource = simpleStorageProtocolResolver.resolve("s3://bucket/path/to/resource", resourceLoader);
  }
}

Hope that helps.

@annagapuz
Copy link

The problem just returned to me with Spring Boot 2.2.1 (maybe 2.2.0 also). But my previous workaround didn't work so I used workaround by @annagapuz but instantiating SimpleStorageProtocolResolver only once in the constructor of my bean.

Good to know - about to upgrade to spring boot 2.2.x

@JurrianFahner
Copy link
Author

JurrianFahner commented Dec 1, 2019

@denis111 and @annagapuz It is nice that you both are testing spring cloud aws on spring-boot 2.2.x. But were you also aware that spring-cloud is not yet ready for spring boot 2.2.x (when you reported the bugs)? I can't figure it out from your feedback, which spring-cloud version you used.
Spring cloud uses a release train, as you can learn from: https://spring.io/projects/spring-cloud

Release Train Boot Version
Hoxton 2.2.x
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

So in order to get spring-boot 2.2.x working with spring cloud, you'll need Hoxton.RELEASE. Can you please let us know whether the problem persists when you upgrade to Hoxton.RELEASE?

@darioseidl
Copy link

This seems to be fixed with Spring Boot 2.2.1 and Spring Cloud AWS 2.2.0 for me. The bug, where the SimpleStorageProtocolResolver would not be resolved when DevTools was on the classpath, has been fixed in Spring Boot and Spring Framework: spring-projects/spring-boot#17214

I have a simple test here: https://github.com/darioseidl/s3demo. With and without Spring Boot Devtools on the classpath, the SimpleStorageResource is resolved correctly now.

@denis111
Copy link

denis111 commented Dec 5, 2019

@JurrianFahner yes, I was on Greenwich and can confirm that with Hoxton I don't need the workaround.

@denis111
Copy link

denis111 commented Dec 5, 2019

No, It just happend to me again running with Gradle's bootRun java.io.FileNotFoundException: Could not open ServletContext resource [/s3://...
But worken one time running from IDE.

@annagapuz
Copy link

@JurrianFahner I saw the error with Boot 2.1.3-RELEASE and Cloud Greenwich.RELEASE. I have not tested with 2.2.X + Hoxton. Will be doing an upgrade soon though.

@artem-emelin
Copy link

Ran into the issue with Boot 2.2.2.RELEASE + Cloud Hoxton.SR1
Workaround by @annagapuz helped

@maciejwalkowiak maciejwalkowiak added component: s3 S3 integration related issue status: waiting-for-triage An issue we've not yet triaged and removed help wanted status: waiting-for-triage An issue we've not yet triaged labels May 29, 2020
@maciejwalkowiak
Copy link
Contributor

I am not able to reproduce this bug with Spring Boot 2.3 and Spring Cloud AWS 2.2.2.

If you are still facing this issue please comment ideally with a link to github repo that reproduces the bug.

@PavelPolyakov
Copy link

@maciejwalkowiak here is a repo with the latest versions, where s3 integration from the guide doesn't work. Unless custom resource loader is defined.

https://github.com/PP-etc/spring-boot-with-s3-example/blob/master/src/main/java/com/pavelpolyakov/s3example/S3ExampleApplication.java#L33

When it's not available, I have this exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 's3ExampleApplication': Invocation of init method failed; nested exception is java.lang.ClassCastException: class org.springframework.web.context.support.ServletContextResource cannot be cast to class org.springframework.core.io.WritableResource (org.springframework.web.context.support.ServletContextResource and org.springframework.core.io.WritableResource are in unnamed module of loader 'app')

@maciejwalkowiak
Copy link
Contributor

Thanks @PavelPolyakov! We will look into it.

@maciejwalkowiak maciejwalkowiak added this to the 2.2.3 milestone Jun 22, 2020
maciejwalkowiak added a commit that referenced this issue Sep 16, 2020
Resolve AmazonS3 client when the first resource is loaded - leaving time for custom AmazonS3 bean to be created.

Fixes gh-640
Fixes gh-641

Introduced when fixing gh-348.
maciejwalkowiak added a commit to maciejwalkowiak/spring-cloud-aws that referenced this issue Oct 15, 2020
maciejwalkowiak added a commit to maciejwalkowiak/spring-cloud-aws that referenced this issue Oct 15, 2020
Resolve AmazonS3 client when the first resource is loaded - leaving time for custom AmazonS3 bean to be created.

Fixes spring-atticgh-640
Fixes spring-atticgh-641

Introduced when fixing spring-atticgh-348.
maciejwalkowiak added a commit to maciejwalkowiak/spring-cloud-aws that referenced this issue Oct 15, 2020
maciejwalkowiak added a commit to maciejwalkowiak/spring-cloud-aws that referenced this issue Oct 15, 2020
Resolve AmazonS3 client when the first resource is loaded - leaving time for custom AmazonS3 bean to be created.

Fixes spring-attic#640
Fixes spring-attic#641

Introduced when fixing spring-attic#348.
juho9000 pushed a commit to juho9000/spring-cloud-aws that referenced this issue Apr 29, 2021
Resolve AmazonS3 client when the first resource is loaded - leaving time for custom AmazonS3 bean to be created.

Fixes spring-attic/spring-cloud-aws#640
Fixes spring-attic/spring-cloud-aws#641

Introduced when fixing spring-attic/spring-cloud-aws#348.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
component: s3 S3 integration related issue help wanted type: bug A general bug
Development

Successfully merging a pull request may close this issue.