paulcwarren / spring-content

Cloud-Native Storage and Enterprise Content Services (ECMS) for Spring
https://paulcwarren.github.io/spring-content/
Apache License 2.0
272 stars 66 forks source link

How do I add a REST API to read a mp3 file from the predefined folder #521

Closed BhanuPrakash531 closed 3 years ago

BhanuPrakash531 commented 3 years ago

I have a list of mp3s in my resource folder, I have a JPA implementation with multiple entities. I need to store the filename of the mp3 in the database as part of an entity and I need to read these mp3 as a REST APIs. Please help. Trying to do as per this example. Spring Content REST but I'm not getting the /dvds/1/image endpoint, server rejects with the error

{
    "timestamp": "2021-04-06T14:01:32.384+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/dvds/2/image"
}
@Data
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Dvd {

   @Id
   @GeneratedValue
   private Long id;

   private String title;

   @OneToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "image_id")
   private Image image;

   @OneToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "stream_id")
   private Stream stream;

}
@Data
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Image {

   // Spring Data managed attribute
   private @Id
   @GeneratedValue
   Long id;

   @OneToOne
   private Dvd dvd;

   // Spring Content managed attributes
   private @ContentId
   UUID contentId;

   private @ContentLength
   Long contentLen;

   @MimeType
   private String mimeType = "text/plain";
}
@Data
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Stream {

   // Spring Data managed attribute
   private @Id
   @GeneratedValue
   Long id;

   @OneToOne
   private Dvd dvd;

   // Spring Content managed attributes
   private @ContentId
   UUID contentId;

   private @ContentLength
   Long contentLen;

   @MimeType
   private String mimeType = "text/plain";
}
@RepositoryRestResource(path="dvds", collectionResourceRel="dvds")
public interface DvdRepository extends JpaRepository<Dvd, Long> {}
@StoreRestResource
public interface ImageStore extends ContentStore<Image, UUID> {}
@StoreRestResource
public interface StreamStore extends ContentStore<Stream, UUID> {}

`

com.github.paulcwarren spring-content-fs-boot-starter 1.2.2
    <dependency>
        <groupId>com.github.paulcwarren</groupId>
        <artifactId>spring-content-rest-boot-starter</artifactId>
        <version>1.2.2</version>
    </dependency>

`

Please let me know what is the issue.

Thanks in advance.

paulcwarren commented 3 years ago

Hi @BhanuPrakash531,

Thanks for raising this issue.

Laying content URIs over entity URIs such as /dvds/{id}/image is a little bit problematic actually especially if the expectation is to POST to /dvds/{id}/image to both create a new image entity (if it doesn't yet exist) and associate content with it in a single atomic operation. POSTing to /dvds/{id}/image to create a new image entity isn't actually supported by Spring Data REST. You have to do something like POST to /images first and then PUT to /dvds/{id}image with a text/uri-list request. So, having Spring Content REST behave differently is probably not a great idea.

It is possible that we could support PUTing to /dvds/{id}/image to associate content (and DELETE to unassociate) but before I do so I need to think through what this means for all data modules. It's fine for JPA, of course, but what would it mean for Mongo, for example, with its Document and more hierarchical data models where content may be nested. Or for Cassandra?

paulcwarren commented 3 years ago

Hi @BhanuPrakash531 again.

Further to my previous response. There is a way to get the job done here. Although it means compromising on the content URI. You had previously mentioned using /dvds/<id}/image. But if you add a ImageRepository (and StreamRepository) then you can manage the content directly by:

curl -X POST -H 'Content-Type:application/hal+json' -d '{}' http://localhost:8080/images

to create a new instance of an Image, then:

curl -X PUT -H 'Content-Type:text/plain' -d 'Testing testing testing!' http://localhost:8080/images/2

to set the content, and:

curl -X GET -H 'Accept:text/plain' http://localhost:8080/images/2

to fetch it again.

However, under this alternative you will be responsible for associating the Dvd entity with the Image entity.

HTH

BhanuPrakash531 commented 3 years ago

Thanks @paulcwarren for the detailed explanation. It helped. We can work with this. Also on another project, we have a list of mp3s in resources folder and we don't have any feature to add more mp3s or images. We just need to fetch it from the resources folder where the filename will be set in the pojo on a database and stream it to the UI. What is the best way to do this as per your expert advise? Thanks in advance

paulcwarren commented 3 years ago

For your second use case it sounds like you probably just need a Store. Something like:

@StoreRestResource(path = "mystore")
public interface ResourceStore extends Store<String>

Which essentially is just a Spring ResourceLoader. Providing a Java API to fetch content by filename (rather than entity); i.e. myStore.getResource(filename)

The Spring Content REST endpoints for a Store also allow you to fetch content by filename. Something like:

curl -X GET -H 'Accept: video/mp4 http://localhost:8080/mystore/`

Then you can iterate the filenames from the database and for each make requests (or getResource calls) in order to fetch your content.

But honestly, if your resource aren't dynamic at all and the resources are just stored on the filesystem you might just be better off using something like this.

HTH

paulcwarren commented 3 years ago

Closing due to inactivity. Feel free to re-open if you would like more help.