Repository: Codingpedia/demo-rest-jersey-spring
Branch: master
Commit: a46ab55dbb6e
Files: 53
Total size: 155.9 KB
Directory structure:
gitextract_xeh4_cxd/
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── org/
│ │ └── codingpedia/
│ │ └── demo/
│ │ └── rest/
│ │ ├── RestDemoJaxRsApplication.java
│ │ ├── dao/
│ │ │ ├── PodcastDao.java
│ │ │ ├── PodcastDaoJPA2Impl.java
│ │ │ └── PodcastEntity.java
│ │ ├── errorhandling/
│ │ │ ├── AbstractStatusType.java
│ │ │ ├── AppException.java
│ │ │ ├── AppExceptionMapper.java
│ │ │ ├── CustomReasonPhraseException.java
│ │ │ ├── CustomReasonPhraseExceptionMapper.java
│ │ │ ├── CustomReasonPhraseExceptionStatusType.java
│ │ │ ├── ErrorMessage.java
│ │ │ ├── GenericExceptionMapper.java
│ │ │ └── NotFoundExceptionMapper.java
│ │ ├── filters/
│ │ │ ├── AppConstants.java
│ │ │ ├── CORSResponseFilter.java
│ │ │ └── LoggingResponseFilter.java
│ │ ├── helpers/
│ │ │ ├── DateISO8601Adapter.java
│ │ │ └── NullAwareBeanUtilsBean.java
│ │ ├── interceptors/
│ │ │ ├── Compress.java
│ │ │ └── GZIPWriterInterceptor.java
│ │ ├── resource/
│ │ │ ├── manifest/
│ │ │ │ ├── ImplementationDetails.java
│ │ │ │ ├── ManifestResource.java
│ │ │ │ └── ManifestService.java
│ │ │ └── podcast/
│ │ │ ├── CustomReasonPhraseExceptionMockResource.java
│ │ │ ├── Podcast.java
│ │ │ ├── PodcastDetailedView.java
│ │ │ ├── PodcastLegacyResource.java
│ │ │ └── PodcastsResource.java
│ │ └── service/
│ │ ├── PodcastService.java
│ │ └── PodcastServiceDbAccessImpl.java
│ ├── resources/
│ │ ├── config/
│ │ │ ├── jetty9.xml
│ │ │ └── persistence-demo.xml
│ │ ├── input_data/
│ │ │ ├── DumpRESTdemoDB.sql
│ │ │ ├── DumpRESTdemoDB_legacy.sql
│ │ │ └── populate_db.sql
│ │ ├── logback.xml
│ │ └── spring/
│ │ ├── applicationContext.xml
│ │ └── security-context.xml
│ └── webapp/
│ ├── META-INF/
│ │ ├── .gitignore
│ │ └── context.xml
│ └── WEB-INF/
│ └── web.xml
└── test/
├── java/
│ └── org/
│ └── codingpedia/
│ └── demo/
│ └── rest/
│ └── service/
│ ├── PodcastServiceDbAccessImplTest.java
│ └── integration/
│ └── RestDemoServiceIT.java
└── resources/
├── data-source.xml
├── db.properties
├── jetty-context.xml
├── soapui/
│ └── Test-Demo-REST-Jersey-with-Spring-soapui-project.xml
└── test-applicationContext.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
# Eclipse artifacts
/.project
/.classpath
/.settings
/bin
/target
/.git
/temp.txt
/release.properties
/.idea
/demo-rest-jersey-spring.iml
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2013 Codingpedia.org
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# demo-rest-jersey-spring @ [](http://www.codingpedia.org)
## Prerequisites:
* MySQL 5.5 or 5.6
* IDE ( preffered Eclipse 4.3+)
* JDK 1.7 (if you want to use Jetty 9 with the jetty-maven-plugin from project)
* Maven 3.*
## Install and run the project
1. download/clone the project
2. prepare the database
* import in MySQL the self-contained file that comes with the project - [demo-rest-jersey-spring / src / main / resources / input_data / DumpRESTdemoDB.sql](https://github.com/Codingpedia/demo-rest-jersey-spring/blob/master/src/main/resources/input_data/DumpRESTdemoDB.sql)
* username/password - `rest_demo`/`rest_demo`
3. change to the root folder of the project and excute the following maven command
* `mvn clean install jetty:run -Djetty.port=8888 -DskipTests=true`
* now the REST api is up and running with Jetty on `localhost:8888`
> **Note:** you could run a similar configuration from Eclipse if you have the m2e plugin installed - see pic below
> **Note:** after you `mvn install` the application, you can deploy the generated __.war__ file in any web container like Tomcat for example.
## Testing the project
### From Maven (failsafe plugin)
Run the following maven command on the console in the root directory of the project
```mvn clean install verify -Djetty.port=8888```
OR
the same in Eclipse

### SoapUI (recommended)
- [download and install SoapUI](http://sourceforge.net/projects/soapui/files/)
- import the REST project in SoapUI - [demo-rest-jersey-spring / src / main / resources / soapui / Test-Demo-REST-Jersey-with-Spring-soapui-project.xml](https://github.com/Codingpedia/demo-rest-jersey-spring/blob/master/src/main/resources/soapui/Test-Demo-REST-Jersey-with-Spring-soapui-project.xml)
- check out our [How to test REST API with SoapUI](http://youtu.be/XV7WW0bDy9c) video tutorial on YouTube
## Go to blog post
[Tutorial – REST API design and implementation in Java with Jersey and Spring](http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/) - complete explanation of this implementation.
## Changelog
## License
[MIT](https://github.com/Codingpedia/demo-rest-jersey-spring/blob/master/LICENSE) © [Codingpedia.org](http://www.codingpedia.org)
================================================
FILE: pom.xml
================================================
4.0.0
org.codingpedia
demo-rest-jersey-spring
0.0.1-SNAPSHOT
war
DemoRestWS
Example of building REST web services with spring, jersey and jpa2/hibernate
4.1.4.RELEASE
3.2.5.RELEASE
2.14
1.9.13
4.2.7.Final
9.2.6.v20141205
1.1.1
1.7.6
maven2-repository.java.net
Java.net Repository for Maven
http://download.java.net/maven/2/
scm:https://github.com/Codingpedia/demo-rest-jersey-spring.git
https://github.com/Codingpedia/demo-rest-jersey-spring
org.glassfish.jersey.ext
jersey-spring3
${jersey.version}
org.springframework
spring-core
org.springframework
spring-web
org.springframework
spring-beans
org.glassfish.jersey.media
jersey-media-json-jackson
${jersey.version}
org.glassfish.jersey.media
jersey-media-multipart
${jersey.version}
org.glassfish.jersey.ext
jersey-entity-filtering
${jersey.version}
org.springframework
spring-core
${spring.version}
org.springframework
spring-context
${spring.version}
commons-logging
commons-logging
org.springframework
spring-web
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-orm
${spring.version}
org.springframework.security
spring-security-core
${spring.security.version}
org.springframework.security
spring-security-web
${spring.security.version}
org.springframework.security
spring-security-config
${spring.security.version}
mysql
mysql-connector-java
5.1.27
provided
org.apache.tomcat
tomcat-jdbc
7.0.35
provided
org.hibernate
hibernate-core
${hibernate.version}
org.hibernate.javax.persistence
hibernate-jpa-2.0-api
1.0.1.Final
org.hibernate
hibernate-entitymanager
${hibernate.version}
org.glassfish.jersey.core
jersey-client
${jersey.version}
commons-dbcp
commons-dbcp
1.4
test
commons-beanutils
commons-beanutils
1.9.1
org.springframework
spring-test
${spring.version}
test
junit
junit
4.11
test
org.mockito
mockito-all
1.10.8
org.glassfish.jersey.test-framework.providers
jersey-test-framework-provider-external
2.5.1
ch.qos.logback
logback-classic
${logback.version}
org.slf4j
jcl-over-slf4j
${jcloverslf4j.version}
org.codehaus.jackson
jackson-core-asl
${jackson.version}
org.codehaus.jackson
jackson-mapper-asl
${jackson.version}
org.codehaus.jackson
jackson-jaxrs
${jackson.version}
javax.servlet
javax.servlet-api
3.1.0
provided
org.apache.maven.plugins
maven-war-plugin
2.5
${project.artifactId}
true
true
package
manifest
true
org.apache.maven.plugins
maven-release-plugin
2.5.1
jgit
https://github.com/Codingpedia/demo-rest-jersey-spring/releases
v@{project.version}
org.apache.maven.scm
maven-scm-provider-jgit
1.9.2
org.eclipse.jetty
jetty-maven-plugin
${jetty.maven.plugin.version}
${project.basedir}/src/main/resources/config/jetty9.xml
STOP
9999
5
5
${project.basedir}/src/main
${project.basedir}/src/test
${project.basedir}/src/test/resources/jetty-context.xml
/${project.artifactId}
mysql
mysql-connector-java
5.1.27
failsafeTestRunner
org.apache.maven.plugins
maven-failsafe-plugin
2.16
integration-test
integration-test
verify
verify
org.eclipse.jetty
jetty-maven-plugin
${jetty.maven.plugin.version}
${project.basedir}/src/main/resources/config/jetty9.xml
STOP
9999
5
5
${project.basedir}/src/main
${project.basedir}/src/test
${project.basedir}/src/test/resources/jetty-context.xml
/${project.artifactId}
mysql
mysql-connector-java
5.1.27
start-jetty
pre-integration-test
run-exploded
0
true
stop-jetty
post-integration-test
stop
soapuiTestRunner-local-jetty
com.smartbear.soapui
soapui-maven-plugin
5.0.0
test
test
${project.basedir}/src/test/resources/soapui/Test-Demo-REST-Jersey-with-Spring-soapui-project.xml
${project.build.directory}\soapui-logs\test-results
baseUrl=http://localhost:8888
soapui.logroot
${project.build.directory}/soapui-logs/
soapuiTestRunner-dev-tomcat
com.smartbear.soapui
soapui-maven-plugin
5.0.0
test
test
${project.basedir}/src/test/resources/soapui/Test-Demo-REST-Jersey-with-Spring-soapui-project.xml
${project.build.directory}\soapui-logs\test-results
baseUrl=http://localhost:8080
true
soapui.logroot
${project.build.directory}/soapui-logs/
================================================
FILE: src/main/java/org/codingpedia/demo/rest/RestDemoJaxRsApplication.java
================================================
package org.codingpedia.demo.rest;
import org.glassfish.jersey.message.GZipEncoder;
import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.EncodingFilter;
/**
* Registers the components to be used by the JAX-RS application
*
* @author ama
*
*/
public class RestDemoJaxRsApplication extends ResourceConfig {
/**
* Register JAX-RS application components.
*/
public RestDemoJaxRsApplication() {
packages("org.codingpedia.demo.rest");
// // register application resources
// register(PodcastsResource.class);
// register(PodcastLegacyResource.class);
//
// // register filters
// register(RequestContextFilter.class);
// register(LoggingResponseFilter.class);
// register(CORSResponseFilter.class);
//
// // register exception mappers
// register(GenericExceptionMapper.class);
// register(AppExceptionMapper.class);
// register(CustomReasonPhraseExceptionMapper.class);
// register(NotFoundExceptionMapper.class);
//
// // register features
// register(JacksonFeature.class);
register(EntityFilteringFeature.class);
EncodingFilter.enableFor(this, GZipEncoder.class);
// property(EntityFilteringFeature.ENTITY_FILTERING_SCOPE, new Annotation[] {PodcastDetailedView.Factory.get()});
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/dao/PodcastDao.java
================================================
package org.codingpedia.demo.rest.dao;
import java.util.List;
/**
*
* @author ama
* @see http://www.codingpedia.org/ama/spring-mybatis-integration-example/
*/
public interface PodcastDao {
public List getPodcasts(String orderByInsertionDate);
public List getRecentPodcasts(int numberOfDaysToLookBack);
/**
* Returns a podcast given its id
*
* @param id
* @return
*/
public PodcastEntity getPodcastById(Long id);
/**
* Find podcast by feed
*
* @param feed
* @return the podcast with the feed specified feed or null if not existent
*/
public PodcastEntity getPodcastByFeed(String feed);
public void deletePodcastById(Long id);
public Long createPodcast(PodcastEntity podcast);
public void updatePodcast(PodcastEntity podcast);
/** removes all podcasts */
public void deletePodcasts();
/**
* Returns all podcasts from "legacy" system
* @return
*/
public List getLegacyPodcasts();
/**
* Returns a "legacy" podcast given its id
*
* @param id
* @return
*/
public PodcastEntity getLegacyPodcastById(Long id);
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/dao/PodcastDaoJPA2Impl.java
================================================
package org.codingpedia.demo.rest.dao;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.TypedQuery;
public class PodcastDaoJPA2Impl implements PodcastDao {
@PersistenceContext(unitName="demoRestPersistence")
private EntityManager entityManager;
@PersistenceContext(unitName="demoRestPersistenceLegacy")
private EntityManager entityManagerLegacy;
public List getPodcasts(String orderByInsertionDate) {
String sqlString = null;
if(orderByInsertionDate != null){
sqlString = "SELECT p FROM PodcastEntity p" + " ORDER BY p.insertionDate " + orderByInsertionDate;
} else {
sqlString = "SELECT p FROM PodcastEntity p";
}
TypedQuery query = entityManager.createQuery(sqlString, PodcastEntity.class);
return query.getResultList();
}
public List getRecentPodcasts(int numberOfDaysToLookBack) {
Calendar calendar = new GregorianCalendar();
calendar.setTimeZone(TimeZone.getTimeZone("UTC+1"));//Munich time
calendar.setTime(new Date());
calendar.add(Calendar.DATE, -numberOfDaysToLookBack);//substract the number of days to look back
Date dateToLookBackAfter = calendar.getTime();
String qlString = "SELECT p FROM PodcastEntity p where p.insertionDate > :dateToLookBackAfter ORDER BY p.insertionDate DESC";
TypedQuery query = entityManager.createQuery(qlString, PodcastEntity.class);
query.setParameter("dateToLookBackAfter", dateToLookBackAfter, TemporalType.DATE);
return query.getResultList();
}
public PodcastEntity getPodcastById(Long id) {
try {
String qlString = "SELECT p FROM PodcastEntity p WHERE p.id = ?1";
TypedQuery query = entityManager.createQuery(qlString, PodcastEntity.class);
query.setParameter(1, id);
return query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
public PodcastEntity getPodcastByFeed(String feed) {
try {
String qlString = "SELECT p FROM PodcastEntity p WHERE p.feed = ?1";
TypedQuery query = entityManager.createQuery(qlString, PodcastEntity.class);
query.setParameter(1, feed);
return query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
public void deletePodcastById(Long id) {
PodcastEntity podcast = entityManager.find(PodcastEntity.class, id);
entityManager.remove(podcast);
}
public Long createPodcast(PodcastEntity podcast) {
podcast.setInsertionDate(new Date());
entityManager.merge(podcast);
entityManager.flush();//force insert to receive the id of the podcast
return podcast.getId();
}
public void updatePodcast(PodcastEntity podcast) {
//TODO think about partial update and full update
entityManager.merge(podcast);
}
public void deletePodcasts() {
Query query = entityManager.createNativeQuery("TRUNCATE TABLE podcasts");
query.executeUpdate();
}
public List getLegacyPodcasts() {
String qlString = "SELECT p FROM PodcastEntity p";
TypedQuery query = entityManagerLegacy.createQuery(qlString, PodcastEntity.class);
return query.getResultList();
}
public PodcastEntity getLegacyPodcastById(Long id) {
try {
String qlString = "SELECT p FROM PodcastEntity p WHERE p.id = ?1";
TypedQuery query = entityManagerLegacy.createQuery(qlString, PodcastEntity.class);
query.setParameter(1, id);
return query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/dao/PodcastEntity.java
================================================
package org.codingpedia.demo.rest.dao;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.commons.beanutils.BeanUtils;
import org.codingpedia.demo.rest.resource.podcast.Podcast;
/**
* Podcast entity
*
* @author ama
*
*/
@Entity
@Table(name="podcasts")
public class PodcastEntity implements Serializable {
private static final long serialVersionUID = -8039686696076337053L;
/** id of the podcast */
@Id
@GeneratedValue
@Column(name="id")
private Long id;
/** title of the podcast */
@Column(name="title")
private String title;
/** link of the podcast on Podcastpedia.org */
@Column(name="link_on_podcastpedia")
private String linkOnPodcastpedia;
/** url of the feed */
@Column(name="feed")
private String feed;
/** description of the podcast */
@Column(name="description")
private String description;
/** insertion date in the database */
@Column(name="insertion_date")
private Date insertionDate;
public PodcastEntity(){}
public PodcastEntity(String title, String linkOnPodcastpedia, String feed,
String description) {
this.title = title;
this.linkOnPodcastpedia = linkOnPodcastpedia;
this.feed = feed;
this.description = description;
}
public PodcastEntity(Podcast podcast){
try {
BeanUtils.copyProperties(this, podcast);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getLinkOnPodcastpedia() {
return linkOnPodcastpedia;
}
public void setLinkOnPodcastpedia(String linkOnPodcastpedia) {
this.linkOnPodcastpedia = linkOnPodcastpedia;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFeed() {
return feed;
}
public void setFeed(String feed) {
this.feed = feed;
}
public Date getInsertionDate() {
return insertionDate;
}
public void setInsertionDate(Date insertionDate) {
this.insertionDate = insertionDate;
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/AbstractStatusType.java
================================================
package org.codingpedia.demo.rest.errorhandling;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.Response.StatusType;
/**
* Class used to provide custom StatusTypes, especially for the the Reason Phrase that appears in the HTTP Status Response
*/
public abstract class AbstractStatusType implements StatusType {
public AbstractStatusType(final Family family, final int statusCode,
final String reasonPhrase) {
super();
this.family = family;
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
}
protected AbstractStatusType(final Status status,
final String reasonPhrase) {
this(status.getFamily(), status.getStatusCode(), reasonPhrase);
}
@Override
public Family getFamily() { return family; }
@Override
public String getReasonPhrase() { return reasonPhrase; }
@Override
public int getStatusCode() { return statusCode; }
private final Family family;
private final int statusCode;
private final String reasonPhrase;
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/AppException.java
================================================
package org.codingpedia.demo.rest.errorhandling;
/**
* Class to map application related exceptions
*
* @author amacoder
*
*/
public class AppException extends Exception {
private static final long serialVersionUID = -8999932578270387947L;
/**
* contains redundantly the HTTP status of the response sent back to the client in case of error, so that
* the developer does not have to look into the response headers. If null a default
*/
Integer status;
/** application specific error code */
int code;
/** link documenting the exception */
String link;
/** detailed error description for developers*/
String developerMessage;
/**
*
* @param status
* @param code
* @param message
* @param developerMessage
* @param link
*/
public AppException(int status, int code, String message,
String developerMessage, String link) {
super(message);
this.status = status;
this.code = code;
this.developerMessage = developerMessage;
this.link = link;
}
public AppException() { }
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getDeveloperMessage() {
return developerMessage;
}
public void setDeveloperMessage(String developerMessage) {
this.developerMessage = developerMessage;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/AppExceptionMapper.java
================================================
package org.codingpedia.demo.rest.errorhandling;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class AppExceptionMapper implements ExceptionMapper {
public Response toResponse(AppException ex) {
return Response.status(ex.getStatus())
.entity(new ErrorMessage(ex))
.type(MediaType.APPLICATION_JSON).
build();
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/CustomReasonPhraseException.java
================================================
package org.codingpedia.demo.rest.errorhandling;
public class CustomReasonPhraseException extends Exception {
private static final long serialVersionUID = -271582074543512905L;
private final int businessCode;
public CustomReasonPhraseException(int businessCode, String message) {
super(message);
this.businessCode = businessCode;
}
public int getBusinessCode() {
return businessCode;
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/CustomReasonPhraseExceptionMapper.java
================================================
package org.codingpedia.demo.rest.errorhandling;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class CustomReasonPhraseExceptionMapper implements ExceptionMapper {
public Response toResponse(CustomReasonPhraseException bex) {
return Response.status(new CustomReasonPhraseExceptionStatusType(Status.BAD_REQUEST))
.entity("Custom Reason Phrase exception occured : " + bex.getMessage())
.build();
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/CustomReasonPhraseExceptionStatusType.java
================================================
package org.codingpedia.demo.rest.errorhandling;
import javax.ws.rs.core.Response.Status;
/**
* Implementation of StatusType for CustomReasonPhraseException.
* The Reason Phrase is set in this case to "Custom error message"
*/
public class CustomReasonPhraseExceptionStatusType extends AbstractStatusType{
private static final String CUSTOM_EXCEPTION_REASON_PHRASE = "Custom error message";
public CustomReasonPhraseExceptionStatusType(Status httpStatus) {
super(httpStatus, CUSTOM_EXCEPTION_REASON_PHRASE);
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/ErrorMessage.java
================================================
package org.codingpedia.demo.rest.errorhandling;
import java.lang.reflect.InvocationTargetException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.beanutils.BeanUtils;
@XmlRootElement
public class ErrorMessage {
/** contains the same HTTP Status code returned by the server */
@XmlElement(name = "status")
int status;
/** application specific error code */
@XmlElement(name = "code")
int code;
/** message describing the error*/
@XmlElement(name = "message")
String message;
/** link point to page where the error message is documented */
@XmlElement(name = "link")
String link;
/** extra information that might useful for developers */
@XmlElement(name = "developerMessage")
String developerMessage;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getDeveloperMessage() {
return developerMessage;
}
public void setDeveloperMessage(String developerMessage) {
this.developerMessage = developerMessage;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public ErrorMessage(AppException ex){
try {
BeanUtils.copyProperties(this, ex);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public ErrorMessage(NotFoundException ex){
this.status = Response.Status.NOT_FOUND.getStatusCode();
this.message = ex.getMessage();
this.link = "https://jersey.java.net/apidocs/2.8/jersey/javax/ws/rs/NotFoundException.html";
}
public ErrorMessage() {}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/GenericExceptionMapper.java
================================================
package org.codingpedia.demo.rest.errorhandling;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.codingpedia.demo.rest.filters.AppConstants;
@Provider
public class GenericExceptionMapper implements ExceptionMapper {
public Response toResponse(Throwable ex) {
ErrorMessage errorMessage = new ErrorMessage();
setHttpStatus(ex, errorMessage);
errorMessage.setCode(AppConstants.GENERIC_APP_ERROR_CODE);
errorMessage.setMessage(ex.getMessage());
StringWriter errorStackTrace = new StringWriter();
ex.printStackTrace(new PrintWriter(errorStackTrace));
errorMessage.setDeveloperMessage(errorStackTrace.toString());
errorMessage.setLink(AppConstants.BLOG_POST_URL);
return Response.status(errorMessage.getStatus())
.entity(errorMessage)
.type(MediaType.APPLICATION_JSON)
.build();
}
private void setHttpStatus(Throwable ex, ErrorMessage errorMessage) {
if(ex instanceof WebApplicationException ) { //NICE way to combine both of methods, say it in the blog
errorMessage.setStatus(((WebApplicationException)ex).getResponse().getStatus());
} else {
errorMessage.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); //defaults to internal server error 500
}
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/errorhandling/NotFoundExceptionMapper.java
================================================
package org.codingpedia.demo.rest.errorhandling;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class NotFoundExceptionMapper implements ExceptionMapper {
public Response toResponse(NotFoundException ex) {
return Response.status(ex.getResponse().getStatus())
.entity(new ErrorMessage(ex))
.type(MediaType.APPLICATION_JSON) //this has to be set to get the generated JSON
.build();
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/filters/AppConstants.java
================================================
package org.codingpedia.demo.rest.filters;
public class AppConstants {
public static final int GENERIC_APP_ERROR_CODE = 5001;
public static final String BLOG_POST_URL = "http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/";
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/filters/CORSResponseFilter.java
================================================
package org.codingpedia.demo.rest.filters;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
@Provider
public class CORSResponseFilter
implements ContainerResponseFilter {
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
MultivaluedMap headers = responseContext.getHeaders();
headers.add("Access-Control-Allow-Origin", "*");
//headers.add("Access-Control-Allow-Origin", "http://podcastpedia.org"); //allows CORS requests only coming from podcastpedia.org
headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
headers.add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, X-Codingpedia");
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/filters/LoggingResponseFilter.java
================================================
package org.codingpedia.demo.rest.filters;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Provider
public class LoggingResponseFilter
implements ContainerResponseFilter {
private static final Logger logger = LoggerFactory.getLogger(LoggingResponseFilter.class);
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
String method = requestContext.getMethod();
logger.debug("Requesting " + method + " for path " + requestContext.getUriInfo().getPath());
Object entity = responseContext.getEntity();
if (entity != null) {
logger.debug("Response " + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(entity));
}
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/helpers/DateISO8601Adapter.java
================================================
package org.codingpedia.demo.rest.helpers;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateISO8601Adapter extends XmlAdapter {
private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSZZ";
private SimpleDateFormat dateFormat;
public DateISO8601Adapter() {
super();
dateFormat = new SimpleDateFormat(ISO_8601_DATE_FORMAT);
}
@Override
public Date unmarshal(String v) throws Exception {
return dateFormat.parse(v);
}
@Override
public String marshal(Date v) throws Exception {
return dateFormat.format(v);
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/helpers/NullAwareBeanUtilsBean.java
================================================
package org.codingpedia.demo.rest.helpers;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.beanutils.BeanUtilsBean;
/**
* Stackoverflow solution to copy non-null properties only, see {@link Helper in order to copy non null properties from object to another ? (Java)}
*
*/
public class NullAwareBeanUtilsBean extends BeanUtilsBean{
@Override
public void copyProperty(Object dest, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
if(value==null)return;
super.copyProperty(dest, name, value);
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/interceptors/Compress.java
================================================
package org.codingpedia.demo.rest.interceptors;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.ws.rs.NameBinding;
//@Compress annotation is the name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/interceptors/GZIPWriterInterceptor.java
================================================
package org.codingpedia.demo.rest.interceptors;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
/**
*
* The jersey framework has a built-in functionality to easily enable content encoding.
* The functionality can be activated by the following code line EncodingFilter.enableFor(ResourceConfig rc, GZipEncoder.class)
*
* You can use this WriterInterceptor to selectively compress responses on the method level.
*/
@Provider
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
MultivaluedMap headers = context.getHeaders();
headers.add("Content-Encoding", "gzip");
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/manifest/ImplementationDetails.java
================================================
package org.codingpedia.demo.rest.resource.manifest;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ImplementationDetails {
@XmlElement(name = "implementationTitle")
String implementationTitle;
@XmlElement(name = "implementationVersion")
String implementationVersion;
@XmlElement(name = "implementationVendorId")
String implementationVendorId;
public String getImplementationTitle() {
return implementationTitle;
}
public void setImplementationTitle(String implementationTitle) {
this.implementationTitle = implementationTitle;
}
public String getImplementationVersion() {
return implementationVersion;
}
public void setImplementationVersion(String implementationVersion) {
this.implementationVersion = implementationVersion;
}
public String getImplementationVendorId() {
return implementationVendorId;
}
public void setImplementationVendorId(String implementationVendorId) {
this.implementationVendorId = implementationVendorId;
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/manifest/ManifestResource.java
================================================
package org.codingpedia.demo.rest.resource.manifest;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.jar.Attributes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
@Path("/manifest")
public class ManifestResource {
@Autowired
ManifestService manifestService;
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getManifestAttributes() throws FileNotFoundException, IOException{
Attributes manifestAttributes = manifestService.getManifestAttributes();
return Response.status(Response.Status.OK)
.entity(manifestAttributes)
.build();
}
@Path("/implementation-details")
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getVersion() throws FileNotFoundException, IOException{
ImplementationDetails implementationVersion = manifestService.getImplementationVersion();
return Response.status(Response.Status.OK)
.entity(implementationVersion)
.build();
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/manifest/ManifestService.java
================================================
package org.codingpedia.demo.rest.resource.manifest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
public class ManifestService {
@Autowired
ServletContext context;
Attributes getManifestAttributes() throws FileNotFoundException, IOException{
InputStream resourceAsStream = context.getResourceAsStream("/META-INF/MANIFEST.MF");
Manifest mf = new Manifest();
mf.read(resourceAsStream);
Attributes atts = mf.getMainAttributes();
return atts;
}
ImplementationDetails getImplementationVersion() throws FileNotFoundException, IOException{
String appServerHome = context.getRealPath("/");
File manifestFile = new File(appServerHome, "META-INF/MANIFEST.MF");
Manifest mf = new Manifest();
mf.read(new FileInputStream(manifestFile));
Attributes atts = mf.getMainAttributes();
ImplementationDetails response = new ImplementationDetails();
response.setImplementationTitle(atts.getValue("Implementation-Title"));
response.setImplementationVersion(atts.getValue("Implementation-Version"));
response.setImplementationVendorId(atts.getValue("Implementation-Vendor-Id"));
return response;
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/podcast/CustomReasonPhraseExceptionMockResource.java
================================================
package org.codingpedia.demo.rest.resource.podcast;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.codingpedia.demo.rest.errorhandling.CustomReasonPhraseException;
import org.codingpedia.demo.rest.service.PodcastService;
import org.springframework.beans.factory.annotation.Autowired;
@Path("/mocked-custom-reason-phrase-exception")
public class CustomReasonPhraseExceptionMockResource {
@Autowired
private PodcastService podcastService;
@GET
public void testReasonChangedInResponse() throws CustomReasonPhraseException{
podcastService.generateCustomReasonPhraseException();
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/podcast/Podcast.java
================================================
package org.codingpedia.demo.rest.resource.podcast;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.apache.commons.beanutils.BeanUtils;
import org.codingpedia.demo.rest.dao.PodcastEntity;
import org.codingpedia.demo.rest.helpers.DateISO8601Adapter;
/**
* Podcast resource placeholder for json/xml representation
*
* @author ama
*
*/
@SuppressWarnings("restriction")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Podcast implements Serializable {
private static final long serialVersionUID = -8039686696076337053L;
/** id of the podcast */
@XmlElement(name = "id")
private Long id;
/** title of the podcast */
@XmlElement(name = "title")
private String title;
/** link of the podcast on Podcastpedia.org */
@XmlElement(name = "linkOnPodcastpedia")
private String linkOnPodcastpedia;
/** url of the feed */
@XmlElement(name = "feed")
private String feed;
/** description of the podcast */
@XmlElement(name = "description")
@PodcastDetailedView
private String description;
/** insertion date in the database */
@XmlElement(name = "insertionDate")
@XmlJavaTypeAdapter(DateISO8601Adapter.class)
@PodcastDetailedView
private Date insertionDate;
public Podcast(PodcastEntity podcastEntity){
try {
BeanUtils.copyProperties(this, podcastEntity);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Podcast(String title, String linkOnPodcastpedia, String feed,
String description) {
this.title = title;
this.linkOnPodcastpedia = linkOnPodcastpedia;
this.feed = feed;
this.description = description;
}
public Podcast(){}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getLinkOnPodcastpedia() {
return linkOnPodcastpedia;
}
public void setLinkOnPodcastpedia(String linkOnPodcastpedia) {
this.linkOnPodcastpedia = linkOnPodcastpedia;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFeed() {
return feed;
}
public void setFeed(String feed) {
this.feed = feed;
}
public Date getInsertionDate() {
return insertionDate;
}
public void setInsertionDate(Date insertionDate) {
this.insertionDate = insertionDate;
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/podcast/PodcastDetailedView.java
================================================
package org.codingpedia.demo.rest.resource.podcast;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.glassfish.hk2.api.AnnotationLiteral;
import org.glassfish.jersey.message.filtering.EntityFiltering;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EntityFiltering
public @interface PodcastDetailedView {
/**
* Factory class for creating instances of {@code ProjectDetailedView} annotation.
*/
public static class Factory extends AnnotationLiteral implements PodcastDetailedView {
/**
*
*/
private static final long serialVersionUID = 3052755593743363317L;
private Factory() {
}
public static PodcastDetailedView get() {
return new Factory();
}
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/podcast/PodcastLegacyResource.java
================================================
package org.codingpedia.demo.rest.resource.podcast;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codingpedia.demo.rest.errorhandling.AppException;
import org.codingpedia.demo.rest.service.PodcastService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
* Service class that handles REST requests
*
* @author amacoder
*
*/
@Component
@Path("/legacy/podcasts")
public class PodcastLegacyResource {
@Autowired
private PodcastService podcastService;
/************************************ READ ************************************/
/**
* Returns all resources (podcasts) from the database
*
* @return
* @throws AppException
*/
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public List getPodcasts() throws AppException {
return podcastService.getLegacyPodcasts();
}
@GET
@Path("{id}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response findById(@PathParam("id") Long id) throws AppException {
Podcast podcastById = podcastService.getLegacyPodcastById(id);
if (podcastById != null) {
return Response.status(200).entity(podcastById).build();
} else {
String message = "The podcast with the id " + id + " does not exist";
throw new AppException(404, 4004, message, message, "link");
}
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/resource/podcast/PodcastsResource.java
================================================
package org.codingpedia.demo.rest.resource.podcast;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codingpedia.demo.rest.errorhandling.AppException;
import org.codingpedia.demo.rest.service.PodcastService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
/**
*
* Service class that handles REST requests
*
* @author amacoder
*
*/
@Component
@Path("/podcasts")
public class PodcastsResource {
@Autowired
private PodcastService podcastService;
/*
* *********************************** CREATE ***********************************
*/
/**
* Adds a new resource (podcast) from the given json format (at least title
* and feed elements are required at the DB level)
*
* @param podcast
* @return
* @throws AppException
*/
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response createPodcast(Podcast podcast) throws AppException {
Long createPodcastId = podcastService.createPodcast(podcast);
return Response.status(Response.Status.CREATED)// 201
.entity("A new podcast has been created")
.header("Location",
"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
+ String.valueOf(createPodcastId)).build();
}
/**
* Adds a new podcast (resource) from "form" (at least title and feed
* elements are required at the DB level)
*
* @param title
* @param linkOnPodcastpedia
* @param feed
* @param description
* @return
* @throws AppException
*/
@POST
@Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
@Produces({ MediaType.TEXT_HTML })
public Response createPodcastFromApplicationFormURLencoded(
@FormParam("title") String title,
@FormParam("linkOnPodcastpedia") String linkOnPodcastpedia,
@FormParam("feed") String feed,
@FormParam("description") String description) throws AppException {
Podcast podcast = new Podcast(title, linkOnPodcastpedia, feed,
description);
Long createPodcastid = podcastService.createPodcast(podcast);
return Response
.status(Response.Status.CREATED)// 201
.entity("A new podcast/resource has been created at /demo-rest-jersey-spring/podcasts/"
+ createPodcastid)
.header("Location",
"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
+ String.valueOf(createPodcastid)).build();
}
/**
* A list of resources (here podcasts) provided in json format will be added
* to the database.
*
* @param podcasts
* @return
* @throws AppException
*/
@POST
@Path("list")
@Consumes({ MediaType.APPLICATION_JSON })
public Response createPodcasts(List podcasts) throws AppException {
podcastService.createPodcasts(podcasts);
return Response.status(Response.Status.CREATED) // 201
.entity("List of podcasts was successfully created").build();
}
/*
* *********************************** READ ***********************************
*/
/**
* Returns all resources (podcasts) from the database
*
* @return
* @throws IOException
* @throws JsonMappingException
* @throws JsonGenerationException
* @throws AppException
*/
@GET
//@Compress //can be used only if you want to SELECTIVELY enable compression at the method level. By using the EncodingFilter everything is compressed now.
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public List getPodcasts(
@QueryParam("orderByInsertionDate") String orderByInsertionDate,
@QueryParam("numberDaysToLookBack") Integer numberDaysToLookBack)
throws IOException, AppException {
List podcasts = podcastService.getPodcasts(
orderByInsertionDate, numberDaysToLookBack);
return podcasts;
}
@GET
@Path("{id}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getPodcastById(@PathParam("id") Long id, @QueryParam("detailed") boolean detailed)
throws IOException, AppException {
Podcast podcastById = podcastService.getPodcastById(id);
return Response.status(200)
.entity(podcastById, detailed ? new Annotation[]{PodcastDetailedView.Factory.get()} : new Annotation[0])
.header("Access-Control-Allow-Headers", "X-extra-header")
.allow("OPTIONS").build();
}
// @GET
// @Path("{id}")
// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
// @PodcastDetailedView
// public Podcast getPodcastById(@PathParam("id") Long id, @QueryParam("detailed") boolean detailed)
// throws IOException, AppException {
// Podcast podcastById = podcastService.getPodcastById(id);
//
// return podcastById;
//// return Response.status(200)
//// .entity(podcastById, detailed ? new Annotation[]{PodcastDetailedView.Factory.get()} : new Annotation[0])
//// .header("Access-Control-Allow-Headers", "X-extra-header")
//// .allow("OPTIONS").build();
// }
/*
* *********************************** UPDATE ***********************************
*/
/**
* The method offers both Creation and Update resource functionality. If
* there is no resource yet at the specified location, then a podcast
* creation is executed and if there is then the resource will be full
* updated.
*
* @param id
* @param podcast
* @return
* @throws AppException
*/
@PUT
@Path("{id}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response putPodcastById(@PathParam("id") Long id, Podcast podcast)
throws AppException {
Podcast podcastById = podcastService.verifyPodcastExistenceById(id);
if (podcastById == null) {
// resource not existent yet, and should be created under the
// specified URI
Long createPodcastId = podcastService.createPodcast(podcast);
return Response
.status(Response.Status.CREATED)
// 201
.entity("A new podcast has been created AT THE LOCATION you specified")
.header("Location",
"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
+ String.valueOf(createPodcastId)).build();
} else {
// resource is existent and a full update should occur
podcastService.updateFullyPodcast(podcast);
return Response
.status(Response.Status.OK)
// 200
.entity("The podcast you specified has been fully updated created AT THE LOCATION you specified")
.header("Location",
"http://localhost:8888/demo-rest-jersey-spring/podcasts/"
+ String.valueOf(id)).build();
}
}
// PARTIAL update
@POST
@Path("{id}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_HTML })
public Response partialUpdatePodcast(@PathParam("id") Long id,
Podcast podcast) throws AppException {
podcast.setId(id);
podcastService.updatePartiallyPodcast(podcast);
return Response
.status(Response.Status.OK)
// 200
.entity("The podcast you specified has been successfully updated")
.build();
}
/*
* *********************************** DELETE ***********************************
*/
@DELETE
@Path("{id}")
@Produces({ MediaType.TEXT_HTML })
public Response deletePodcastById(@PathParam("id") Long id) {
podcastService.deletePodcastById(id);
return Response.status(Response.Status.NO_CONTENT)// 204
.entity("Podcast successfully removed from database").build();
}
@DELETE
@Produces({ MediaType.TEXT_HTML })
public Response deletePodcasts() {
podcastService.deletePodcasts();
return Response.status(Response.Status.NO_CONTENT)// 204
.entity("All podcasts have been successfully removed").build();
}
public void setpodcastService(PodcastService podcastService) {
this.podcastService = podcastService;
}
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/service/PodcastService.java
================================================
package org.codingpedia.demo.rest.service;
import java.util.List;
import org.codingpedia.demo.rest.errorhandling.AppException;
import org.codingpedia.demo.rest.errorhandling.CustomReasonPhraseException;
import org.codingpedia.demo.rest.resource.podcast.Podcast;
/**
*
* @author ama
* @see http://www.codingpedia.org/ama/spring-mybatis-integration-example/
*/
public interface PodcastService {
/*
* ******************** Create related methods **********************
* */
public Long createPodcast(Podcast podcast) throws AppException;
public void createPodcasts(List podcasts) throws AppException;
/*
******************** Read related methods ********************
*/
/**
*
* @param orderByInsertionDate - if set, it represents the order by criteria (ASC or DESC) for displaying podcasts
* @param numberDaysToLookBack - if set, it represents number of days to look back for podcasts, null
* @return list with podcasts coressponding to search criterias
* @throws AppException
*/
public List getPodcasts(String orderByInsertionDate, Integer numberDaysToLookBack) throws AppException;
/**
* Returns a podcast given its id
*
* @param id
* @return
* @throws AppException
*/
public Podcast getPodcastById(Long id) throws AppException;
/**
* Returns all podcasts from "legacy" system
* @return
*/
public List getLegacyPodcasts();
/**
* Returns a "legacy" podcast given its id
*
* @param id
* @return
*/
public Podcast getLegacyPodcastById(Long id);
/*
* ******************** Update related methods **********************
* */
public void updateFullyPodcast(Podcast podcast) throws AppException;
public void updatePartiallyPodcast(Podcast podcast) throws AppException;
/*
* ******************** Delete related methods **********************
* */
public void deletePodcastById(Long id);
/** removes all podcasts */
public void deletePodcasts();
/*
* ******************** Helper methods **********************
* */
public Podcast verifyPodcastExistenceById(Long id);
/**
* Empty method generating a Business Exception
* @throws CustomReasonPhraseException
*/
public void generateCustomReasonPhraseException() throws CustomReasonPhraseException;
}
================================================
FILE: src/main/java/org/codingpedia/demo/rest/service/PodcastServiceDbAccessImpl.java
================================================
package org.codingpedia.demo.rest.service;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.core.Response;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.codingpedia.demo.rest.dao.PodcastDao;
import org.codingpedia.demo.rest.dao.PodcastEntity;
import org.codingpedia.demo.rest.errorhandling.AppException;
import org.codingpedia.demo.rest.errorhandling.CustomReasonPhraseException;
import org.codingpedia.demo.rest.filters.AppConstants;
import org.codingpedia.demo.rest.helpers.NullAwareBeanUtilsBean;
import org.codingpedia.demo.rest.resource.podcast.Podcast;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
public class PodcastServiceDbAccessImpl implements PodcastService {
@Autowired
PodcastDao podcastDao;
/********************* Create related methods implementation ***********************/
@Transactional("transactionManager")
public Long createPodcast(Podcast podcast) throws AppException {
validateInputForCreation(podcast);
//verify existence of resource in the db (feed must be unique)
PodcastEntity podcastByFeed = podcastDao.getPodcastByFeed(podcast.getFeed());
if(podcastByFeed != null){
throw new AppException(Response.Status.CONFLICT.getStatusCode(), 409, "Podcast with feed already existing in the database with the id " + podcastByFeed.getId(),
"Please verify that the feed and title are properly generated", AppConstants.BLOG_POST_URL);
}
return podcastDao.createPodcast(new PodcastEntity(podcast));
}
private void validateInputForCreation(Podcast podcast) throws AppException {
if(podcast.getFeed() == null){
throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(), 400, "Provided data not sufficient for insertion",
"Please verify that the feed is properly generated/set", AppConstants.BLOG_POST_URL);
}
if(podcast.getTitle() == null){
throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(), 400, "Provided data not sufficient for insertion",
"Please verify that the title is properly generated/set", AppConstants.BLOG_POST_URL);
}
//etc...
}
@Transactional("transactionManager")
public void createPodcasts(List podcasts) throws AppException {
for (Podcast podcast : podcasts) {
createPodcast(podcast);
}
}
// ******************** Read related methods implementation **********************
public List getPodcasts(String orderByInsertionDate, Integer numberDaysToLookBack) throws AppException {
//verify optional parameter numberDaysToLookBack first
if(numberDaysToLookBack!=null){
List recentPodcasts = podcastDao.getRecentPodcasts(numberDaysToLookBack);
return getPodcastsFromEntities(recentPodcasts);
}
if(isOrderByInsertionDateParameterValid(orderByInsertionDate)){
throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(), 400, "Please set either ASC or DESC for the orderByInsertionDate parameter", null , AppConstants.BLOG_POST_URL);
}
List podcasts = podcastDao.getPodcasts(orderByInsertionDate);
return getPodcastsFromEntities(podcasts);
}
private boolean isOrderByInsertionDateParameterValid(
String orderByInsertionDate) {
return orderByInsertionDate!=null
&& !("ASC".equalsIgnoreCase(orderByInsertionDate) || "DESC".equalsIgnoreCase(orderByInsertionDate));
}
public Podcast getPodcastById(Long id) throws AppException {
PodcastEntity podcastById = podcastDao.getPodcastById(id);
if(podcastById == null){
throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
404,
"The podcast you requested with id " + id + " was not found in the database",
"Verify the existence of the podcast with the id " + id + " in the database",
AppConstants.BLOG_POST_URL);
}
return new Podcast(podcastDao.getPodcastById(id));
}
private List getPodcastsFromEntities(List podcastEntities) {
List response = new ArrayList();
for(PodcastEntity podcastEntity : podcastEntities){
response.add(new Podcast(podcastEntity));
}
return response;
}
public List getRecentPodcasts(int numberOfDaysToLookBack) {
List recentPodcasts = podcastDao.getRecentPodcasts(numberOfDaysToLookBack);
return getPodcastsFromEntities(recentPodcasts);
}
public List getLegacyPodcasts() {
List legacyPodcasts = podcastDao.getLegacyPodcasts();
return getPodcastsFromEntities(legacyPodcasts);
}
public Podcast getLegacyPodcastById(Long id) {
return new Podcast(podcastDao.getLegacyPodcastById(id));
}
/********************* UPDATE-related methods implementation ***********************/
@Transactional("transactionManager")
public void updateFullyPodcast(Podcast podcast) throws AppException {
//do a validation to verify FULL update with PUT
if(isFullUpdate(podcast)){
throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
400,
"Please specify all properties for Full UPDATE",
"required properties - id, title, feed, lnkOnPodcastpedia, description" ,
AppConstants.BLOG_POST_URL);
}
Podcast verifyPodcastExistenceById = verifyPodcastExistenceById(podcast.getId());
if(verifyPodcastExistenceById == null){
throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
404,
"The resource you are trying to update does not exist in the database",
"Please verify existence of data in the database for the id - " + podcast.getId(),
AppConstants.BLOG_POST_URL);
}
podcastDao.updatePodcast(new PodcastEntity(podcast));
}
/**
* Verifies the "completeness" of podcast resource sent over the wire
*
* @param podcast
* @return
*/
private boolean isFullUpdate(Podcast podcast) {
return podcast.getId() == null
|| podcast.getFeed() == null
|| podcast.getLinkOnPodcastpedia() == null
|| podcast.getTitle() == null
|| podcast.getDescription() == null;
}
/********************* DELETE-related methods implementation ***********************/
@Transactional("transactionManager")
public void deletePodcastById(Long id) {
podcastDao.deletePodcastById(id);
}
@Transactional("transactionManager")
public void deletePodcasts() {
podcastDao.deletePodcasts();
}
public Podcast verifyPodcastExistenceById(Long id) {
PodcastEntity podcastById = podcastDao.getPodcastById(id);
if(podcastById == null){
return null;
} else {
return new Podcast(podcastById);
}
}
@Transactional("transactionManager")
public void updatePartiallyPodcast(Podcast podcast) throws AppException {
//do a validation to verify existence of the resource
Podcast verifyPodcastExistenceById = verifyPodcastExistenceById(podcast.getId());
if(verifyPodcastExistenceById == null){
throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
404,
"The resource you are trying to update does not exist in the database",
"Please verify existence of data in the database for the id - " + podcast.getId(),
AppConstants.BLOG_POST_URL);
}
copyPartialProperties(verifyPodcastExistenceById, podcast);
podcastDao.updatePodcast(new PodcastEntity(verifyPodcastExistenceById));
}
private void copyPartialProperties(Podcast verifyPodcastExistenceById,
Podcast podcast) {
BeanUtilsBean notNull=new NullAwareBeanUtilsBean();
try {
notNull.copyProperties(verifyPodcastExistenceById, podcast);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void generateCustomReasonPhraseException() throws CustomReasonPhraseException {
throw new CustomReasonPhraseException(4000, "message attached to the Custom Reason Phrase Exception");
}
public void setPodcastDao(PodcastDao podcastDao) {
this.podcastDao = podcastDao;
}
}
================================================
FILE: src/main/resources/config/jetty9.xml
================================================
jdbc/restDemoDB
jdbc:mysql://localhost:3306/rest_demo?allowMultiQueries=true
rest_demo
rest_demo
jdbc/restDemoLegacyDB
jdbc:mysql://localhost:3306/rest_demo_legacy?allowMultiQueries=true
rest_demo
rest_demo
================================================
FILE: src/main/resources/config/persistence-demo.xml
================================================
org.hibernate.ejb.HibernatePersistence
org.hibernate.ejb.HibernatePersistence
================================================
FILE: src/main/resources/input_data/DumpRESTdemoDB.sql
================================================
CREATE DATABASE IF NOT EXISTS `rest_demo` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `rest_demo`;
-- MySQL dump 10.13 Distrib 5.6.10, for Win64 (x86_64)
--
-- Host: 127.0.0.1 Database: rest_demo
-- ------------------------------------------------------
-- Server version 5.6.10-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `podcasts`
--
DROP TABLE IF EXISTS `podcasts`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `podcasts` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(145) NOT NULL,
`feed` varchar(145) NOT NULL,
`insertion_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`description` varchar(500) DEFAULT NULL,
`link_on_podcastpedia` varchar(145) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `feed_UNIQUE` (`feed`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `podcasts`
--
LOCK TABLES `podcasts` WRITE;
/*!40000 ALTER TABLE `podcasts` DISABLE KEYS */;
INSERT INTO `podcasts` VALUES (1,'Quarks & Co - zum Mitnehmen','http://podcast.wdr.de/quarks.xml','2014-01-09 20:21:10','Quarks & Co: Das Wissenschaftsmagazin','http://www.podcastpedia.org/podcasts/1/Quarks-Co-zum-Mitnehmen'),(2,'- The Naked Scientists Podcast - Stripping Down Science','http://www.thenakedscientists.com/naked_scientists_podcast.xml','2014-01-09 20:21:10','The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.','http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science'),(3,'NPR: TED Radio Hour Podcast','http://www.npr.org/rss/podcast.php?id=510298','2014-01-09 20:21:10','The TED Radio Hour is a journey through fascinating ideas: astonishing inventions, fresh approaches to old problems, new ways to think and create. Based on Talks given by riveting speakers on the world-renowned TED stage, each show is centered on a common theme - such as the source of happiness, crowd-sourcing innovation, power shifts, or inexplicable connections. The TED Radio Hour is hosted by Guy Raz, and is a co-production of NPR & TED. Follow the show @TEDRadioHour.','http://www.podcastpedia.org/podcasts/1183/NPR-TED-Radio-Hour-Podcast');
/*!40000 ALTER TABLE `podcasts` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2014-01-09 20:21:54
================================================
FILE: src/main/resources/input_data/DumpRESTdemoDB_legacy.sql
================================================
CREATE DATABASE IF NOT EXISTS `rest_demo_legacy` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `rest_demo_legacy`;
-- MySQL dump 10.13 Distrib 5.6.10, for Win64 (x86_64)
--
-- Host: 127.0.0.1 Database: rest_demo
-- ------------------------------------------------------
-- Server version 5.6.10-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `podcasts`
--
DROP TABLE IF EXISTS `podcasts`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `podcasts` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(145) NOT NULL,
`feed` varchar(145) NOT NULL,
`insertion_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`description` varchar(500) DEFAULT NULL,
`link_on_podcastpedia` varchar(145) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `feed_UNIQUE` (`feed`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `podcasts`
--
LOCK TABLES `podcasts` WRITE;
/*!40000 ALTER TABLE `podcasts` DISABLE KEYS */;
INSERT INTO `podcasts` VALUES (1,'Quarks & Co - zum Mitnehmen','http://podcast.wdr.de/quarks.xml','2014-01-09 20:21:10','Quarks & Co: Das Wissenschaftsmagazin','http://www.podcastpedia.org/podcasts/1/Quarks-Co-zum-Mitnehmen'),(2,'- The Naked Scientists Podcast - Stripping Down Science','http://www.thenakedscientists.com/naked_scientists_podcast.xml','2014-01-09 20:21:10','The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.','http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science'),(3,'NPR: TED Radio Hour Podcast','http://www.npr.org/rss/podcast.php?id=510298','2014-01-09 20:21:10','The TED Radio Hour is a journey through fascinating ideas: astonishing inventions, fresh approaches to old problems, new ways to think and create. Based on Talks given by riveting speakers on the world-renowned TED stage, each show is centered on a common theme - such as the source of happiness, crowd-sourcing innovation, power shifts, or inexplicable connections. The TED Radio Hour is hosted by Guy Raz, and is a co-production of NPR & TED. Follow the show @TEDRadioHour.','http://www.podcastpedia.org/podcasts/1183/NPR-TED-Radio-Hour-Podcast');
/*!40000 ALTER TABLE `podcasts` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2014-01-09 20:21:54
================================================
FILE: src/main/resources/input_data/populate_db.sql
================================================
select * from podcasts;
insert into podcasts
set title = 'Quarks & Co - zum Mitnehmen',
link_on_podcastpedia ='http://www.podcastpedia.org/podcasts/1/Quarks-Co-zum-Mitnehmen',
feed = 'http://podcast.wdr.de/quarks.xml',
description='Quarks & Co: Das Wissenschaftsmagazin';
insert into podcasts
set title = '- The Naked Scientists Podcast - Stripping Down Science',
link_on_podcastpedia ='http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science',
feed = 'http://www.thenakedscientists.com/naked_scientists_podcast.xml',
description='The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.';
insert into podcasts
set title = 'NPR: TED Radio Hour Podcast',
link_on_podcastpedia ='http://www.podcastpedia.org/podcasts/1183/NPR-TED-Radio-Hour-Podcast',
feed = 'http://www.npr.org/rss/podcast.php?id=510298',
description='The TED Radio Hour is a journey through fascinating ideas: astonishing inventions, fresh approaches to old problems, new ways to think and create. Based on Talks given by riveting speakers on the world-renowned TED stage, each show is centered on a common theme - such as the source of happiness, crowd-sourcing innovation, power shifts, or inexplicable connections. The TED Radio Hour is hosted by Guy Raz, and is a co-production of NPR & TED. Follow the show @TEDRadioHour.';
select * from podcasts;
================================================
FILE: src/main/resources/logback.xml
================================================
%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
TRACE
c:/tmp/rest-demo/rest-demo.log
c:/tmp/rest-demo/archive/rest-demo.%d{yyyy-MM-dd}.log
30
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n
c:/tmp/rest-demo/archive/minutes/rest-demo-minute.%d{yyyy-MM-dd_HH-mm}.log
1
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n
================================================
FILE: src/main/resources/spring/applicationContext.xml
================================================
================================================
FILE: src/main/resources/spring/security-context.xml
================================================
================================================
FILE: src/main/webapp/META-INF/.gitignore
================================================
/MANIFEST.MF
================================================
FILE: src/main/webapp/META-INF/context.xml
================================================
================================================
FILE: src/main/webapp/WEB-INF/web.xml
================================================
Demo - Restful Web Application
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
classpath:spring/applicationContext.xml
classpath:spring/security-context.xml
jersey-servlet
org.glassfish.jersey.servlet.ServletContainer
javax.ws.rs.Application
org.codingpedia.demo.rest.RestDemoJaxRsApplication
1
jersey-servlet
/*
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/manifest/*
Database resource for rest demo web application
jdbc/restDemoDB
javax.sql.DataSource
Container
Database resource for legacy system of demo rest web application
jdbc/restDemoLegacyDB
javax.sql.DataSource
Container
================================================
FILE: src/test/java/org/codingpedia/demo/rest/service/PodcastServiceDbAccessImplTest.java
================================================
package org.codingpedia.demo.rest.service;
import static org.mockito.Mockito.*;
import org.codingpedia.demo.rest.dao.PodcastDao;
import org.codingpedia.demo.rest.dao.PodcastEntity;
import org.codingpedia.demo.rest.errorhandling.AppException;
import org.codingpedia.demo.rest.resource.podcast.Podcast;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PodcastServiceDbAccessImplTest {
private static final Long CREATED_PODCAST_RESOURCE_ID = Long.valueOf(1);
private static final String SOME_FEED = "some_feed";
private static final String SOME_TITLE = "some title";
private static final String EXISTING_FEED = "http://quarks.de/feed";
private static final Long SOME_ID = 13L;
@Rule
public ExpectedException exception = ExpectedException.none();
PodcastServiceDbAccessImpl sut;//system under test
@Mock
PodcastDao podcastDao;
@Before
public void setUp() throws Exception {
sut = new PodcastServiceDbAccessImpl();
sut.setPodcastDao(podcastDao);
}
@Test
public void testCreatePodcast_successful() throws AppException {
when(podcastDao.getPodcastByFeed(SOME_FEED)).thenReturn(null);
when(podcastDao.createPodcast(any(PodcastEntity.class))).thenReturn(CREATED_PODCAST_RESOURCE_ID);
Podcast podcast = new Podcast();
podcast.setFeed(SOME_FEED);
podcast.setTitle(SOME_TITLE);
Long createPodcast = sut.createPodcast(podcast);
verify(podcastDao).getPodcastByFeed(SOME_FEED);//verifies if the method podcastDao.getPodcastByFeed has been called exactly once with that exact input parameter
verify(podcastDao, times(1)).getPodcastByFeed(SOME_FEED);//same as above
verify(podcastDao, times(1)).getPodcastByFeed(eq(SOME_FEED));//same as above
verify(podcastDao, times(1)).getPodcastByFeed(anyString());//verifies if the method podcastDao.getPodcastByFeed has been called exactly once with any string as input
verify(podcastDao, atLeastOnce()).getPodcastByFeed(SOME_FEED);//verifies if the method podcastDao.getPodcastByFeed has been called at least once with that exact input parameter
verify(podcastDao, atLeast(1)).getPodcastByFeed(SOME_FEED);//verifies if the method podcastDao.getPodcastByFeed has been called at least once with that exact input parameter
verify(podcastDao, times(1)).createPodcast(any(PodcastEntity.class));
verify(podcastDao, never()).getLegacyPodcastById(anyLong());//verifies the method podcastDao.getLegacyPodcastById has never been called
Assert.assertTrue(createPodcast == CREATED_PODCAST_RESOURCE_ID);
}
@Test(expected=AppException.class)
public void testCreatePodcast_error() throws AppException {
PodcastEntity existingPodcast = new PodcastEntity();
when(podcastDao.getPodcastByFeed(EXISTING_FEED)).thenReturn(existingPodcast);
Podcast podcast = new Podcast();
podcast.setFeed(EXISTING_FEED);
podcast.setTitle(SOME_TITLE);
sut.createPodcast(podcast);
}
@Test
public void testCreatePodcast_validation_missingFeed() throws AppException {
exception.expect(AppException.class);
exception.expectMessage("Provided data not sufficient for insertion");
sut.createPodcast(new Podcast());
}
@Test
public void testCreatePodcast_validation_missingTitle() throws AppException {
exception.expect(AppException.class);
exception.expectMessage("Provided data not sufficient for insertion");
Podcast podcast = new Podcast();
podcast.setFeed(EXISTING_FEED);
sut.createPodcast(podcast);
}
@Test
public void testUpdatePartiallyPodcast_successful() throws AppException {
PodcastEntity podcastEntity = new PodcastEntity();
podcastEntity.setId(SOME_ID);
when(podcastDao.getPodcastById(SOME_ID)).thenReturn(podcastEntity);
doNothing().when(podcastDao).updatePodcast(any(PodcastEntity.class));
Podcast podcast = new Podcast(podcastEntity);
podcast.setFeed(SOME_FEED);
podcast.setTitle(SOME_TITLE);
sut.updatePartiallyPodcast(podcast);
verify(podcastDao).getPodcastById(SOME_ID);//verifies if the method podcastDao.getPodcastById has been called exactly once with that exact input parameter
verify(podcastDao).updatePodcast(any(PodcastEntity.class));
Assert.assertTrue(podcast.getFeed() == SOME_FEED);
Assert.assertTrue(podcast.getTitle() == SOME_TITLE);
}
@Test
public void testUpdatePartiallyPodcast_not_existing_podcast() {
when(podcastDao.getPodcastById(SOME_ID)).thenReturn(null);
Podcast podcast = new Podcast();
podcast.setId(SOME_ID);
try {
sut.updatePartiallyPodcast(podcast);
Assert.fail("Should have thrown an exception");
} catch (AppException e) {
verify(podcastDao).getPodcastById(SOME_ID);//verifies if the method podcastDao.getPodcastById has been called exactly once with that exact input parameter
Assert.assertEquals(e.getCode(), 404);
}
}
}
================================================
FILE: src/test/java/org/codingpedia/demo/rest/service/integration/RestDemoServiceIT.java
================================================
package org.codingpedia.demo.rest.service.integration;
import java.io.IOException;
import java.util.List;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codingpedia.demo.rest.resource.podcast.Podcast;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.junit.Assert;
import org.junit.Test;
public class RestDemoServiceIT {
@Test
public void testGetPodcasts() throws JsonGenerationException,
JsonMappingException, IOException {
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonFeature.class);
Client client = ClientBuilder.newClient(clientConfig);
WebTarget webTarget = client
.target("http://localhost:8888/demo-rest-jersey-spring/podcasts/");
Builder request = webTarget.request();
request.header("Content-type", MediaType.APPLICATION_JSON);
Response response = request.get();
Assert.assertTrue(response.getStatus() == 200);
List podcasts = response
.readEntity(new GenericType>() {
});
ObjectMapper mapper = new ObjectMapper();
System.out.print(mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(podcasts));
Assert.assertTrue("At least one podcast is present",
podcasts.size() > 0);
}
@Test
public void testGetPodcast() throws JsonGenerationException,
JsonMappingException, IOException {
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonFeature.class);
Client client = ClientBuilder.newClient(clientConfig);
WebTarget webTarget = client
.target("http://localhost:8888/demo-rest-jersey-spring/podcasts/2");
Builder request = webTarget.request(MediaType.APPLICATION_JSON);
Response response = request.get();
Assert.assertTrue(response.getStatus() == 200);
Podcast podcast = response.readEntity(Podcast.class);
ObjectMapper mapper = new ObjectMapper();
System.out
.print("Received podcast from database *************************** "
+ mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(podcast));
}
@Test
public void testGetLegacyPodcasts() throws JsonGenerationException,
JsonMappingException, IOException {
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonFeature.class);
Client client = ClientBuilder.newClient(clientConfig);
WebTarget webTarget = client
.target("http://localhost:8888/demo-rest-jersey-spring/legacy/podcasts/");
Builder request = webTarget.request();
request.header("Content-type", MediaType.APPLICATION_JSON);
Response response = request.get();
Assert.assertTrue(response.getStatus() == 200);
List podcasts = response
.readEntity(new GenericType>() {
});
ObjectMapper mapper = new ObjectMapper();
System.out.print(mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(podcasts));
Assert.assertTrue("At least one podcast is present in the LEGACY",
podcasts.size() > 0);
}
@Test
public void testGetLegacyPodcast() throws JsonGenerationException,
JsonMappingException, IOException {
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonFeature.class);
Client client = ClientBuilder.newClient(clientConfig);
WebTarget webTarget = client
.target("http://localhost:8888/demo-rest-jersey-spring/legacy/podcasts/2");
Builder request = webTarget.request(MediaType.APPLICATION_JSON);
Response response = request.get();
Assert.assertTrue(response.getStatus() == 200);
Podcast podcast = response.readEntity(Podcast.class);
ObjectMapper mapper = new ObjectMapper();
System.out
.print("Received podcast from LEGACY database *************************** "
+ mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(podcast));
}
}
================================================
FILE: src/test/resources/data-source.xml
================================================
================================================
FILE: src/test/resources/db.properties
================================================
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3307/pcmDB?allowMultiQueries=true
db.username=rest_demo
db.password=rest_demo
================================================
FILE: src/test/resources/jetty-context.xml
================================================
org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern
^$
================================================
FILE: src/test/resources/soapui/Test-Demo-REST-Jersey-with-Spring-soapui-project.xml
================================================
${#Project#baseUrl}http://localhost:8080/demo-rest-jersey-springhttp://localhost:8888/demo-rest-jersey-springorderByInsertionDateQUERYapplication/json200Responseapplication/json400 500pod:Fault0data0dataapplication/xml200podcasts${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/No Authorizationapplication/json406 500 409 400pod:Faultapplication/json0data0datatext/html201html0data0data0data0data${#Project#baseUrl}No AuthorizationtitleQUERYlinkOnPodcastpediaQUERYfeedQUERYdescriptionQUERYapplication/json406 500 409 400 415pod:Faultapplication/json0data0datatext/html201html0datamultipart/form-dataapplication/x-www-form-urlencoded${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/No AuthorizationorderByInsertionDatetitlelinkOnPodcastpediafeeddescription204data204data204data204data0data204data404data204data0data204data204data404data404data204data204dataapplication/json500pod:Fault204data204data204data204data0data204data204data${#Project#baseUrl}No AuthorizationididTEMPLATEidapplication/json200ns:Responseapplication/json404 500pod:Fault0data404data404data0dataapplication/xml200podcasttext/html; charset=ISO-8859-1404html0data${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/1No Authorizationapplication/json405 404 500 400ns:Faultapplication/json0datatext/html201 200htmltext/plain400data${#Project#baseUrl}No Authorizationapplication/jsonapplication/json400 404 500ns:Faulttext/html200html0data0data${#Project#baseUrl}No Authorization204data204data204data204data${#Project#baseUrl}No Authorization200data${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/1No Authorizationapplication/json500man:Faultapplication/json200Responseapplication/xml200implementationDetailstext/html; charset=ISO-8859-1401htmltext/html;charset=utf-8401html${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/version/manifest-attributesNo Authorizationtext/html; charset=ISO-8859-1404 500 401htmlapplication/json500 404ver:Fault0dataapplication/xml200versionResponseapplication/json200ver:Response0data0data0data0data0data0datatext/html;charset=utf-8401html${#Project#baseUrl}http://localhost/versionNo AuthorizationSEQUENTIALsysdate${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/204No Authorization${#Project#baseUrl}{
"title":"- The Naked Scientists Podcast - Stripping Down Science-new-title2",
"linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
"description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."
}http://localhost/demo-rest-jersey-spring/podcasts/400Please verify that the feed is properly generated/setfalsefalseNo Authorization${#Project#baseUrl}{
"linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
"feed":"http://www.thenakedscientists.com/naked_scientists_podcast.xml33",
"description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."
}http://localhost/demo-rest-jersey-spring/podcasts/400Please verify that the title is properly generated/setfalsefalseNo Authorization${#Project#baseUrl}{
"title":"- The Naked Scientists Podcast - Stripping Down Science",
"linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
"feed":"feed_placeholder",
"description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."
}http://localhost/demo-rest-jersey-spring/podcasts/201//// check for the location header
assert messageExchange.responseHeaders["Location"] != "http://localhost:8888/demo-rest-jersey-spring/podcasts/1"No Authorization${#Project#baseUrl}{
"title":"- The Naked Scientists Podcast - Stripping Down Science",
"linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
"feed":"feed_placeholder",
"description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."
}http://localhost/demo-rest-jersey-spring/podcasts/409No Authorization${#Project#baseUrl}{
"id":2,
"title":"Quarks & Co - zum Mitnehmen",
"linkOnPodcastpedia":"http://www.podcastpedia.org/quarks",
"feed":"http://podcast.wdr.de/quarks.xml",
"description":"Quarks & Co: Das Wissenschaftsmagazin"
}http://localhost/demo-rest-jersey-spring/podcasts/2201// check for the amazon id header
assert messageExchange.responseHeaders["Location"] != "http://localhost:8888/demo-rest-jersey-spring/podcasts/2"No Authorizationtrue${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/200feed_placeholderfalsefalseNo Authorization${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/200feed_placeholderfalsefalsehttp://podcast.wdr.de/quarks.xmlfalsefalseNo Authorization${#Project#baseUrl}{
"id":2,
"title":"Quarks & Co - zum Mitnehmen",
"linkOnPodcastpedia":"http://www.podcastpedia.org/quarks",
"feed":"http://podcast.wdr.de/quarks.xml"
}http://localhost/demo-rest-jersey-spring/podcasts/2400No Authorization${#Project#baseUrl}{
"id":2,
"title":"Quarks & Co - zum Mitnehmen",
"linkOnPodcastpedia":"http://www.podcastpedia.org/quarks",
"feed":"http://podcast.wdr.de/quarks.xml",
"description":"Quarks & Co: Das Wissenschaftsmagazin"
}http://localhost/demo-rest-jersey-spring/podcasts/2200No Authorization${#Project#baseUrl}{
"title":"Quarks & Co - zum Mitnehmen - GREAT PODCAST"
}http://localhost/demo-rest-jersey-spring/podcasts/3404No Authorization${#Project#baseUrl}{
"title":"Quarks & Co - zum Mitnehmen - GREAT PODCAST"
}http://localhost/demo-rest-jersey-spring/podcasts/3200No Authorization${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/2204No Authorizationtrue${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/404No Authorization${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/podcasts/No Authorization
orderByInsertionDatetitlelinkOnPodcastpediafeeddescriptionSEQUENTIAL${#Project#baseUrl}http://localhost/version200DemoRestWSfalsefalserestrestBasicBasicGlobal HTTP Settings${#Project#baseUrl}http://localhost/demo-rest-jersey-spring/version/manifest-attributes200restrestBasicBasicGlobal HTTP SettingsbaseUrlhttp://localhost:8888/demo-rest-jersey-spring
================================================
FILE: src/test/resources/test-applicationContext.xml
================================================