|
| 1 | + |
| 2 | + |
| 3 | +# Getting Started With Spring Content |
| 4 | + |
| 5 | +This guide walks you through building an application that uses Spring Content to store and retrieve content in a database. |
| 6 | + |
| 7 | +## What you'll build |
| 8 | + |
| 9 | +You'll build an application that stores Document POJOs in a Mongo database. |
| 10 | + |
| 11 | +## What you'll need |
| 12 | + |
| 13 | +- About 15 minutes |
| 14 | +- A favorite text editor or IDE |
| 15 | +- JDK 1.8 or later |
| 16 | +- Maven 3.0+ |
| 17 | +- MongoDB 3.0.7 |
| 18 | +- You can also import the code from this guide as well as view the web page directly into Spring Tool Suite (STS) and work your way through it from there. |
| 19 | + |
| 20 | +## How to complete this guide |
| 21 | + |
| 22 | +Like most Spring Getting Started guides, you can start form scratch and complete each step, or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code. |
| 23 | + |
| 24 | +To start from scratch, move on to Build with Maven. |
| 25 | + |
| 26 | +To skip the basics, do the following: |
| 27 | + |
| 28 | +- Download and unzip the source repository for this guide, or clone it using Git: `git clone https://[email protected]/paulcwarren/spring-content.git` |
| 29 | +- cd into spring-content/spring-gs-accessing-data-mongo/initial |
| 30 | +- Jump ahead to `Define a simple entity`. |
| 31 | +When you’re finished, you can check your results against the code in `spring-content/spring-gs-accessing-content-mongo/complete`. |
| 32 | + |
| 33 | +## Build with Maven |
| 34 | + |
| 35 | +First you set up a basic build script. You can use any build system you like when building apps with Spring, but the code you need to work with [Maven](https://maven.apache.org/) is included here. If you’re not familiar with Maven, refer to [Building Java Projects with Maven](http://spring.io/guides/gs/maven). |
| 36 | + |
| 37 | +### Create a directory structure |
| 38 | + |
| 39 | +In a project directory of your choosing, create the following subdirectory structure; for example, with `mkdir -p src/main/java/hello` on *nix systems: |
| 40 | + |
| 41 | +``` |
| 42 | +∟ src |
| 43 | + ∟ main |
| 44 | + ∟ java |
| 45 | + ∟ docs |
| 46 | +``` |
| 47 | + |
| 48 | +`pom.xml` |
| 49 | + |
| 50 | + |
| 51 | + <?xml version="1.0" encoding="UTF-8"?> |
| 52 | + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| 53 | + <modelVersion>4.0.0</modelVersion> |
| 54 | + |
| 55 | + <groupId>org.springframework.content.gs</groupId> |
| 56 | + <artifactId>gs-accessing-content-jpa</artifactId> |
| 57 | + <version>0.1.0</version> |
| 58 | + |
| 59 | + <parent> |
| 60 | + <groupId>org.springframework.boot</groupId> |
| 61 | + <artifactId>spring-boot-starter-parent</artifactId> |
| 62 | + <version>1.2.7.RELEASE</version> |
| 63 | + </parent> |
| 64 | + |
| 65 | + <dependencies> |
| 66 | + <dependency> |
| 67 | + <groupId>org.springframework.boot</groupId> |
| 68 | + <artifactId>spring-boot-starter-data-mongodb</artifactId> |
| 69 | + </dependency> |
| 70 | + <dependency> |
| 71 | + <groupId>org.springframework.boot</groupId> |
| 72 | + <artifactId>spring-boot-starter-data-rest</artifactId> |
| 73 | + </dependency> |
| 74 | + <dependency> |
| 75 | + <groupId>org.springframework.boot</groupId> |
| 76 | + <artifactId>spring-boot-starter-web</artifactId> |
| 77 | + </dependency> |
| 78 | + <dependency> |
| 79 | + <groupId>org.springframework.boot</groupId> |
| 80 | + <artifactId>spring-boot-starter-content-mongo</artifactId> |
| 81 | + <version>1.2.1.RELEASE</version> |
| 82 | + </dependency> |
| 83 | + <dependency> |
| 84 | + <groupId>org.springframework.boot</groupId> |
| 85 | + <artifactId>spring-boot-starter-content-rest</artifactId> |
| 86 | + <version>1.2.1.RELEASE</version> |
| 87 | + </dependency> |
| 88 | + </dependencies> |
| 89 | + |
| 90 | + <properties> |
| 91 | + <java.version>1.8</java.version> |
| 92 | + </properties> |
| 93 | + |
| 94 | + <build> |
| 95 | + <plugins> |
| 96 | + <plugin> |
| 97 | + <groupId>org.springframework.boot</groupId> |
| 98 | + <artifactId>spring-boot-maven-plugin</artifactId> |
| 99 | + </plugin> |
| 100 | + </plugins> |
| 101 | + </build> |
| 102 | + </project> |
| 103 | + |
| 104 | +The [Spring Boot Maven plugin](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-maven-plugin) provides many convenient features: |
| 105 | + |
| 106 | +- It collects all the jars on the classpath and builds a single, runnable "über-jar", which makes it more convenient to execute and transport your service. |
| 107 | +- It searches for the `public static void main()` method to flag as a runnable class. |
| 108 | +- It provides a built-in dependency resolver that sets the version number to match [Spring Boot dependencies](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-dependencies/pom.xml). You can override any version you wish, but it will default to Boot’s chosen set of versions. |
| 109 | + |
| 110 | +### Define a simple entity with content |
| 111 | + |
| 112 | +In this example, you store Document objects, annotated as a Mongo entity with Content. |
| 113 | + |
| 114 | +`src/main/java/docs/SpringDocument.java` |
| 115 | + |
| 116 | + package docs; |
| 117 | + |
| 118 | + import org.springframework.content.annotations.Content; |
| 119 | + import org.springframework.content.annotations.ContentId; |
| 120 | + import org.springframework.content.annotations.ContentLength; |
| 121 | + import org.springframework.data.annotation.Id; |
| 122 | + import org.springframework.data.mongodb.core.mapping.Document; |
| 123 | + |
| 124 | + @Document |
| 125 | + public class SpringDocument { |
| 126 | + |
| 127 | + @Id |
| 128 | + private String id; |
| 129 | + |
| 130 | + private String title; |
| 131 | + private List<String> keywords; |
| 132 | + |
| 133 | + @Content |
| 134 | + private ContentMetadata content; |
| 135 | + |
| 136 | + ... getters and setters ... |
| 137 | + |
| 138 | + public static class ContentMetadata { |
| 139 | + |
| 140 | + public ContentMetadata() {} |
| 141 | + |
| 142 | + @ContentId |
| 143 | + private String id; |
| 144 | + |
| 145 | + @ContentLength |
| 146 | + private long length; |
| 147 | + |
| 148 | + @MimeType |
| 149 | + private String mimeType; |
| 150 | + |
| 151 | + .. getters and setters ... |
| 152 | + |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | +Here you have a standard Spring Data entity bean class `SpringDocument` class with several attributes, `id`, `title`, `keywords`. |
| 159 | + |
| 160 | +>**Note:** For more information on Spring Data annotations see the relevant [Spring Data](http://docs.spring.io/spring-data/commons/docs/current/reference/html/) documentation. |
| 161 | +
|
| 162 | +In addition this entity bean also has the `content` attribute annotated with the Spring Content annotation `@Content`, indicating that instances of `ContentMetadata` are content entities. These entities will be mapped to Mongo's GridFS. |
| 163 | + |
| 164 | +`ContentMetadata` has three attributes, `id`, `length` and `mimeType`. The `id` attribute is annotated with `@ContentId` so that Spring Content will recognize it as the content entity's ID. |
| 165 | + |
| 166 | +The `length` attribute is annotated with `@ContentLength` so that Spring Content will recognize it as the content entity's content length. |
| 167 | + |
| 168 | +Finally, the `mimeType` attribute is annotated with `@MimeType` so that Spring Content REST will recognize it as the content entity's mime type. |
| 169 | + |
| 170 | +All of these annotated attributes will be managed by Spring Content. |
| 171 | + |
| 172 | +### Create a Spring Data Repository |
| 173 | + |
| 174 | +So that we can perform simple CRUD operations, over a hypermedia-based API, create a simple repository for the `SpringDocument` class annotated with as a `@RepositoryRestResource`. |
| 175 | + |
| 176 | +`src/main/java/docs/SpringDocumentRepository.java` |
| 177 | + |
| 178 | + package docs; |
| 179 | + |
| 180 | + import org.springframework.data.repository.CrudRepository; |
| 181 | + import org.springframework.data.rest.core.annotation.RepositoryRestResource; |
| 182 | + |
| 183 | + @RepositoryRestResource(path="/docs", collectionResourceRel="docs") |
| 184 | + public interface SpringDocumentRepository extends CrudRepository<SpringDocument, String> { |
| 185 | + |
| 186 | + } |
| 187 | + |
| 188 | +### Add a Spring Content ContentStore |
| 189 | + |
| 190 | +Just like Spring Data focuses on storing data in a database, Spring Content focuses on storing content in various stores, in this case in Mongo GridFS store. It's most compelling feature is the ability to create content store implementations automatically, at runtime, from a content store interface. |
| 191 | + |
| 192 | +To see how this works, create a content store interface that works with SpringDocument's `ContentMetadata` entity: |
| 193 | + |
| 194 | +`src/main/java/docs/ContentMetadataContentStore.java` |
| 195 | + |
| 196 | + package docs; |
| 197 | + |
| 198 | + import org.springframework.content.common.repository.ContentStore; |
| 199 | + |
| 200 | + import docs.SpringDocument.ContentMetadata; |
| 201 | + import internal.org.springframework.content.rest.annotations.ContentStoreRestResource; |
| 202 | + |
| 203 | + @ContentStoreRestResource |
| 204 | + public interface ContentMetadataContentStore extends ContentStore<ContentMetadata, String> { |
| 205 | + } |
| 206 | + |
| 207 | +`ContentMetadataContentStore` extends the `ContentStore` interface. The type of the content entity and the type of the content entity's ID, `ContentMetadata` and `String` respectively, are specified in the generic parameters on `ContentStore`. By extending `ContentStore`, `ContentMetadataContentStore` inherits several methods for working with persisted content. |
| 208 | + |
| 209 | +In a java application, you would expect to write an implementation for the `ContentMetadaDataContentStore` class. But what makes Spring Content so powerful is that you don't have to do this. Spring Content will create an implementation on the fly when you run the application. |
| 210 | + |
| 211 | +Likewise, in a web-based application you would also expect to implement HTTP handlers allowing you to PUT and GET content over HTTP. With Spring Content REST these are also implemented for you when you add the `ContentStoreRestResource` annotation to your content store. |
| 212 | + |
| 213 | +Let's wire this up and see what it looks like! |
| 214 | + |
| 215 | +### Create an application class |
| 216 | + |
| 217 | +Spring Content integrates seamlessly into Spring Boot, therefore you create the standard Application class. |
| 218 | + |
| 219 | +`src/main/java/docs/Application.java` |
| 220 | + |
| 221 | + package docs; |
| 222 | + |
| 223 | + import org.springframework.boot.SpringApplication; |
| 224 | + import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 225 | + |
| 226 | + @SpringBootApplication |
| 227 | + public class ContentApplication { |
| 228 | + |
| 229 | + public static void main(String[] args) { |
| 230 | + SpringApplication.run(ContentApplication.class); |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | +### Build an executable JAR |
| 235 | + |
| 236 | +You can build a single executable JAR file that contains all the necessary dependencies, classes and resources. This makes it easy to ship, version and deploy. |
| 237 | + |
| 238 | +If you are using Maven, you can run the application using `mvn spring-boot:run`. Or you can build the JAR file with `mvn clean package` and run the JAR by typing: |
| 239 | + |
| 240 | + java -jar target/spring-gs-accessing-content-mongo-0.1.0.jar |
| 241 | + |
| 242 | +> **Note:** The procedure above will create a runnable JAR. You can also opt to [build a classic WAR file](http://spring.io/guides/gs/convert-jar-to-war/) instead. |
| 243 | +
|
| 244 | +### Handle Content |
| 245 | + |
| 246 | +First create an instance of a `SpringDocument`. Using curl, issue the following command: |
| 247 | + |
| 248 | + curl -XPOST -H 'Content-Type:application/json' -d '{"title":"test doc","keywords":["one","two"]}' http://localhost:8080/docs |
| 249 | + |
| 250 | +Check that this `SpringDocument` was created by issing the following command: |
| 251 | + |
| 252 | + curl http://localhost:8080/docs |
| 253 | + |
| 254 | +and this should respond with: |
| 255 | + |
| 256 | + { |
| 257 | + "_embedded" : { |
| 258 | + "docs" : [ { |
| 259 | + "title" : "test doc", |
| 260 | + "keywords" : [ "one", "two" ], |
| 261 | + "content" : null, |
| 262 | + "_links" : { |
| 263 | + "self" : { |
| 264 | + "href" : "http://localhost:8080/docs/5636224fa82677aa529322b6" |
| 265 | + } |
| 266 | + } |
| 267 | + } ] |
| 268 | + } |
| 269 | + } |
| 270 | + |
| 271 | +which shows us there is one document that may be fetched with a `GET` request to `http://localhost:8080/docs/5636224fa82677aa529322b6` |
| 272 | + |
| 273 | +> **Note:** you're IDs will obviously be different, adjust as appropriate |
| 274 | + |
| 275 | +Notice, that the `content` attribute is null. That is because we haven't added any content yet so let's add some, issue the following command: |
| 276 | + |
| 277 | + curl -XPOST -F file=@/tmp/test.txt http://localhost:8080/docs/5636224fa82677aa529322b6/content |
| 278 | + |
| 279 | +> **Note:** In our example /tmp/test.txt contains the simple plain text `Hello Spring Content World!` but this could be any binary content |
| 280 | +
|
| 281 | +Now, re-query the original `SpringDocument` again:- |
| 282 | + |
| 283 | + curl http://localhost:8080/docs/5636224fa82677aa529322b6 |
| 284 | + |
| 285 | +This time it should respond with: |
| 286 | + |
| 287 | + { |
| 288 | + "title" : "test doc", |
| 289 | + "keywords" : [ "one", "two" ], |
| 290 | + "content" : { |
| 291 | + "length" : 28, |
| 292 | + "mimeType" : "text/plain" |
| 293 | + }, |
| 294 | + "_links" : { |
| 295 | + "self" : { |
| 296 | + "href" : "http://localhost:8080/docs/5636224fa82677aa529322b6" |
| 297 | + }, |
| 298 | + "content" : { |
| 299 | + "href" : "http://localhost:8080/docs/5636224fa82677aa529322b6/content/64bf2339-c8e5-44f1-b960-aeb9ea8e4a7e" |
| 300 | + } |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | +We see the `content` attribute now contains useful information about the document's content, namely `length` and `mimeType`. These were set automatically by Spring Content. |
| 305 | + |
| 306 | +Similarly, `_links` also now contains a linkrel for `content` allowing clients to navigate from this `SpringDocument` resource to it's content. Let's do that now, issue the command: |
| 307 | + |
| 308 | + http://localhost:8080/docs/5636224fa82677aa529322b6/content/64bf2339-c8e5-44f1-b960-aeb9ea8e4a7e |
| 309 | + |
| 310 | +which responds: |
| 311 | + |
| 312 | + Hello Spring Content World! |
| 313 | + |
| 314 | +### Summary |
| 315 | + |
| 316 | +Congratulations! You've written a simple application that uses Spring Content and Spring Content REST to save objects with content to a database and to fetch them again using a hypermedia-based REST API - all without writing a single concrete implementation class. |
| 317 | + |
| 318 | +### Want to know more |
| 319 | + |
| 320 | +Read more about the project including it's anticipated backlog [here](spring-content.md). |
0 commit comments