VideoSvcApi.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package org.magnum.mobilecloud.video.client; import java.util.Collection; import org.magnum.mobilecloud.video.repository.Video; import retrofit.http.Body; import retrofit.http.GET; import retrofit.http.POST; import retrofit.http.Query; /** * This interface defines an API for a VideoSvc. The * interface is used to provide a contract for client/server * interactions. The interface is annotated with Retrofit * annotations so that clients can automatically convert the * * * @author jules * */ public interface VideoSvcApi { public static final String TITLE_PARAMETER = "title"; public static final String DURATION_PARAMETER = "duration"; // The path where we expect the VideoSvc to live public static final String VIDEO_SVC_PATH = "/video"; // The path to search videos by title public static final String VIDEO_TITLE_SEARCH_PATH = VIDEO_SVC_PATH + "/search/findByName"; // The path to search videos by title public static final String VIDEO_DURATION_SEARCH_PATH = VIDEO_SVC_PATH + "/search/findByDurationLessThan"; @GET(VIDEO_SVC_PATH) public Collection<Video> getVideoList(); @POST(VIDEO_SVC_PATH) public Void addVideo(@Body Video v); @GET(VIDEO_TITLE_SEARCH_PATH) public Collection<Video> findByTitle(@Query(TITLE_PARAMETER) String title); @GET(VIDEO_DURATION_SEARCH_PATH) public Collection<Video> findByDurationLessThan(@Query(DURATION_PARAMETER) String title); } |
ResourcesMapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
package org.magnum.mobilecloud.video.json; import java.io.IOException; import org.springframework.hateoas.Resources; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; /** * <Begin long explanation of why this class was created...> * * * By default, Spring Data Rest uses a format called HATEOAS (http://en.wikipedia.org/wiki/HATEOAS) * to output the data returned from a Repository. The results from findAll(), findByName(), etc. are * wrapped in an Object called Resources. When this Resources object is converted to JSON, it adds * additional fields to the JSON so that we don't just get back a list of Video objects. * * For our VideoRepository, the default output would like something like this for the /video : * * { "_links": { "search": { "href": "http://localhost:8080/video/search" } }, "_embedded": { "videos": [ { "name": "Foo", "url": null, "duration": 100, "_links": { "self": { "href": "http://localhost:8080/video/1" } } } ] } } * You can comment out the Application.halObjectMapper() and rerun the application if you would * like to see what the default format looks like with full HATEOAS. * * For this simple example, the extra HATEOAS "_embedded" and "_links" formatting for the top-level * JSON adds extra complexity. Because of the format, we can't just directly unmarshall this response * into a list of Video objects. * * To simplify this example and make it possible to directly unmarshall the responses as a list of * Video objects, this ObjectMapper overrides the default JSON marshalling of Spring Data Rest so * that it outputs this instead: * * [ { "name": "Foo", "url": null, "duration": 100, "_links": { "self": { "href": "http://localhost:8080/video/1" } } } ] * * This alternate format allows us to directly unmarshall the HTTP response bodies from the VideoRepository * into a list of Video objects. * * @author jules * */ public class ResourcesMapper extends ObjectMapper { // This anonymous inner class will handle conversion of the Spring Data Rest // Resources objects into JSON. Resources are objects that Spring Data Rest // creates with the Videos it obtains from your VideoRepository @SuppressWarnings("rawtypes") private JsonSerializer<Resources> serializer = new JsonSerializer<Resources>() { // We are going to register this class to handle all instances of type // Resources @Override public Class<Resources> handledType() { return Resources.class; } @Override public void serialize(Resources value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { // Extracted the actual data inside of the Resources object // that we care about (e.g., the list of Video objects) Object content = value.getContent(); // Instead of all of the Resources member variables, etc. // Just mashall the actual content (Videos) into the JSON JsonSerializer<Object> s = provider.findValueSerializer( content.getClass(), null); s.serialize(content, jgen, provider); } }; // Create an ObjectMapper and tell it to use our customer serializer // to convert Resources objects into JSON public ResourcesMapper() { SimpleModule module = new SimpleModule(); module.addSerializer(serializer); registerModule(module); } } |
Video.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
package org.magnum.mobilecloud.video.repository; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import com.google.common.base.Objects; /** * A simple object to represent a video and its URL for viewing. * * @author jules * */ @Entity public class Video { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; private String url; private long duration; public Video() { } public Video(String name, String url, long duration) { super(); this.name = name; this.url = url; this.duration = duration; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public long getDuration() { return duration; } public void setDuration(long duration) { this.duration = duration; } public long getId() { return id; } public void setId(long id) { this.id = id; } /** * Two Videos will generate the same hashcode if they have exactly the same * values for their name, url, and duration. * */ @Override public int hashCode() { // Google Guava provides great utilities for hashing return Objects.hashCode(name, url, duration); } /** * Two Videos are considered equal if they have exactly the same values for * their name, url, and duration. * */ @Override public boolean equals(Object obj) { if (obj instanceof Video) { Video other = (Video) obj; // Google Guava provides great utilities for equals too! return Objects.equal(name, other.name) && Objects.equal(url, other.url) && duration == other.duration; } else { return false; } } } |
VideoRepository.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package org.magnum.mobilecloud.video.repository; import java.util.Collection; import org.magnum.mobilecloud.video.client.VideoSvcApi; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; /** * An interface for a repository that can store Video * objects and allow them to be searched by title. * * @author jules * */ // This @RepositoryRestResource annotation tells Spring Data Rest to // expose the VideoRepository through a controller and map it to the // "/video" path. This automatically enables you to do the following: // // 1. List all videos by sending a GET request to /video // 2. Add a video by sending a POST request to /video with the JSON for a video // 3. Get a specific video by sending a GET request to /video/{videoId} // (e.g., /video/1 would return the JSON for the video with id=1) // 4. Send search requests to our findByXYZ methods to /video/search/findByXYZ // (e.g., /video/search/findByName?title=Foo) // @RepositoryRestResource(path = VideoSvcApi.VIDEO_SVC_PATH) public interface VideoRepository extends CrudRepository<Video, Long>{ // Find all videos with a matching title (e.g., Video.name) public Collection<Video> findByName( // The @Param annotation tells Spring Data Rest which HTTP request // parameter it should use to fill in the "title" variable used to // search for Videos @Param(VideoSvcApi.TITLE_PARAMETER) String title); // Find all videos that are shorter than a specified duration public Collection<Video> findByDurationLessThan( // The @Param annotation tells tells Spring Data Rest which HTTP request // parameter it should use to fill in the "duration" variable used to // search for Videos @Param(VideoSvcApi.DURATION_PARAMETER) long maxduration); /* * See: http://docs.spring.io/spring-data/jpa/docs/1.3.0.RELEASE/reference/html/jpa.repositories.html * for more examples of writing query methods */ } |
Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package org.magnum.mobilecloud.video; import org.magnum.mobilecloud.video.json.ResourcesMapper; import org.magnum.mobilecloud.video.repository.VideoRepository; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import com.fasterxml.jackson.databind.ObjectMapper; //Tell Spring to automatically inject any dependencies that are marked in //our classes with @Autowired @EnableAutoConfiguration // Tell Spring to automatically create a JPA implementation of our // VideoRepository @EnableJpaRepositories(basePackageClasses = VideoRepository.class) // Tell Spring to turn on WebMVC (e.g., it should enable the DispatcherServlet // so that requests can be routed to our Controllers) @EnableWebMvc // Tell Spring that this object represents a Configuration for the // application @Configuration // Tell Spring to go and scan our controller package (and all sub packages) to // find any Controllers or other components that are part of our applciation. // Any class in this package that is annotated with @Controller is going to be // automatically discovered and connected to the DispatcherServlet. @ComponentScan public class Application extends RepositoryRestMvcConfiguration { // Tell Spring to launch our app! public static void main(String[] args) { SpringApplication.run(Application.class, args); } // We are overriding the bean that RepositoryRestMvcConfiguration // is using to convert our objects into JSON so that we can control // the format. The Spring dependency injection will inject our instance // of ObjectMapper in all of the spring data rest classes that rely // on the ObjectMapper. This is an example of how Spring dependency // injection allows us to easily configure dependencies in code that // we don't have easy control over otherwise. // // Normally, we would not override this object mapping. However, in this // case, we are overriding the JSON conversion so that we can easily // extract a list of videos, etc. using Retrofit. You can remove this // method from the class to see what the default HATEOAS-based responses // from Spring Data Rest look like. You will need to access the server // from your browser as removing this method will break the Retrofit // client. // // See the ResourcesMapper class for more details. @Override public ObjectMapper halObjectMapper(){ return new ResourcesMapper(); } } |