├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── docker ├── base │ └── Dockerfile ├── wildfly-ex │ ├── Dockerfile │ └── wildfly-init-redhat.sh └── wildfly │ └── Dockerfile ├── etc └── checkstyle │ ├── checkstyle.xml │ └── suppressions.xml ├── jaxrs-jwt-filter ├── .gitignore ├── README.md ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── sixturtle │ │ └── jwt │ │ ├── EchoAPI.java │ │ ├── JWTRequestFilter.java │ │ ├── JWTSecured.java │ │ └── RestApplication.java │ ├── resources │ └── secure-keystore.jks │ └── webapp │ ├── WEB-INF │ ├── beans.xml │ ├── jboss-deployment-structure.xml │ ├── jboss-web.xml │ └── web.xml │ └── index.html ├── jee-fuse ├── .gitignore ├── README.md ├── build.gradle ├── etc │ ├── checkstyle │ │ ├── checkstyle.xml │ │ └── suppressions.xml │ └── wildfly │ │ └── standalone │ │ └── configuration │ │ └── standalone.xml ├── gradle.properties └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sixturtle │ │ │ ├── db │ │ │ ├── JPARepository.java │ │ │ ├── JPARepositoryImpl.java │ │ │ ├── PersonRepository.java │ │ │ ├── RoleRepository.java │ │ │ └── UserRepository.java │ │ │ ├── exception │ │ │ ├── InvalidEntityException.java │ │ │ ├── RemoteCallException.java │ │ │ └── UnknownEntityException.java │ │ │ ├── model │ │ │ ├── BasicEntity.java │ │ │ ├── PersonEntity.java │ │ │ ├── RoleEntity.java │ │ │ ├── RoleType.java │ │ │ └── UserEntity.java │ │ │ ├── remote │ │ │ ├── AbstractRestClient.java │ │ │ ├── ClientResponseMapper.java │ │ │ ├── ConfigLoader.java │ │ │ └── service │ │ │ │ ├── EmailStatus.java │ │ │ │ ├── EmailValidator.java │ │ │ │ ├── EmailValidatorImpl.java │ │ │ │ └── MashupContext.java │ │ │ └── web │ │ │ ├── BasicExceptionMapper.java │ │ │ ├── ErrorInfo.java │ │ │ ├── PaginatedModel.java │ │ │ ├── SixturtleApplication.java │ │ │ ├── URLHelper.java │ │ │ └── service │ │ │ ├── PersonService.java │ │ │ └── UserService.java │ ├── resources │ │ ├── META-INF │ │ │ └── persistence.xml │ │ └── config │ │ │ └── mashupConfig.json │ └── webapp │ │ ├── WEB-INF │ │ ├── beans.xml │ │ ├── jboss-deployment-structure.xml │ │ ├── jboss-web.xml │ │ └── web.xml │ │ └── index.html │ └── test │ ├── java │ └── com │ │ └── sixturtle │ │ ├── common │ │ ├── BasicJPATest.java │ │ └── RestApiTest.java │ │ ├── remote │ │ └── service │ │ │ └── EmailValidatorTest.java │ │ └── web │ │ └── service │ │ ├── PersonServiceTest.java │ │ └── UserServiceTest.java │ └── resources │ ├── META-INF │ └── persistence.xml │ ├── __files │ ├── invalid-response.json │ └── valid-response.json │ ├── dbunit │ ├── person-test.xml │ └── user-test.xml │ ├── json │ ├── person-create.json │ └── user-create.json │ └── log4j.xml ├── rest-spring-tomcat ├── .gitignore ├── build.gradle ├── etc │ ├── azure │ │ ├── azuredeploy.json │ │ └── azuredeploy.parameters.json │ └── checkstyle │ │ ├── checkstyle.xml │ │ └── suppressions.xml ├── gradle.properties └── src │ └── main │ ├── java │ └── com │ │ └── sixturtle │ │ ├── configuration │ │ ├── ApplicationConfiguration.java │ │ └── ApplicationInitializer.java │ │ ├── controller │ │ └── PersonController.java │ │ ├── model │ │ ├── Person.java │ │ └── PersonList.java │ │ └── service │ │ └── PersonRepository.java │ └── resources │ ├── application.properties │ ├── import.sql │ └── log4j.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .eclipse/ 2 | .gradle/ 3 | .idea/ 4 | .settings/ 5 | bin/ 6 | build/ 7 | .classpath 8 | .project 9 | *.eml 10 | *.idea 11 | *.iml 12 | *~ 13 | #* 14 | 15 | /bin-test/ 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/sixturtle/examples.png?branch=master)](https://travis-ci.org/sixturtle/examples) 2 | # Examples 3 | This repository contains tutorials and samples. 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | group = 'sixturtle' 3 | version = '1.0' 4 | } 5 | 6 | description = 'Example Application' 7 | 8 | // configure sub projects 9 | subprojects { 10 | 11 | apply plugin: 'eclipse' 12 | apply plugin: 'java' 13 | apply plugin: 'jacoco' 14 | apply plugin: 'maven' 15 | apply plugin: 'checkstyle' 16 | 17 | // all sub projects must use same java version 18 | sourceCompatibility = 1.8 19 | targetCompatibility = 1.8 20 | 21 | // repositories locations 22 | repositories { 23 | mavenLocal() 24 | mavenCentral() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docker/base/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image with CentOS + systemd + OpenJDK 2 | 3 | FROM centos:latest 4 | MAINTAINER Anurag Sharma 5 | 6 | ENV container docker 7 | 8 | # Install updates 9 | RUN yum -y update; yum clean all 10 | 11 | # Install systemd 12 | # Systemd primary task is to manage the boot process and provides information about it. 13 | RUN yum -y install systemd; yum clean all; \ 14 | (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \ 15 | rm -f /lib/systemd/system/multi-user.target.wants/*;\ 16 | rm -f /etc/systemd/system/*.wants/*;\ 17 | rm -f /lib/systemd/system/local-fs.target.wants/*; \ 18 | rm -f /lib/systemd/system/sockets.target.wants/*udev*; \ 19 | rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \ 20 | rm -f /lib/systemd/system/basic.target.wants/*;\ 21 | rm -f /lib/systemd/system/anaconda.target.wants/*; 22 | 23 | # Install OpenJDK Development package 24 | RUN yum -y install unzip java-1.8.0-openjdk-devel && yum clean all 25 | 26 | # Set the JAVA_HOME variable to make it clear where Java is located 27 | ENV JAVA_HOME /usr/lib/jvm/java 28 | ENV PATH $PATH:/$JAVA_HOME/bin 29 | 30 | CMD ["/usr/sbin/init"] 31 | -------------------------------------------------------------------------------- /docker/wildfly-ex/Dockerfile: -------------------------------------------------------------------------------- 1 | ## 2 | # A generic container for wildfly with an option of keeping application specific 3 | # in the mapped volume. The entry point script 'wildfly-init-redhat.sh' does the 4 | # deployment of files from the mapped volume to the $JBOSS_HOME directory. 5 | # 6 | # This container can be used to keep enviroment specific sensitive information 7 | # out of Docker image so that the same image with an env specific mapped volume 8 | # can be used to run the application for a different environment. 9 | # 10 | # Example: 11 | # $ docker build --rm -t sixturtle/wildfly-ex . 12 | # 13 | # 14 | # /opt 15 | # +--/deployment 16 | # +------/dev 17 | # +----------/wildfly 18 | # +------------/bin/init.d 19 | # +----------------wildfly.conf # contains environment specific sensitive values 20 | # +------------/modules/modules/system/layers/base 21 | # +-------------/org/postgres/main 22 | # +-------------------module.xml 23 | # +-------------------postgresql-9.4-1204.jdbc42.jar 24 | # +-------------/standalone 25 | # +---------------/configuration 26 | # +-----------------standalone.xml # template with environment specific variables rather than actual values 27 | # +---------------/deployment 28 | # +-----------------your-app.war 29 | # +------/qa 30 | # +------/prod 31 | # 32 | # Environment: DEV 33 | # $ docker run -d -p 8080:8080 -v /opt/deployment/dev/wildfly:/opt/dist/wildfly --name wildfly-dev sixturtle/wildfly-ex 34 | # $ docker stop wildfly-dev 35 | # $ docker start wildfly-dev 36 | # 37 | # Environment: QA 38 | # $ docker run -d -p 8080:8080 -v /opt/deployment/qa/wildfly:/opt/dist/wildfly --name wildfly-qa sixturtle/wildfly-ex 39 | # $ docker stop wildfly-qa 40 | # $ docker start wildfly-qa 41 | # 42 | 43 | # FROM jboss/wildfly:8.2.0.Final 44 | FROM sixturtle/wildfly:latest 45 | 46 | MAINTAINER Anurag Sharma 47 | 48 | # Setup a distribution directory for app specific files 49 | USER root 50 | 51 | # Anything that is not environment specific can be packaged to the container 52 | # with a set of defaults so that if the container is handed over to someone 53 | # it will work. 54 | # The environment sensitive data can be overriden via /opt/dist/wildfly/bin/init.d/wildfly.conf 55 | #ADD ./deployment/wildfly $JBOSS_HOME/ 56 | 57 | # Map your Wildfly overlay here 58 | RUN mkdir -p /opt/dist/wildfly 59 | 60 | COPY ./wildfly-init-redhat.sh / 61 | RUN chmod +x /wildfly-init-redhat.sh 62 | 63 | USER jboss 64 | ENTRYPOINT ["/wildfly-init-redhat.sh"] 65 | -------------------------------------------------------------------------------- /docker/wildfly-ex/wildfly-init-redhat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ -z "$JBOSS_HOME" ]; then 5 | JBOSS_HOME=/opt/wildfly 6 | fi 7 | export JBOSS_HOME 8 | 9 | if [ -z "$JBOSS_USER" ]; then 10 | JBOSS_USER=jboss 11 | fi 12 | 13 | # Deploy artifacts and overlay customization from a mapped distribution folder 14 | APP_DIST_DIR=/opt/dist/wildfly 15 | if [ -d $APP_DIST_DIR ] && [ "$(ls -A $APP_DIST_DIR)" ]; then 16 | rm $JBOSS_HOME/standalone/deployments/* 17 | cp -r $APP_DIST_DIR/* $JBOSS_HOME 18 | fi 19 | 20 | 21 | # Load Java configuration. 22 | [ -r /etc/java/java.conf ] && . /etc/java/java.conf 23 | export JAVA_HOME 24 | 25 | # Load JBoss AS init.d configuration. 26 | if [ -z "$JBOSS_CONF" ]; then 27 | JBOSS_CONF=$JBOSS_HOME/bin/init.d/wildfly.conf 28 | fi 29 | 30 | [ -r "$JBOSS_CONF" ] && . "${JBOSS_CONF}" 31 | 32 | 33 | # Set defaults. 34 | if [ -z "$JBOSS_BIND_ADDR" ]; then 35 | JBOSS_BIND_ADDR=0.0.0.0 36 | fi 37 | 38 | if [ -z "$JBOSS_BIND_ADDR_MGMT" ]; then 39 | JBOSS_BIND_ADDR_MGMT=0.0.0.0 40 | fi 41 | 42 | 43 | # Startup mode of wildfly 44 | if [ -z "$JBOSS_MODE" ]; then 45 | JBOSS_MODE=standalone 46 | fi 47 | 48 | # Startup mode script 49 | if [ "$JBOSS_MODE" = "standalone" ]; then 50 | JBOSS_SCRIPT=$JBOSS_HOME/bin/standalone.sh 51 | if [ -z "$JBOSS_CONFIG" ]; then 52 | JBOSS_CONFIG=standalone.xml 53 | fi 54 | else 55 | JBOSS_SCRIPT=$JBOSS_HOME/bin/domain.sh 56 | if [ -z "$JBOSS_DOMAIN_CONFIG" ]; then 57 | JBOSS_DOMAIN_CONFIG=domain.xml 58 | fi 59 | if [ -z "$JBOSS_HOST_CONFIG" ]; then 60 | JBOSS_HOST_CONFIG=host.xml 61 | fi 62 | fi 63 | 64 | 65 | if [ "$JBOSS_MODE" = "standalone" ]; then 66 | $JBOSS_SCRIPT -Djboss.bind.address=$JBOSS_BIND_ADDR -Djboss.bind.address.management=$JBOSS_BIND_ADDR_MGMT -c $JBOSS_CONFIG 67 | else 68 | $JBOSS_SCRIPT -Djboss.bind.address=$JBOSS_BIND_ADDR -Djboss.bind.address.management=$JBOSS_BIND_ADDR_MGMT --domain-config=$JBOSS_DOMAIN_CONFIG --host-config=$JBOSS_HOST_CONFIG 69 | fi 70 | -------------------------------------------------------------------------------- /docker/wildfly/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image + wildfly 2 | 3 | FROM sixturtle/base:latest 4 | MAINTAINER Anurag Sharma 5 | 6 | # Create a user and group used to launch processes 7 | RUN groupadd -r jboss -g 1000 && useradd -u 1000 -r -g jboss -m -d /opt/jboss -s /sbin/nologin -c "JBoss user" jboss && \ 8 | chmod 755 /opt/jboss 9 | 10 | # Set the working directory to jboss' user home directory 11 | WORKDIR /opt/jboss 12 | 13 | # Specify the user which should be used to execute all commands below 14 | USER jboss 15 | 16 | ENV WILDFLY_VERSION 8.2.0.Final 17 | ENV JBOSS_HOME /opt/jboss/wildfly 18 | 19 | # Use local downloaded files 20 | RUN cd $HOME \ 21 | && curl -O https://download.jboss.org/wildfly/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \ 22 | && tar xf wildfly-$WILDFLY_VERSION.tar.gz \ 23 | && mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \ 24 | && rm wildfly-$WILDFLY_VERSION.tar.gz 25 | 26 | # Ensure signals are forwarded to the JVM process correctly for graceful shutdown 27 | ENV LAUNCH_JBOSS_IN_BACKGROUND true 28 | 29 | # Expose the ports we're interested in 30 | EXPOSE 8080 31 | 32 | 33 | # Set the default command to run on boot 34 | # This will boot WildFly in the standalone mode and bind to all interface 35 | CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"] 36 | -------------------------------------------------------------------------------- /etc/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /etc/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/.gitignore: -------------------------------------------------------------------------------- 1 | .eclipse/ 2 | .gradle/ 3 | .idea/ 4 | .settings/ 5 | bin/ 6 | build/ 7 | .classpath 8 | .project 9 | *.eml 10 | *.idea 11 | *.iml 12 | *~ 13 | #* 14 | 15 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This sample program demonstrates steps required to secure a REST API. 5 | 6 | 7 | How to Build 8 | ============ 9 | Run following command to get a WAR bundle 10 | 11 | $ gradle clean build 12 | 13 | Once build is successful, the artifact can be found at "build/libs". 14 | 15 | 16 | How to Deploy 17 | ============= 18 | 1. Copy build artifact to Wildfly deployment folder 19 | 20 | $ cp build/libs/jaxrs-jwt-filter.war $JBOSS_HOME/standalone/deployment 21 | 22 | 2. Start Wildfly server 23 | 24 | $ $JBOSS_HOME/bin/run.sh 25 | 26 | 27 | How to Test 28 | =========== 29 | 1. Obtain JWT token 30 | 31 | $ curl --data "username=your-username&password=your-password" https://your-jwt-issuer/protocol/openid-connect/token 32 | 33 | 2. Invoke Echo API 34 | 35 | $ curl -H "Authorization: Bearer " http://127.0.0.1:8080/jaxrs-jwt-filter/api/echo?message=Hello 36 | 37 | 38 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/build.gradle: -------------------------------------------------------------------------------- 1 | // Project definition 2 | description = "A sample program to demonstrate the use of JWT filter to protect a JAX-RS based REST API" 3 | 4 | // plugins 5 | apply plugin: 'war' 6 | apply plugin: 'eclipse-wtp' 7 | 8 | // java version 9 | sourceCompatibility = 1.8 10 | targetCompatibility = 1.8 11 | 12 | // repositories locations 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | 18 | // properties 19 | ext { 20 | jeeVersion = "7.0" 21 | 22 | slf4jVersion = "1.7.2" 23 | log4jVersion = "1.2.17" 24 | 25 | jacksonProvidersVersion = "2.4.1" 26 | 27 | jwtVersion = "4.2" 28 | codecVersion = "1.6" 29 | 30 | hamcrestVersion = "1.3" 31 | mockitoVersion = "1.9.5" 32 | junitVersion = "4.11" 33 | } 34 | 35 | // define standard dependencies 36 | dependencies { 37 | // required for compilation but not packaged 38 | providedCompile ( 39 | ["javax:javaee-api:$jeeVersion"], 40 | ["org.slf4j:slf4j-api:$slf4jVersion"], 41 | ["com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jacksonProvidersVersion"], 42 | ) 43 | 44 | // required for compilation as well as packaged 45 | compile ( 46 | ["com.nimbusds:nimbus-jose-jwt:$jwtVersion"], 47 | ["commons-codec:commons-codec:$codecVersion"] 48 | ) 49 | 50 | 51 | // required only for test 52 | testCompile ( 53 | ["junit:junit:$junitVersion"], 54 | ["org.slf4j:slf4j-log4j12:$slf4jVersion"], 55 | ["org.hamcrest:hamcrest-library:$hamcrestVersion"], 56 | ) 57 | 58 | // using higher version from hamcrest-library and junit 59 | testCompile ("org.mockito:mockito-core:$mockitoVersion") { 60 | exclude group: "org.hamcrest", module: "hamcrest-core" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/java/com/sixturtle/jwt/EchoAPI.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.jwt; 2 | 3 | import javax.annotation.security.RolesAllowed; 4 | import javax.ws.rs.GET; 5 | import javax.ws.rs.Path; 6 | import javax.ws.rs.Produces; 7 | import javax.ws.rs.QueryParam; 8 | import javax.ws.rs.core.Context; 9 | import javax.ws.rs.core.MediaType; 10 | import javax.ws.rs.core.Response; 11 | import javax.ws.rs.core.SecurityContext; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.sixturtle.jwt.JWTRequestFilter.JWTPrincipal; 17 | 18 | /** 19 | * A sample REST endpoint to demonstrate {@link JWTRequestFilter} and {@link JWTSecured}. 20 | */ 21 | @Path("/echo") 22 | @Produces({ MediaType.TEXT_PLAIN }) 23 | public class EchoAPI { 24 | private static final Logger log = LoggerFactory.getLogger(EchoAPI.class); 25 | 26 | @Context 27 | protected SecurityContext securityContext; 28 | 29 | /** 30 | * A sample GET request to demonstrate {@link JWTRequestFilter} 31 | * 32 | * @param message The query parameter 33 | * @return echos back the query parameter 34 | */ 35 | @GET 36 | @JWTSecured 37 | @RolesAllowed("USER") // just for demonstration, check JWTRequestFilter to see what roles are injected to security context 38 | public Response echo(@QueryParam("message") String message) { 39 | JWTPrincipal p = (JWTPrincipal) securityContext.getUserPrincipal(); 40 | log.info("Received message: {} from principal: {}", message, p); 41 | // TODO: inspect principal for fine grain security before proceeding 42 | return Response.ok().entity(message).build(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/java/com/sixturtle/jwt/JWTSecured.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.jwt; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import javax.ws.rs.NameBinding; 9 | 10 | /** 11 | * A name bound annotation to be used with {@link JWTRequestFilter}. 12 | * 13 | * Name-Binding: Filters and interceptors can be name-bound. Name binding is a 14 | * concept that allows to say to a JAX-RS runtime that a specific filter or 15 | * interceptor will be executed only for a specific resource method. When a 16 | * filter or an interceptor is limited only to a specific resource method we say 17 | * that it is name-bound. Filters and interceptors that do not have such a 18 | * limitation are called global. 19 | */ 20 | @NameBinding 21 | @Target({ ElementType.TYPE, ElementType.METHOD }) 22 | @Retention(RetentionPolicy.RUNTIME) 23 | public @interface JWTSecured { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/java/com/sixturtle/jwt/RestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.jwt; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import javax.ws.rs.ApplicationPath; 8 | import javax.ws.rs.core.Application; 9 | 10 | /** 11 | * Responsible for handling the requests to RESTful resources. 12 | * 13 | * @see Application 14 | * @see ApplicationPath 15 | */ 16 | @ApplicationPath("/api/") 17 | public class RestApplication extends Application { 18 | 19 | private Set> classes = new HashSet>(); 20 | 21 | /* 22 | * (non-Javadoc) 23 | * @see javax.ws.rs.core.Application#getClasses() 24 | */ 25 | @Override 26 | public Set> getClasses() { 27 | /* 28 | * Add all the resource service classes here 29 | */ 30 | classes.addAll( 31 | Arrays.asList( 32 | 33 | )); 34 | return classes; 35 | } 36 | } -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/resources/secure-keystore.jks: -------------------------------------------------------------------------------- 1 | This file should be replaced with your custom JKS store file which contains the public key of JWT issuer -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/webapp/WEB-INF/jboss-deployment-structure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/webapp/WEB-INF/jboss-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jaxrs-jwt-filter 4 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | A simple Rest API to demonstrate JWT security 11 | 12 | 13 | 14 | Custom JAX-RS Providers 15 | resteasy.providers 16 | com.sixturtle.jwt.JWTRequestFilter 17 | 18 | 19 | resteasy.role.based.security 20 | true 21 | 22 | 23 | -------------------------------------------------------------------------------- /jaxrs-jwt-filter/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A simple REST API to demonstrate JWT security 6 | 7 | 8 |

Welcome to Echo Web API.

9 | 10 | -------------------------------------------------------------------------------- /jee-fuse/.gitignore: -------------------------------------------------------------------------------- 1 | .eclipse/ 2 | .gradle/ 3 | .idea/ 4 | .settings/ 5 | bin/ 6 | build/ 7 | .classpath 8 | .project 9 | *.eml 10 | *.idea 11 | *.iml 12 | *~ 13 | #* 14 | 15 | /bin-test/ 16 | -------------------------------------------------------------------------------- /jee-fuse/README.md: -------------------------------------------------------------------------------- 1 | # Running in Wildfly Container 2 | 3 | $ export JBOSS_HOME=/opt/wildfly 4 | $ gradle clean build deploy 5 | $ docker run -d -p 8080:8080 -v /opt/wildfly:/opt/dist/wildfly --name wildfly sixturtle/wildfly-ex 6 | 7 | -------------------------------------------------------------------------------- /jee-fuse/build.gradle: -------------------------------------------------------------------------------- 1 | // Project definition 2 | description = 'JEE Fuse Application' 3 | group = "sixturtle" 4 | version = 1.0 5 | 6 | 7 | // Standard plugins 8 | apply plugin: 'war' 9 | apply plugin: 'eclipse-wtp' 10 | apply plugin: 'jacoco' 11 | apply plugin: 'checkstyle' 12 | 13 | 14 | // project properties appended to gradle ext namespace 15 | ext { 16 | // Provided libs 17 | jeeVersion = "7.0" 18 | 19 | slf4jVersion = "1.7.2" 20 | log4jVersion = "1.2.17" 21 | 22 | jacksonProvidersVersion = "2.4.1" 23 | resteasyVersion = "3.0.9.Final" 24 | 25 | hibernateVersion = "4.3.7.Final" 26 | validatorVersion = "5.1.2.Final" 27 | commonsLangVersion = "3.4" 28 | beanutilsVersion = "1.9.2" 29 | 30 | // jUnit libs 31 | junitVersion = "4.11" 32 | hamcrestVersion = "1.3" 33 | mockitoVersion = "1.9.5" 34 | 35 | wiremockVersion = "1.52" 36 | undertowVersion = "1.2.9.Final" 37 | 38 | hsqldbVersion = "2.3.2" 39 | dbunitVersion = "2.4.9" 40 | 41 | // REST Documentation 42 | swaggerVersion = "1.3.12" 43 | swaggerDocletVersion = "1.1.0" 44 | } 45 | 46 | // repositories locations 47 | repositories { 48 | mavenLocal() 49 | mavenCentral() 50 | } 51 | 52 | // define a custom scope for dependencies 53 | configurations { 54 | provided 55 | swagger 56 | } 57 | 58 | configurations.all { 59 | // To examine dependencies, comment failOnVersionConflict() and run following commands to see conflicts 60 | // $ gradle dependencies --configuration testRuntime 61 | // $ gradle dependencyInsight --configuration testRuntime --dependency "org.apache.httpcomponents" 62 | resolutionStrategy { 63 | // fail eagerly on version conflict (includes transitive dependencies) 64 | // e.g. multiple different versions of the same dependency (group and name are equal) 65 | failOnVersionConflict() 66 | } 67 | } 68 | 69 | // configure custom scope of dependencies on the sourceSets 70 | sourceSets { 71 | main { compileClasspath += configurations.provided } 72 | } 73 | eclipse { 74 | classpath { 75 | plusConfigurations += [ configurations.provided ] 76 | } 77 | } 78 | 79 | // checkstyle configuration 80 | checkstyle { 81 | ignoreFailures = false 82 | configFile = new File(rootDir, "etc/checkstyle/checkstyle.xml") 83 | configProperties.checkstyleConfigDir = configFile.parentFile 84 | } 85 | 86 | // define standard dependencies common to all sub projects 87 | dependencies { 88 | // required for compilation and also packaged 89 | compile ( 90 | ["org.apache.commons:commons-lang3:$commonsLangVersion"], 91 | ["commons-beanutils:commons-beanutils:$beanutilsVersion"], 92 | ) 93 | 94 | // required for compilation but not packaged 95 | providedCompile ( 96 | ["javax:javaee-api:$jeeVersion"], 97 | ["org.slf4j:slf4j-api:$slf4jVersion"], 98 | ["com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jacksonProvidersVersion"], 99 | ) 100 | 101 | // required only for generating swagger compliant REST api doc 102 | // https://github.com/teamcarma/swagger-jaxrs-doclet 103 | swagger ( 104 | ["javax:javaee-api:$jeeVersion"], 105 | ["com.carma:swagger-doclet:$swaggerDocletVersion"] 106 | ) 107 | 108 | // required only for test 109 | testCompile ( 110 | ["junit:junit:$junitVersion"], 111 | ["org.slf4j:slf4j-log4j12:$slf4jVersion"], 112 | ["org.hamcrest:hamcrest-library:$hamcrestVersion"], 113 | ["com.fasterxml.jackson.core:jackson-databind:$jacksonProvidersVersion"], 114 | ["org.hibernate:hibernate-validator:$validatorVersion"], 115 | ["org.hibernate:hibernate-entitymanager:$hibernateVersion"], 116 | ["org.hsqldb:hsqldb:$hsqldbVersion"] 117 | ) 118 | // using higher version from hamcrest-library and junit 119 | testCompile ("org.mockito:mockito-core:$mockitoVersion") { 120 | exclude group: "org.hamcrest", module: "hamcrest-core" 121 | } 122 | testCompile ("io.undertow:undertow-servlet:$undertowVersion") { 123 | exclude group: "org.jboss.logging", module: "jboss-logging" 124 | } 125 | testCompile ("org.jboss.resteasy:resteasy-jackson2-provider:$resteasyVersion") { 126 | exclude group: "com.fasterxml.jackson.core", module: "jackson-annotations" 127 | } 128 | testCompile ("org.jboss.resteasy:resteasy-jaxrs:$resteasyVersion") { 129 | exclude group: "commons-io", module: "commons-io" 130 | exclude group: "org.apache.httpcomponents" 131 | } 132 | testCompile ("org.jboss.resteasy:resteasy-undertow:$resteasyVersion") { 133 | //exclude group: "commons-io", module: "commons-io" 134 | exclude group: "org.apache.httpcomponents" 135 | } 136 | testCompile ("org.dbunit:dbunit:$dbunitVersion") { 137 | exclude group: "org.slf4j" 138 | exclude group: "junit" 139 | } 140 | // Remove the duplicate dependencies that wiremock pulls in 141 | testCompile ("com.github.tomakehurst:wiremock:$wiremockVersion") { 142 | exclude group: 'com.fasterxml.jackson.core' 143 | exclude group: 'org.slf4j' 144 | exclude group: 'net.minidev' 145 | exclude group: 'commons-io' 146 | exclude group: 'commons-logging' 147 | } 148 | } 149 | 150 | // Generates swagger compliant REST api documentation 151 | // https://github.com/teamcarma/swagger-jaxrs-doclet 152 | task swagger(type: Javadoc) { 153 | source = sourceSets.main.allJava 154 | destinationDir = reporting.file("swagger") 155 | options.classpath = configurations.compile.files.asType(List) // compile path 156 | options.docletpath = configurations.swagger.files.asType(List) // swagger path 157 | options.doclet = "com.carma.swagger.doclet.ServiceDoclet" 158 | options.addStringOption("apiVersion", currentVersion) 159 | options.addStringOption("docBasePath", "/sixturtle/docs") 160 | options.addStringOption("apiBasePath", "/sixturtle/api") 161 | options.addBooleanOption("skipUiFiles", false) 162 | } 163 | 164 | // Append swagger api docs into war 165 | war { 166 | it.dependsOn swagger 167 | from ("${reportsDir}/swagger", {into 'docs'}) 168 | } 169 | 170 | task copyConfig(type: Copy) { 171 | description 'Copy Wildfly Configuration to JBOSS_HOME' 172 | 173 | def jbossHome = System.getenv()['JBOSS_HOME'] 174 | from ('etc/wildfly') 175 | into jbossHome 176 | } 177 | 178 | // Deploy the WAR to a local JBOSS in standalone mode 179 | task deploy(type: Copy) { 180 | description 'Deploy the WAR to a local JBOSS in standalone mode' 181 | 182 | def jbossHome = System.getenv()['JBOSS_HOME'] 183 | def destDir = jbossHome + '/standalone/deployments/' 184 | from tasks.withType(War) 185 | from tasks.withType(Ear) 186 | into destDir 187 | } 188 | deploy.onlyIf {System.getenv()['JBOSS_HOME']} 189 | deploy.dependsOn copyConfig -------------------------------------------------------------------------------- /jee-fuse/etc/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /jee-fuse/etc/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /jee-fuse/gradle.properties: -------------------------------------------------------------------------------- 1 | currentVersion=1.0.0 2 | org.gradle.daemon=true -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/db/JPARepository.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.db; 2 | 3 | import java.util.List; 4 | 5 | import javax.persistence.EntityManager; 6 | 7 | import com.sixturtle.exception.InvalidEntityException; 8 | import com.sixturtle.exception.UnknownEntityException; 9 | import com.sixturtle.model.BasicEntity; 10 | 11 | /** 12 | * Represents a generic CRUD interface for database entities. 13 | * 14 | * @author Anurag Sharma 15 | * 16 | * @param The Entity Type 17 | * @param The Entity ID Type 18 | */ 19 | public interface JPARepository, L> { 20 | /** 21 | * @return {@link EntityManager} 22 | */ 23 | EntityManager getEntityManager(); 24 | 25 | /** 26 | * @param entity 27 | * {@link BasicEntity} 28 | * @return E entity created 29 | * @throws InvalidEntityException 30 | * when validation fails 31 | */ 32 | E create(final E entity) throws InvalidEntityException; 33 | 34 | /** 35 | * @param entityId 36 | * entityId 37 | * @param entity 38 | * {@link BasicEntity} 39 | * @return E entity updated 40 | * @throws UnknownEntityException 41 | * when entity is not found by entityId 42 | * @throws InvalidEntityException 43 | * when validation fails 44 | */ 45 | E update(final L entityId, final E entity) throws InvalidEntityException, UnknownEntityException; 46 | 47 | /** 48 | * @param entityId 49 | * entityId 50 | * @throws UnknownEntityException 51 | * when entity is not found by entityId 52 | */ 53 | void delete(final L entityId) throws UnknownEntityException; 54 | 55 | /** 56 | * @param entityId 57 | * entityId 58 | * @return E The {@link BasicEntity} type 59 | * @throws UnknownEntityException 60 | * when entity is not found by entityId 61 | */ 62 | E find(final L entityId) throws UnknownEntityException; 63 | 64 | /** 65 | * @param query 66 | * The named query name 67 | * @return Long count 68 | */ 69 | Long count(final String query); 70 | 71 | /** 72 | * @param query 73 | * The named query name 74 | * @param offset 75 | * offset 76 | * @param limit 77 | * limit 78 | * @return {@link List} of {@link BasicEntity} type 79 | */ 80 | List list(final String query, final int offset, final int limit); 81 | } -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/db/JPARepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.db; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | import javax.inject.Inject; 9 | import javax.persistence.EntityManager; 10 | import javax.persistence.EntityNotFoundException; 11 | import javax.persistence.PersistenceContext; 12 | import javax.persistence.Query; 13 | import javax.validation.ConstraintViolation; 14 | import javax.validation.Validator; 15 | 16 | import org.apache.commons.beanutils.BeanUtils; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import com.sixturtle.exception.InvalidEntityException; 21 | import com.sixturtle.exception.UnknownEntityException; 22 | import com.sixturtle.model.BasicEntity; 23 | 24 | /** 25 | * Generic JPA implementation of {@link JPARepository}. 26 | * 27 | * @author Anurag Sharma 28 | * 29 | * @param 30 | * Entity type 31 | * @param l 32 | * Entity Id type 33 | */ 34 | public abstract class JPARepositoryImpl, L> implements JPARepository { 35 | protected static Logger log = LoggerFactory.getLogger(JPARepository.class); 36 | 37 | protected static final String HINT_HIBERNATE_CACHEABLE = "org.hibernate.cacheable"; 38 | 39 | private Class entityClass; 40 | 41 | private EntityManager em; 42 | private Validator validator; 43 | 44 | /** 45 | * Set the {@link EntityManager} or let CDI inject it. 46 | * 47 | * @param em 48 | * An instance of {@link EntityManager} 49 | * 50 | * @see PersistenceContext 51 | */ 52 | @PersistenceContext 53 | public void setEntityManager(final EntityManager em) { 54 | this.em = em; 55 | } 56 | /** 57 | * @return an instance of {@link EntityManager}. 58 | */ 59 | public EntityManager getEntityManager() { 60 | return em; 61 | } 62 | 63 | /** 64 | * Sets the validator. 65 | * 66 | * @param validator 67 | * the new validator 68 | */ 69 | @Inject 70 | public void setValidator(final Validator validator) { 71 | this.validator = validator; 72 | } 73 | 74 | 75 | /* 76 | * (non-Javadoc) 77 | * @see com.sixturtle.db.JPARepository#create(com.sixturtle.model.BasicEntity) 78 | */ 79 | @Override 80 | public E create(final E entity) throws InvalidEntityException { 81 | if (entity == null) { 82 | throw new InvalidEntityException(); 83 | } 84 | 85 | Set> violations = validator.validate(entity); 86 | if (!violations.isEmpty()) { 87 | throw InvalidEntityException.valueOf("Unable to create entity due to validation errors", violations); 88 | } 89 | try { 90 | em.persist(entity); 91 | } catch (final Exception e) { 92 | final String message = String.format("Unexpected error occurred while creating the entity: %s", entity); 93 | log.error(message, e); 94 | throw new RuntimeException(message, e); 95 | } 96 | 97 | return entity; 98 | } 99 | 100 | /* 101 | * (non-Javadoc) 102 | * @see com.sixturtle.db.JPARepository#update(java.lang.Object, com.sixturtle.model.BasicEntity) 103 | */ 104 | @Override 105 | public E update(final L id, final E entity) throws InvalidEntityException, UnknownEntityException { 106 | if (entity == null) { 107 | throw new InvalidEntityException(); 108 | } 109 | 110 | E managed = find(id); 111 | if (managed == null) { 112 | throw new UnknownEntityException("Unable to update because the entity was not found by Id: " + id); 113 | } else { 114 | final Set> violations = validator.validate(entity); 115 | if (!violations.isEmpty()) { 116 | throw InvalidEntityException.valueOf("Unable to update entity due to validation errors", violations); 117 | } 118 | try { 119 | /* 120 | * Copy property values from the origin bean to the destination 121 | * bean for all cases where the property names are the same. 122 | */ 123 | BeanUtils.copyProperties(managed /* destination */, entity /* origin */); 124 | em.persist(managed); 125 | } catch (Exception e) { 126 | final String message = String.format("Unexpected error occurred while updating the entity: %s", entity); 127 | throw new RuntimeException(message, e); 128 | } 129 | } 130 | return managed; 131 | } 132 | 133 | /* 134 | * (non-Javadoc) 135 | * @see com.sixturtle.db.JPARepository#delete(java.lang.Object) 136 | */ 137 | @Override 138 | public void delete(final L id) throws UnknownEntityException { 139 | E managed = find(id); 140 | 141 | if (managed == null) { 142 | throw new UnknownEntityException("Unable to delete because the entity was not found by Id: " + id); 143 | } else { 144 | try { 145 | em.remove(managed); 146 | } catch (final Exception e) { 147 | final String message = String.format("Unexpected error occurred while deleting the entity with id: %s", id); 148 | throw new RuntimeException(message, e); 149 | } 150 | } 151 | } 152 | 153 | /* 154 | * (non-Javadoc) 155 | * @see com.sixturtle.db.JPARepository#count(java.lang.String) 156 | */ 157 | @Override 158 | public Long count(final String queryName) { 159 | Query jpqlQuery = getEntityManager().createNamedQuery(queryName); 160 | return (long) jpqlQuery.setHint(HINT_HIBERNATE_CACHEABLE, true).getSingleResult(); 161 | } 162 | 163 | /* 164 | * (non-Javadoc) 165 | * @see com.sixturtle.controller.BasicRepository#list(int, int) 166 | */ 167 | @SuppressWarnings("unchecked") 168 | @Override 169 | public List list(final String queryName, final int offset, final int limit) { 170 | Query jpqlQuery = getEntityManager().createNamedQuery(queryName); 171 | 172 | return jpqlQuery.setFirstResult(offset).setMaxResults(limit).setHint(HINT_HIBERNATE_CACHEABLE, true) 173 | .getResultList(); 174 | } 175 | 176 | /** 177 | * Finds entity by determining the generic class name. 178 | * 179 | * @param id 180 | * The entity Id 181 | * @return {@link BasicEntity} type 182 | */ 183 | public E find(final L id) { 184 | E entity = null; 185 | try { 186 | entity = em.find(getEntityClass(), id); 187 | } catch (final EntityNotFoundException e) { 188 | log.debug("Entity not found by Id: {}", id); 189 | } catch (final Exception e) { 190 | final String message = String.format("Unexpected error while looking for an entity by Id: %s", id); 191 | log.error(message, e); 192 | throw new RuntimeException(message, e); 193 | } 194 | return entity; 195 | } 196 | 197 | /** 198 | * Get the class of the generic type E. 199 | * 200 | * @return the class of the generic type E 201 | */ 202 | @SuppressWarnings("unchecked") 203 | private Class getEntityClass() { 204 | if (entityClass == null) { 205 | final Type type = getClass().getGenericSuperclass(); 206 | if (type instanceof ParameterizedType) { 207 | final ParameterizedType paramType = (ParameterizedType) type; 208 | entityClass = (Class) paramType.getActualTypeArguments()[0]; 209 | } else { 210 | throw new IllegalArgumentException("Could not guess entity class by reflection"); 211 | } 212 | } 213 | return entityClass; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/db/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.db; 2 | 3 | import javax.inject.Named; 4 | 5 | import com.sixturtle.model.PersonEntity; 6 | 7 | /** 8 | * Represents {@link PersonEntity} repository. 9 | * 10 | * @author Anurag Sharma 11 | */ 12 | @Named 13 | public class PersonRepository extends JPARepositoryImpl { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/db/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.db; 2 | 3 | import javax.inject.Named; 4 | 5 | import com.sixturtle.model.RoleEntity; 6 | 7 | /** 8 | * Represents {@link RoleEntity} repository. 9 | * 10 | * @author Anurag Sharma 11 | */ 12 | @Named 13 | public class RoleRepository extends JPARepositoryImpl { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/db/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.db; 2 | 3 | import javax.inject.Named; 4 | 5 | import com.sixturtle.model.UserEntity; 6 | 7 | /** 8 | * Represents {@link UserEntity} repository. 9 | * 10 | * @author Anurag Sharma 11 | */ 12 | @Named 13 | public class UserRepository extends JPARepositoryImpl { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/exception/InvalidEntityException.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.exception; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | import javax.validation.ConstraintViolation; 8 | 9 | /** 10 | * Represents bean validation exception. This class converts a list of 11 | * violations into a {@link Map} of fields and error messages. 12 | * 13 | * @see persistence.xml for validation-mode 14 | * 15 | * @author Anurag Sharma 16 | */ 17 | public class InvalidEntityException extends Exception { 18 | private static final long serialVersionUID = 1864068115460117002L; 19 | private Map violations; 20 | 21 | /** 22 | * Default constructor. 23 | */ 24 | public InvalidEntityException() { 25 | } 26 | 27 | /** 28 | * Initialize the exception. 29 | * 30 | * @param message 31 | * exception message 32 | * @param violations 33 | * {@link Map} of violations 34 | */ 35 | public InvalidEntityException(final String message, final Map violations) { 36 | super(message + ": " + violations); 37 | this.violations = violations; 38 | } 39 | 40 | /** 41 | * Initialize the exception. 42 | * 43 | * @param message 44 | * exception message 45 | * @param cause 46 | * exception cause 47 | * @param violations 48 | * {@link Map} of violations 49 | */ 50 | public InvalidEntityException(final String message, final Throwable cause, final Map violations) { 51 | super(message + ": " + violations, cause); 52 | this.violations = violations; 53 | } 54 | 55 | /** 56 | * Builds a message list with field and error message for each violations. 57 | * 58 | * @return A concatenated list of violations with field name and error 59 | * message. 60 | */ 61 | public final Map getViolations() { 62 | return violations; 63 | } 64 | 65 | /** 66 | * Converts a {@link Set} of {@link ConstraintViolation} into a {@link Map} 67 | * of (String, String). 68 | * 69 | * @param message 70 | * exception message 71 | * @param violations 72 | * A {@link Set} of {@link ConstraintViolation} 73 | * 74 | * @param 75 | * Generic type 76 | * 77 | * @return {@link Map} of (String, String) 78 | */ 79 | public static InvalidEntityException valueOf(final String message, final Set> violations) { 80 | Map violationsMap = new HashMap(); 81 | for (ConstraintViolation v : violations) { 82 | violationsMap.put(v.getPropertyPath().toString(), v.getMessage()); 83 | } 84 | return new InvalidEntityException(message, violationsMap); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/exception/RemoteCallException.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.exception; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Represents an error that occurred while making a remote call to a 3rd party API. 7 | * 8 | * @author Anurag Sharma 9 | */ 10 | public class RemoteCallException extends IOException { 11 | private static final long serialVersionUID = 5007279622942466295L; 12 | 13 | private int statusCode; 14 | private String responseMessage; 15 | 16 | /** 17 | * Constructor to accept error code and error message. 18 | * 19 | * @param message The error message to include summary 20 | * @param statusCode The error code received from the remote call 21 | * @param responseMessage The native message received from the remote call 22 | */ 23 | public RemoteCallException(final String message, final int statusCode, final String responseMessage) { 24 | super(message); 25 | this.statusCode = statusCode; 26 | this.responseMessage = responseMessage; 27 | } 28 | 29 | /** 30 | * Constructor to accept error code, error message and the cause 31 | * 32 | * @param message 33 | * exception message 34 | * @param cause 35 | * exception cause 36 | * @param statusCode 37 | * status code for the error exception 38 | * @param responseMessage 39 | * response message for the exception 40 | */ 41 | public RemoteCallException(final String message, final Throwable cause, final int statusCode, final String responseMessage) { 42 | super(message, cause); 43 | this.statusCode = statusCode; 44 | this.responseMessage = responseMessage; 45 | } 46 | 47 | /** 48 | * @return the errorCode 49 | */ 50 | public final int getStatusCode() { 51 | return statusCode; 52 | } 53 | 54 | /** 55 | * @return the responseMessage 56 | */ 57 | public final String getResponseMessage() { 58 | return responseMessage; 59 | } 60 | 61 | /** 62 | * @return combined error message 63 | */ 64 | public final String getUnifiedRemoteError() { 65 | return statusCode + ": " + responseMessage; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/exception/UnknownEntityException.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.exception; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | import javax.validation.ConstraintViolation; 8 | 9 | /** 10 | * Represents an error to indicate that the data is not found. 11 | * 12 | * @author Anurag Sharma 13 | */ 14 | public class UnknownEntityException extends Exception { 15 | private static final long serialVersionUID = 5849318601242477783L; 16 | private Map violations; // (field, message) tuple 17 | 18 | /** 19 | * Default constructor. 20 | */ 21 | public UnknownEntityException() { 22 | 23 | } 24 | 25 | /** 26 | * Constructor that takes message 27 | * 28 | * @param message 29 | * The error message 30 | */ 31 | public UnknownEntityException(String message) { 32 | super(message); 33 | } 34 | 35 | /** 36 | * Constructor with violation map and cause of exception. 37 | * 38 | * @param message 39 | * The error message 40 | * @param violations 41 | * A map of (field, error message) 42 | */ 43 | public UnknownEntityException(final String message, final Map violations) { 44 | super(message); 45 | this.violations = violations; 46 | } 47 | 48 | /** 49 | * Constructor with violation map and cause of exception. 50 | * 51 | * @param message 52 | * The error message 53 | * @param violations 54 | * A map of (field, error message) 55 | * @param cause 56 | * The chained exception 57 | */ 58 | public UnknownEntityException(final String message, final Map violations, final Throwable cause) { 59 | super(message, cause); 60 | this.violations = violations; 61 | } 62 | 63 | /** 64 | * @return the violations 65 | */ 66 | public final Map getViolations() { 67 | return violations; 68 | } 69 | 70 | /** 71 | * Converts a {@link Set} of {@link ConstraintViolation} into a {@link Map} 72 | * of (String, String). 73 | * 74 | * @param message 75 | * The error message 76 | * @param violations 77 | * A {@link Set} of {@link ConstraintViolation} 78 | * @param 79 | * Generic type 80 | * @return A {@link Map} of (String, String) 81 | */ 82 | public static UnknownEntityException valueOf(final String message, final Set> violations) { 83 | Map violationsMap = new HashMap(); 84 | for (ConstraintViolation v : violations) { 85 | violationsMap.put(v.getPropertyPath().toString(), v.getMessage()); 86 | } 87 | return new UnknownEntityException(message, violationsMap); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/model/BasicEntity.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.xml.bind.annotation.XmlRootElement; 6 | 7 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 10 | 11 | /** 12 | * Super class of managed entity beans to enforce primary key type. This would 13 | * help implement a generic repository. 14 | * 15 | * @author Anurag Sharma 16 | * @param 17 | * The data type of primary key 18 | */ 19 | @XmlRootElement 20 | @JsonInclude(Include.NON_NULL) 21 | @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) 22 | public interface BasicEntity extends Serializable { 23 | /** 24 | * @return The primary key of the entity 25 | */ 26 | E getId(); 27 | } 28 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/model/PersonEntity.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Cacheable; 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import javax.persistence.NamedQueries; 11 | import javax.persistence.NamedQuery; 12 | import javax.persistence.SequenceGenerator; 13 | import javax.persistence.Table; 14 | import javax.persistence.Version; 15 | import javax.validation.constraints.NotNull; 16 | import javax.validation.constraints.Size; 17 | 18 | 19 | /** 20 | * Person entity in database. 21 | * 22 | * @author Anurag Sharma 23 | */ 24 | @Entity 25 | @Cacheable 26 | @Table(name = "PERSON") 27 | @SequenceGenerator(name = "PERSON_ID_GENERATOR", sequenceName = "PERSON_ID_SEQ") 28 | @NamedQueries({ 29 | @NamedQuery( 30 | name = PersonEntity.QUERY_FIND_ALL, 31 | query = "SELECT p FROM PersonEntity p"), 32 | @NamedQuery( 33 | name = PersonEntity.QUERY_COUNT_ALL, 34 | query = "SELECT COUNT(1) FROM PersonEntity p") 35 | }) 36 | public class PersonEntity implements BasicEntity, Serializable { 37 | private static final long serialVersionUID = 8990255112980205427L; 38 | public static final String QUERY_FIND_ALL = "PersonEntity.findAll"; 39 | public static final String QUERY_COUNT_ALL = "PersonEntity.countAll"; 40 | 41 | @Id 42 | @Column(name = "ID") 43 | @GeneratedValue(generator = "PERSON_ID_GENERATOR") 44 | private Long id; 45 | 46 | @NotNull @Size(min = 1, max = 255) 47 | @Column(name = "FIRST_NAME", length = 255) 48 | private String firstName; 49 | 50 | @Column(name = "MIDDLE_NAME", length = 255) 51 | private String middleName; 52 | 53 | @NotNull @Size(min = 1, max = 255) 54 | @Column(name = "LAST_NAME", length = 255) 55 | private String lastName; 56 | 57 | @NotNull @Size(min = 1, max = 255) 58 | @Column(name = "EMAIL", nullable = false, length = 255, unique = true) 59 | private String email; 60 | 61 | @NotNull @Size(min = 1, max = 20) 62 | @Column(name = "PHONE", length = 20) 63 | private String phone; 64 | 65 | @Version 66 | private Long version; 67 | 68 | 69 | /** 70 | * Default constructor 71 | */ 72 | public PersonEntity() { 73 | } 74 | 75 | /** 76 | * Creates a {@link PersonEntity} object using following parameters. 77 | * 78 | * @param firstName 79 | * The first name 80 | * @param middleName 81 | * The middle name 82 | * @param lastName 83 | * The last name 84 | * @param email 85 | * The email 86 | * @param phone 87 | * The phone 88 | */ 89 | public PersonEntity( 90 | final String firstName, 91 | final String middleName, 92 | final String lastName, 93 | final String email, 94 | final String phone) { 95 | this.firstName = firstName; 96 | this.middleName = middleName; 97 | this.lastName = lastName; 98 | this.email = email; 99 | this.phone = phone; 100 | } 101 | 102 | /** 103 | * @return the id 104 | */ 105 | public Long getId() { 106 | return id; 107 | } 108 | /** 109 | * @return the firstName 110 | */ 111 | public String getFirstName() { 112 | return firstName; 113 | } 114 | /** 115 | * @param firstName the firstName to set 116 | */ 117 | public void setFirstName(String firstName) { 118 | this.firstName = firstName; 119 | } 120 | /** 121 | * @return the middleName 122 | */ 123 | public String getMiddleName() { 124 | return middleName; 125 | } 126 | /** 127 | * @param middleName the middleName to set 128 | */ 129 | public void setMiddleName(String middleName) { 130 | this.middleName = middleName; 131 | } 132 | /** 133 | * @return the lastName 134 | */ 135 | public String getLastName() { 136 | return lastName; 137 | } 138 | /** 139 | * @param lastName the lastName to set 140 | */ 141 | public void setLastName(String lastName) { 142 | this.lastName = lastName; 143 | } 144 | /** 145 | * @return the email 146 | */ 147 | public String getEmail() { 148 | return email; 149 | } 150 | /** 151 | * @param email the email to set 152 | */ 153 | public void setEmail(String email) { 154 | this.email = email; 155 | } 156 | /** 157 | * @return the phone 158 | */ 159 | public String getPhone() { 160 | return phone; 161 | } 162 | /** 163 | * @param phone the phone to set 164 | */ 165 | public void setPhone(String phone) { 166 | this.phone = phone; 167 | } 168 | 169 | /* 170 | * (non-Javadoc) 171 | * @see java.lang.Object#toString() 172 | */ 173 | @Override 174 | public String toString() { 175 | StringBuilder builder = new StringBuilder(); 176 | 177 | builder.append("Person {") 178 | .append("id:").append(id).append(",") 179 | .append("firstName:").append(firstName).append(",") 180 | .append("middleName:").append(middleName).append(",") 181 | .append("lastName:").append(lastName).append(",") 182 | .append("email:").append(email).append(",") 183 | .append("phone:").append(phone) 184 | .append('}'); 185 | 186 | return builder.toString(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/model/RoleEntity.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.EnumType; 8 | import javax.persistence.Enumerated; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | import javax.persistence.NamedQueries; 12 | import javax.persistence.NamedQuery; 13 | import javax.persistence.SequenceGenerator; 14 | import javax.persistence.Table; 15 | import javax.persistence.Version; 16 | import javax.validation.constraints.NotNull; 17 | 18 | 19 | /** 20 | * 21 | * Role entity in database. A user can have more than one role. 22 | * 23 | * @author Anurag Sharma 24 | */ 25 | @Entity 26 | @Table(name = "ROLE") 27 | @SequenceGenerator(name = "ROLE_ID_GENERATOR", sequenceName = "ROLE_ID_SEQ") 28 | @NamedQueries({ 29 | @NamedQuery( 30 | name = RoleEntity.QUERY_FIND_BY_ROLE_TYPE, 31 | query = "SELECT r FROM RoleEntity r WHERE r.name = :name"), 32 | @NamedQuery( 33 | name = RoleEntity.QUERY_GET_ALL_ROLES, 34 | query = "FROM RoleEntity r") 35 | }) 36 | public class RoleEntity implements BasicEntity, Serializable { 37 | private static final long serialVersionUID = 6997056091436404528L; 38 | 39 | public static final String QUERY_FIND_BY_ROLE_TYPE = "RoleEntity.findByRoleType"; 40 | public static final String QUERY_GET_ALL_ROLES = "RoleEntity.getAllRoles"; 41 | 42 | @Id 43 | @Column(name = "ID") 44 | @GeneratedValue(generator = "ROLE_ID_GENERATOR") 45 | private Long id; 46 | 47 | @NotNull 48 | @Enumerated(EnumType.STRING) 49 | @Column(name = "ROLE_NAME", nullable = false, length = 255, unique = true) 50 | private RoleType name = RoleType.USER; 51 | 52 | @Column(name = "ROLE_DESCRIPTION", length = 1024) 53 | private String description; 54 | 55 | @Version 56 | private Long version; 57 | 58 | /** 59 | * Gets id. 60 | * 61 | * @return the id 62 | */ 63 | public Long getId() { 64 | return id; 65 | } 66 | /** 67 | * @return the name 68 | */ 69 | public RoleType getName() { 70 | return name; 71 | } 72 | /** 73 | * @param name the name to set 74 | */ 75 | public void setName(RoleType name) { 76 | this.name = name; 77 | } 78 | /** 79 | * @return the description 80 | */ 81 | public String getDescription() { 82 | return description; 83 | } 84 | /** 85 | * @param description the description to set 86 | */ 87 | public void setDescription(String description) { 88 | this.description = description; 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | StringBuilder builder = new StringBuilder("Role {"); 94 | 95 | builder.append("id:").append(id).append(",") 96 | .append("name:").append(name).append(",") 97 | .append("description:").append(description) 98 | .append('}'); 99 | 100 | return builder.toString(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/model/RoleType.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | 5 | /** 6 | * Represents various options for Roles of a user. 7 | * 8 | * @author Anurag Sharma 9 | */ 10 | public enum RoleType { 11 | ADMIN ("Administrator"), 12 | USER ("User"); 13 | 14 | private String description; 15 | 16 | /** 17 | * Private constructor to allow description. 18 | * 19 | * @param description The string value of the enum constant. 20 | */ 21 | private RoleType(String description) { 22 | this.description = description; 23 | } 24 | 25 | /** 26 | * @return The string value of the enum constant 27 | */ 28 | @JsonValue 29 | public String getDescription() { 30 | return description; 31 | } 32 | } -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/model/UserEntity.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import javax.persistence.Cacheable; 8 | import javax.persistence.CascadeType; 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.FetchType; 12 | import javax.persistence.GeneratedValue; 13 | import javax.persistence.Id; 14 | import javax.persistence.JoinColumn; 15 | import javax.persistence.JoinTable; 16 | import javax.persistence.ManyToMany; 17 | import javax.persistence.NamedQueries; 18 | import javax.persistence.NamedQuery; 19 | import javax.persistence.OneToOne; 20 | import javax.persistence.SequenceGenerator; 21 | import javax.persistence.Table; 22 | import javax.persistence.Version; 23 | import javax.validation.Valid; 24 | import javax.validation.constraints.NotNull; 25 | 26 | 27 | /** 28 | * A database entity representing a tenant user. 29 | * 30 | * @author Anurag Sharma 31 | */ 32 | @Entity 33 | @Cacheable 34 | @Table(name = "USER") 35 | @SequenceGenerator(name = "USER_ID_GENERATOR", sequenceName = "USER_ID_SEQ") 36 | @NamedQueries({ 37 | @NamedQuery( 38 | name = UserEntity.QUERY_COUNT_ALL, 39 | query = "SELECT COUNT(1) FROM UserEntity u"), 40 | @NamedQuery( 41 | name = UserEntity.QUERY_FIND_ALL, 42 | query = "SELECT u FROM UserEntity u") 43 | }) 44 | public class UserEntity implements BasicEntity, Serializable { 45 | private static final long serialVersionUID = 5906694206059291913L; 46 | 47 | public static final String QUERY_COUNT_ALL = "UserEntity.countAll"; 48 | public static final String QUERY_FIND_ALL = "UserEntity.findAll"; 49 | 50 | 51 | @Id 52 | @Column(name = "ID") 53 | @GeneratedValue(generator = "USER_ID_GENERATOR") 54 | private Long id; 55 | 56 | @NotNull @Valid 57 | @OneToOne(cascade = CascadeType.PERSIST) 58 | @JoinColumn(name = "PERSON_ID", nullable = false, unique = true, referencedColumnName = "ID") 59 | private PersonEntity person; 60 | 61 | @NotNull @Valid 62 | @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) 63 | @JoinTable( 64 | name = "USER_ROLE", 65 | joinColumns = @JoinColumn(name = "USER_ID", referencedColumnName = "ID"), 66 | inverseJoinColumns = @JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")) 67 | private Set roles = new HashSet(); 68 | 69 | @Column(name = "ACTIVE", nullable = false) 70 | private Boolean active = Boolean.TRUE; 71 | 72 | @Version 73 | private Long version; 74 | 75 | 76 | /** 77 | * Gets the id. 78 | * 79 | * @return the id 80 | */ 81 | @Override 82 | public Long getId() { 83 | return id; 84 | } 85 | /** 86 | * Gets the person. 87 | * 88 | * @return the person 89 | */ 90 | public PersonEntity getPerson() { 91 | return person; 92 | } 93 | /** 94 | * Sets the person. 95 | * 96 | * @param person 97 | * the new person 98 | */ 99 | public void setPerson(final PersonEntity person) { 100 | this.person = person; 101 | } 102 | /** 103 | * Gets roles. 104 | * 105 | * @return the roles 106 | */ 107 | public Set getRoles() { 108 | return roles; 109 | } 110 | /** 111 | * Sets roles. 112 | * 113 | * @param roles the roles 114 | */ 115 | public void setRoles(final Set roles) { 116 | this.roles = roles; 117 | } 118 | 119 | /** 120 | * @return the active 121 | */ 122 | public Boolean getActive() { 123 | return active; 124 | } 125 | /** 126 | * @param active the active to set 127 | */ 128 | public void setActive(Boolean active) { 129 | this.active = active; 130 | } 131 | 132 | /* 133 | * (non-Javadoc) 134 | * @see java.lang.Object#toString() 135 | */ 136 | @Override 137 | public String toString() { 138 | StringBuilder builder = new StringBuilder(); 139 | 140 | builder.append("User {") 141 | .append("id:").append(id).append(",") 142 | .append("person:").append(person).append(",") 143 | .append("roles:").append(roles).append(",") 144 | .append("active:").append(active).append(",") 145 | .append('}'); 146 | 147 | return builder.toString(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/remote/AbstractRestClient.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote; 2 | 3 | import javax.ws.rs.ClientErrorException; 4 | import javax.ws.rs.ForbiddenException; 5 | import javax.ws.rs.NotAuthorizedException; 6 | import javax.ws.rs.ProcessingException; 7 | import javax.ws.rs.WebApplicationException; 8 | import javax.ws.rs.client.Client; 9 | import javax.ws.rs.client.ClientBuilder; 10 | import javax.ws.rs.core.Response; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; 16 | 17 | import com.sixturtle.exception.InvalidEntityException; 18 | import com.sixturtle.exception.RemoteCallException; 19 | 20 | 21 | /** 22 | * Common ground work for making REST calls using JAX-RS client APIs. 23 | * 24 | * @author Anurag Sharma 25 | */ 26 | public abstract class AbstractRestClient { 27 | private static final Logger log = LoggerFactory.getLogger(AbstractRestClient.class); 28 | private static final int MAX_AUTH_RETRY = 1; 29 | 30 | protected static final String AUTH_BEARER_TOKEN = "Bearer %s"; 31 | protected static final String HDR_AUTHORIZATION = "Authorization"; 32 | 33 | private Client client; 34 | private ClientResponseMapper mapper = new ClientResponseMapper(); 35 | 36 | /** 37 | * Default constructor sets up all the required providers. 38 | */ 39 | public AbstractRestClient() { 40 | this(ClientBuilder.newClient()); 41 | } 42 | 43 | /** 44 | * Special constructor to allow passing custom Client. It can be used to 45 | * create a client trusting all SSL certificates. May be useful during unit 46 | * testing but not recommended for production. 47 | * 48 | * @param client 49 | * An instance of {@link Client} 50 | */ 51 | public AbstractRestClient(final Client client) { 52 | try { 53 | this.client = client.register(JacksonJsonProvider.class) 54 | .register(mapper); 55 | } catch (Exception e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | 60 | 61 | /** 62 | * @return an instance of {@link Client} which has 63 | * {@link JacksonJsonProvider} and {@link ClientResponseMapper}. 64 | */ 65 | public Client getClient() { 66 | return client; 67 | } 68 | 69 | /** 70 | * @return an instance of {@link ClientResponseMapper} 71 | */ 72 | public ClientResponseMapper getMapper() { 73 | return mapper; 74 | } 75 | 76 | /** 77 | * An abstract method that subclass must implement to handle HTTP 401/403 78 | * errors that may occur during {@link #invoke(InvokeCommand)}. 79 | * 80 | * @throws RemoteCallException 81 | * in case of runtime error from Rest API call to authenticate 82 | */ 83 | protected abstract void authenticate() throws RemoteCallException; 84 | 85 | /** 86 | * A Rest API execution template with common error handling and 87 | * authentication. Idea is to capture 401/403 and call 88 | * {@link #authenticate()} first and then try again. This method translates 89 | * any other runtime error into a {@link RemoteCallException}. 90 | * 91 | * @param 92 | * The response type 93 | * @param command 94 | * An implementation of {@link InvokeCommand} 95 | * @return The response returned by {@link InvokeCommand} 96 | * @throws InvalidEntityException 97 | * in case of validation error performed by 98 | * {@link InvokeCommand} 99 | * @throws RemoteCallException 100 | * in case of runtime error from Rest API call 101 | */ 102 | protected R invoke(final InvokeCommand command) 103 | throws InvalidEntityException, RemoteCallException { 104 | R response = null; 105 | 106 | for (int iIndex = 0; (iIndex <= MAX_AUTH_RETRY) && (response == null);) { 107 | try { 108 | response = command.execute(); 109 | break; 110 | } catch (NotAuthorizedException | ForbiddenException e) { 111 | if (iIndex < MAX_AUTH_RETRY) { 112 | ++iIndex; 113 | log.warn("Auth token expired, refresh token retryCount: {}", iIndex); 114 | authenticate(); 115 | continue; 116 | } else { 117 | throw new RemoteCallException( 118 | "Exceeded max retries but failed to authenticate", 119 | e, 120 | getMapper().getErrorCode(), 121 | getMapper().getErrorMessage()); 122 | } 123 | } catch (ClientErrorException e) { 124 | /* 125 | * If ForbiddenException and NotAuthorizedException is not 126 | * thrown by the framework, then inspect the error code. 127 | */ 128 | if ((getMapper().getErrorCode() == Response.Status.FORBIDDEN.getStatusCode() 129 | || getMapper().getErrorCode() == Response.Status.UNAUTHORIZED.getStatusCode()) 130 | && (iIndex < MAX_AUTH_RETRY)) { 131 | ++iIndex; 132 | log.warn("Authentication required error, retryCount: {}", iIndex); 133 | authenticate(); 134 | continue; 135 | } else { 136 | throw new RemoteCallException( 137 | "Client request error during remote call", 138 | e, 139 | getMapper().getErrorCode(), 140 | getMapper().getErrorMessage()); 141 | } 142 | } catch (ProcessingException e) { 143 | String message = "Unexpected error occurred: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()); 144 | throw new RemoteCallException( 145 | "Processing error during remote call", 146 | e, 147 | Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 148 | message); 149 | } catch (WebApplicationException e) { 150 | throw new RemoteCallException( 151 | "Server error during remote call", 152 | e, 153 | getMapper().getErrorCode(), 154 | getMapper().getErrorMessage()); 155 | } 156 | } 157 | return response; 158 | } 159 | 160 | /** 161 | * A generic command interface to serve as a callback. This command may 162 | * throw {@link RuntimeException}. 163 | * 164 | *

165 | * 166 | * The caller of {@link AbstractRestClient#invoke(InvokeCommand)} 167 | * can implement this interface to just focus on making Rest API 168 | * call and not worrying about error handling and authentication. 169 | * 170 | * @param The return type of {@link #execute()} method 171 | * 172 | * @author Anurag Sharma 173 | */ 174 | protected interface InvokeCommand { 175 | /** 176 | * Executes command. 177 | * 178 | * @return The response of type R 179 | */ 180 | R execute(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/remote/ClientResponseMapper.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Scanner; 6 | 7 | import javax.ws.rs.client.ClientRequestContext; 8 | import javax.ws.rs.client.ClientResponseContext; 9 | import javax.ws.rs.client.ClientResponseFilter; 10 | import javax.ws.rs.core.Form; 11 | import javax.ws.rs.core.MultivaluedMap; 12 | import javax.ws.rs.core.Response; 13 | import javax.ws.rs.ext.Provider; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | /** 19 | * A custom JAX-RX provider to implement {@link ClientResponseFilter} to filter 20 | * request and response of an API call for logging purpose. 21 | * 22 | * @author Anurag Sharma 23 | */ 24 | @Provider 25 | public class ClientResponseMapper implements ClientResponseFilter { 26 | private static Logger log = LoggerFactory.getLogger(ClientResponseMapper.class); 27 | 28 | private int errorCode; 29 | private String errorMessage = ""; 30 | 31 | /** 32 | * @return the errorCode 33 | */ 34 | public final int getErrorCode() { 35 | return errorCode; 36 | } 37 | /** 38 | * @return the errorMessage 39 | */ 40 | public final String getErrorMessage() { 41 | return errorMessage; 42 | } 43 | 44 | /* (non-Javadoc) 45 | * @see javax.ws.rs.client.ClientResponseFilter#filter(javax.ws.rs.client.ClientRequestContext, javax.ws.rs.client.ClientResponseContext) 46 | */ 47 | @Override 48 | public void filter( 49 | final ClientRequestContext requestContext, 50 | final ClientResponseContext responseContext) throws IOException { 51 | 52 | this.errorCode = responseContext.getStatus(); 53 | 54 | // In case of HTTP error, log request and response 55 | if (responseContext.getStatus() >= Response.Status.BAD_REQUEST.getStatusCode()) { 56 | String reqMessage = ""; 57 | if (requestContext.hasEntity()) { 58 | Object e = requestContext.getEntity(); 59 | 60 | if (e instanceof Form) { 61 | reqMessage = ((Form) e).asMap().toString(); 62 | } else if (e instanceof Object[]) { 63 | StringBuilder sb = new StringBuilder(); 64 | sb.append("["); 65 | for (Object o : (Object[]) e) { 66 | sb.append(o.toString()).append(","); 67 | } 68 | sb.append("]"); 69 | reqMessage = sb.toString(); 70 | } else { 71 | reqMessage = e.toString(); 72 | } 73 | } 74 | String respMessage = ""; 75 | if (responseContext.hasEntity()) { 76 | InputStream is = responseContext.getEntityStream(); 77 | respMessage = convertStreamToString(is); 78 | } 79 | log.error("REST API error:\n{} {}\n{}\n{}\n\nHTTP {} {}\n{}\n{}", 80 | requestContext.getMethod(), 81 | requestContext.getUri(), 82 | reqHeaders(requestContext.getHeaders()), 83 | reqMessage, 84 | responseContext.getStatus(), 85 | responseContext.getStatusInfo().getReasonPhrase(), 86 | respHeaders(responseContext.getHeaders()), 87 | respMessage); 88 | 89 | this.errorMessage = respMessage; 90 | } 91 | } 92 | 93 | /** 94 | * Convert response headers into a {@link String} format. 95 | * 96 | * @param headers An instance of {@link MultivaluedMap} 97 | * @return The {@link String} representation of the header values 98 | */ 99 | private String reqHeaders(final MultivaluedMap headers) { 100 | StringBuilder builder = new StringBuilder(); 101 | 102 | for (String key : headers.keySet()) { 103 | builder.append(key).append(": ").append(headers.get(key)).append("\n"); 104 | } 105 | return builder.toString(); 106 | } 107 | 108 | /** 109 | * Convert response headers into a {@link String} format. 110 | * Note: This version is needed due to varying generic type. 111 | * 112 | * @param headers An instance of {@link MultivaluedMap} 113 | * @return The {@link String} representation of the header values 114 | */ 115 | private String respHeaders(final MultivaluedMap headers) { 116 | StringBuilder builder = new StringBuilder(); 117 | 118 | for (String key : headers.keySet()) { 119 | builder.append(key).append(": ").append(headers.get(key)).append("\n"); 120 | } 121 | return builder.toString(); 122 | } 123 | 124 | /** 125 | * Reads entire input stream and converts to String. 126 | * 127 | * 128 | * Note: It does not close the stream. But {@link InputStream} 129 | * received from {@link ClientResponseContext} is expected to 130 | * be closed by JAX-RS runtime. 131 | * 132 | * 133 | * @param is The input stream 134 | * @return The content of input stream in String format 135 | */ 136 | private String convertStreamToString(final InputStream is) { 137 | @SuppressWarnings("resource") 138 | Scanner s = new Scanner(is).useDelimiter("\\A"); // read all 139 | return s.hasNext() ? s.next() : ""; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/remote/ConfigLoader.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.FileNotFoundException; 5 | import java.io.InputStream; 6 | import java.net.URL; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | 14 | /** 15 | * A helper class to load JSON based configuration files and convert them to a Java Type using Jackson Mapper API 16 | * Most helpful in loading configuration files that are relative to the .class file, not absolute. 17 | * 18 | * @author Anurag Sharma 19 | */ 20 | public final class ConfigLoader { 21 | private static final int BUFF_SIZE = 1024; 22 | private static final Logger log = LoggerFactory.getLogger(ConfigLoader.class); 23 | 24 | /** 25 | * Private constructor for utility class. 26 | */ 27 | private ConfigLoader() { 28 | // utility class with all static methods, hide constructor 29 | } 30 | 31 | /** 32 | * Loads a JSON file and converts to the provided clazz type. 33 | * 34 | * @param clazz 35 | * The returned class type 36 | * @param jsonFilepath 37 | * The filepath of the JSON file 38 | * @param 39 | * Generic Type 40 | * @return An instance of clazz loaded from the JSON file in case of 41 | * success, null otherwise 42 | */ 43 | public static T loadJsonFileContent(final Class clazz, final String jsonFilepath) { 44 | return resolveJsonFileTemplate(clazz, jsonFilepath, null, null); 45 | } 46 | 47 | /** 48 | * Given a JSON template filepath, it creates an instance of clazz after 49 | * replacing all the tokens with provided values. 50 | * 51 | * @param clazz 52 | * class 53 | * @param templatePath 54 | * The JSON template filepath 55 | * @param tokens 56 | * An array of token names available in the template file 57 | * @param values 58 | * An array of values in the same order and token names 59 | * @param 60 | * Generic Type 61 | * 62 | * @return an instance of clazz in case of success, null otherwise 63 | */ 64 | public static T resolveJsonFileTemplate( 65 | final Class clazz, 66 | final String templatePath, 67 | final String[] tokens, 68 | final String[] values) { 69 | 70 | T t = null; 71 | try { 72 | String content = loadFileContent(templatePath); 73 | if (StringUtils.isNotBlank(content)) { 74 | 75 | // replace tokens with values if tokens are provided 76 | if (tokens != null) { 77 | content = StringUtils.replaceEach(content, tokens, values); 78 | } 79 | 80 | log.trace("resolved content of template {}: \n{}", templatePath, content); 81 | 82 | ObjectMapper jsonMapper = new ObjectMapper(); 83 | t = jsonMapper.readValue(content, clazz); 84 | } else { 85 | log.error("empty content loaded from: {}", templatePath); 86 | } 87 | } catch (Exception e) { 88 | log.error(e.getMessage(), e); 89 | } 90 | return t; 91 | } 92 | 93 | /** 94 | * Loads a file content to String. Will first try to be loaded from the 95 | * OS filesystem. If the file is not found, then the file will try to 96 | * be loaded relative to the class file as a class resource. 97 | * 98 | * @param filepath The filename with full path 99 | * 100 | * @return content of the file in case of success, exception otherwise 101 | * @throws Exception in case of error 102 | */ 103 | public static String loadFileContent(final String filepath) throws Exception { 104 | InputStream in = null; 105 | try { 106 | 107 | try { 108 | in = new FileInputStream(filepath); 109 | log.trace("loading system file: {}", filepath); 110 | } catch (final FileNotFoundException e) { 111 | in = ConfigLoader.class.getClassLoader().getResourceAsStream(filepath); 112 | log.trace("loading class loader resource file: {}", filepath); 113 | } 114 | if (in == null) { 115 | URL url = ConfigLoader.class.getResource(filepath); 116 | log.trace("loading class resource url: {}", url); 117 | if (url != null) { 118 | in = new FileInputStream(url.getPath()); 119 | } 120 | } 121 | 122 | if (in != null) { 123 | StringBuilder sb = new StringBuilder(); 124 | byte[] buffer = new byte[BUFF_SIZE]; 125 | int len = 0; 126 | while ((len = in.read(buffer)) != -1) { 127 | sb.append(new String(buffer, 0, len)); 128 | buffer = new byte[BUFF_SIZE]; 129 | } 130 | log.trace("file content: \n{}", sb); 131 | return sb.toString(); 132 | } else { 133 | throw new FileNotFoundException(filepath); 134 | } 135 | } finally { 136 | if (in != null) { 137 | try { 138 | in.close(); 139 | } catch (Exception e) { 140 | log.error("Error Closing File stream {}", e.getMessage()); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/remote/service/EmailStatus.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote.service; 2 | 3 | import java.io.Serializable; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | /** 8 | * Represents response returned from {@link EmailValidator#isValidEmail(String)} 9 | * 10 | * @author Anurag Sharma 11 | */ 12 | public class EmailStatus implements Serializable { 13 | private static final long serialVersionUID = 3431386157628983516L; 14 | 15 | @JsonProperty("isValid") 16 | private boolean valid; 17 | 18 | /** 19 | * @return the isValid 20 | */ 21 | public boolean isValid() { 22 | return valid; 23 | } 24 | /** 25 | * @param isValid the isValid to set 26 | */ 27 | public void setValid(boolean isValid) { 28 | this.valid = isValid; 29 | } 30 | 31 | /* (non-Javadoc) 32 | * @see java.lang.Object#toString() 33 | */ 34 | @Override 35 | public String toString() { 36 | StringBuilder builder = new StringBuilder(); 37 | builder.append("EmailStatus {") 38 | .append("isValid:").append(valid) 39 | .append("}"); 40 | return builder.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/remote/service/EmailValidator.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote.service; 2 | 3 | import com.sixturtle.exception.InvalidEntityException; 4 | import com.sixturtle.exception.RemoteCallException; 5 | 6 | /** 7 | * Interface for Email Validation API. 8 | * 9 | * "https://pozzad-email-validator.p.mashape.com/emailvalidator/validateEmail" 10 | * 11 | * @author Anurag Sharma 12 | */ 13 | public interface EmailValidator { 14 | /** 15 | * Checks email format and domain to see if email is valid. 16 | * 17 | * @param email 18 | * The email address 19 | * @return true if valid, false otherwise 20 | * @throws InvalidEntityException 21 | * when validation fails 22 | * @throws RemoteCallException 23 | * when remote call error occurrs 24 | */ 25 | boolean isValidEmail(final String email) throws RemoteCallException, InvalidEntityException; 26 | } 27 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/remote/service/EmailValidatorImpl.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote.service; 2 | 3 | import javax.inject.Named; 4 | import javax.ws.rs.core.MediaType; 5 | import javax.ws.rs.core.Response; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.sixturtle.exception.InvalidEntityException; 11 | import com.sixturtle.exception.RemoteCallException; 12 | import com.sixturtle.remote.AbstractRestClient; 13 | import com.sixturtle.remote.ConfigLoader; 14 | 15 | /** 16 | * Implements {@link EmailValidator} API. 17 | * 18 | * @author Anurag Sharma 19 | */ 20 | @Named 21 | public class EmailValidatorImpl extends AbstractRestClient implements EmailValidator { 22 | private static final Logger log = LoggerFactory.getLogger(EmailValidator.class); 23 | 24 | private MashupContext context; 25 | 26 | /** 27 | * Default constructor to setup context 28 | */ 29 | public EmailValidatorImpl() { 30 | super(); 31 | loadContext(); 32 | } 33 | 34 | /** 35 | * @return the context 36 | */ 37 | public MashupContext getContext() { 38 | return context; 39 | } 40 | 41 | 42 | /* (non-Javadoc) 43 | * @see com.sixturtle.remote.EmailValidator#isValidEmail(java.lang.String) 44 | */ 45 | @Override 46 | public boolean isValidEmail(String email) throws RemoteCallException, InvalidEntityException { 47 | boolean status = false; 48 | 49 | Response response = super.invoke(new InvokeCommand() { 50 | /* 51 | * (non-Javadoc) 52 | * @see com.sixturtle.remote.AbstractRestClient.InvokeCommand#execute() 53 | */ 54 | @Override 55 | public Response execute() { 56 | Response response = getClient() 57 | .target(getContext().getBaseUrl() + getContext().getApiPath() + "?email=" + email) 58 | .request() 59 | .accept(MediaType.APPLICATION_JSON) 60 | .header( 61 | MashupContext.AUTH_HEADER, 62 | getContext().getAuthcode() 63 | ) 64 | .get(); 65 | log.debug("Email Validation Response: {}", response.getStatus()); 66 | return response; 67 | } 68 | 69 | }); 70 | EmailStatus s = response.readEntity(EmailStatus.class); 71 | if (s != null) { 72 | status = s.isValid(); 73 | } 74 | return status; 75 | } 76 | 77 | /* 78 | * (non-Javadoc) 79 | * @see com.sixturtle.remote.AbstractRestClient#authenticate() 80 | */ 81 | @Override 82 | protected void authenticate() throws RemoteCallException { 83 | // NONE: pre-authentication setup for this API call 84 | } 85 | 86 | /** 87 | * Loads {@link MashupContext} from a property file. The property file can be 88 | * hosted externally and passed as command line argument 89 | * -Dmashup.config=/path/to/mashupConfig.json or it can use the default 90 | * bundled property. 91 | */ 92 | private void loadContext() { 93 | String configFilePath = System.getProperty("mashup.config"); 94 | if (configFilePath == null) { 95 | configFilePath = "config/mashupConfig.json"; // default, bundled property 96 | } 97 | context = ConfigLoader.loadJsonFileContent(MashupContext.class, configFilePath); 98 | log.debug("loaded mashup context: {}", context); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/remote/service/MashupContext.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote.service; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Security Context for using Mashup API. 7 | * 8 | * @author Anurag Sharma 9 | */ 10 | public class MashupContext implements Serializable { 11 | private static final long serialVersionUID = 6605440997492972143L; 12 | public static final String AUTH_HEADER = "X-Mashape-Key"; 13 | 14 | private String baseUrl; 15 | private String apiPath; 16 | private String authcode; 17 | 18 | /** 19 | * @return the baseUrl 20 | */ 21 | public String getBaseUrl() { 22 | return baseUrl; 23 | } 24 | /** 25 | * @param baseUrl the baseUrl to set 26 | */ 27 | public void setBaseUrl(String baseUrl) { 28 | this.baseUrl = baseUrl; 29 | } 30 | /** 31 | * @return the apiPath 32 | */ 33 | public String getApiPath() { 34 | return apiPath; 35 | } 36 | /** 37 | * @param apiPath the apiPath to set 38 | */ 39 | public void setApiPath(String apiPath) { 40 | this.apiPath = apiPath; 41 | } 42 | /** 43 | * @return the authcode 44 | */ 45 | public String getAuthcode() { 46 | return authcode; 47 | } 48 | /** 49 | * @param authcode the authcode to set 50 | */ 51 | public void setAuthcode(String authcode) { 52 | this.authcode = authcode; 53 | } 54 | 55 | /* (non-Javadoc) 56 | * @see java.lang.Object#toString() 57 | */ 58 | @Override 59 | public String toString() { 60 | StringBuilder builder = new StringBuilder(); 61 | builder.append("MashupContext {") 62 | .append("baseUrl:").append(baseUrl).append(",") 63 | .append("apiPath:").append(apiPath).append(",") 64 | .append("authcode:").append(authcode) 65 | .append("}"); 66 | return builder.toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/web/BasicExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web; 2 | 3 | import java.io.EOFException; 4 | import java.util.List; 5 | 6 | import javax.ejb.EJBAccessException; 7 | import javax.ws.rs.BadRequestException; 8 | import javax.ws.rs.NotAcceptableException; 9 | import javax.ws.rs.NotAllowedException; 10 | import javax.ws.rs.NotAuthorizedException; 11 | import javax.ws.rs.NotFoundException; 12 | import javax.ws.rs.NotSupportedException; 13 | import javax.ws.rs.WebApplicationException; 14 | import javax.ws.rs.core.Context; 15 | import javax.ws.rs.core.HttpHeaders; 16 | import javax.ws.rs.core.MediaType; 17 | import javax.ws.rs.core.Response; 18 | import javax.ws.rs.core.Response.Status; 19 | import javax.ws.rs.ext.ExceptionMapper; 20 | import javax.ws.rs.ext.Provider; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import com.fasterxml.jackson.core.JsonParseException; 26 | import com.fasterxml.jackson.databind.JsonMappingException; 27 | import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; 28 | 29 | import com.sixturtle.exception.InvalidEntityException; 30 | import com.sixturtle.exception.UnknownEntityException; 31 | 32 | /** 33 | * Maps most commonly known HTTP errors into a JSON response. This should hide 34 | * the regular error pages shown by server for these common HTTP errors. 35 | * 36 | * @author Anurag Sharma 37 | */ 38 | @Provider 39 | public class BasicExceptionMapper implements ExceptionMapper { 40 | private static Logger logger = LoggerFactory.getLogger(BasicExceptionMapper.class); 41 | 42 | @Context 43 | private HttpHeaders headers; 44 | 45 | /** 46 | * @param headers 47 | * {@link HttpHeaders} 48 | */ 49 | public void setHeaders(HttpHeaders headers) { 50 | this.headers = headers; 51 | } 52 | 53 | /* 54 | * (non-Javadoc) 55 | * 56 | * @see javax.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) 57 | */ 58 | @Override 59 | public Response toResponse(final Throwable ex) { 60 | // default is server error 61 | ErrorInfo error = convertToError(ex); 62 | if (error == null) { 63 | /* 64 | * If the current exception class could not be mapped then try 65 | * mapping the root cause exception. But this should be the last try 66 | * to map exception to HTTP error. 67 | */ 68 | Throwable t = ex.getCause(); 69 | error = convertToError(t); 70 | if (error == null) { 71 | logger.error("Unable to find a matching code so mapping to the default HTTP 500", ex); 72 | error = new ErrorInfo(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Unexpected server error occurred"); 73 | } 74 | } 75 | 76 | /* 77 | * Convert the errorInfo to match with "Accept" header 78 | */ 79 | MediaType media = MediaType.APPLICATION_JSON_TYPE; 80 | List accepts = headers.getAcceptableMediaTypes(); 81 | if (accepts != null && !accepts.isEmpty()) { 82 | media = accepts.get(0); // pick the first one 83 | } 84 | if (media != MediaType.APPLICATION_JSON_TYPE && media != MediaType.APPLICATION_XML_TYPE) { 85 | media = MediaType.APPLICATION_JSON_TYPE; // default to JSON 86 | } 87 | return Response.status(error.getCode()).type(media).entity(error).build(); 88 | } 89 | 90 | /** 91 | * Converts an exception to ErrorInfo. 92 | * 93 | * @param ex 94 | * the exception to be converted to {@link ErrorInfo} 95 | * @return the error info 96 | */ 97 | private ErrorInfo convertToError(final Throwable ex) { 98 | // default is server error 99 | ErrorInfo error = new ErrorInfo(Status.INTERNAL_SERVER_ERROR.getStatusCode(), 100 | "Unexpected server error occurred"); 101 | if (ex instanceof EJBAccessException) { 102 | error.setCode(Status.FORBIDDEN.getStatusCode()); 103 | error.setMessage("Access restricted"); 104 | } else if ((ex instanceof BadRequestException) 105 | || (ex instanceof EOFException)) { 106 | error.setCode(Status.BAD_REQUEST.getStatusCode()); 107 | error.setMessage(ex.getLocalizedMessage()); 108 | if (ex.getCause() instanceof InvalidEntityException) { 109 | InvalidEntityException e = (InvalidEntityException) ex.getCause(); 110 | error.setMessage(e.getMessage()); 111 | error.setAdditionalInfo(e.getViolations()); 112 | } 113 | 114 | } else if (ex instanceof NotFoundException) { 115 | error.setCode(Status.NOT_FOUND.getStatusCode()); 116 | error.setMessage(ex.getLocalizedMessage()); 117 | if (ex.getCause() instanceof UnknownEntityException) { 118 | UnknownEntityException e = (UnknownEntityException) ex.getCause(); 119 | error.setMessage(e.getMessage()); 120 | error.setAdditionalInfo(e.getViolations()); 121 | } 122 | 123 | } else if (ex instanceof UnrecognizedPropertyException) { 124 | /* 125 | * WARNING: UnrecognizedPropertyException is coming from Jackson 126 | * library which we did not intend to depend on at compile time. 127 | * However, catching this specific exception allows us to return 128 | * appropriate HTTP code rather than throwing HTTP 500. 129 | * 130 | * Further usage of classes coming from Jackson library are not 131 | * recommended. 132 | */ 133 | error.setCode(Status.BAD_REQUEST.getStatusCode()); 134 | error.setMessage("Unrecognized field: " 135 | + ((UnrecognizedPropertyException) ex).getPropertyName()); 136 | logger.error("Unrecognized fields", ex); 137 | 138 | } else if (ex instanceof JsonParseException) { 139 | /* 140 | * WARNING: JsonParseException is coming from Jackson library which 141 | * we did not intend to depend on at compile time. However, catching 142 | * this specific exception allows us to return appropriate HTTP code 143 | * rather than throwing HTTP 500. 144 | * 145 | * Further usage of classes coming from Jackson library are not 146 | * recommended. 147 | */ 148 | error.setCode(Status.BAD_REQUEST.getStatusCode()); 149 | error.setMessage("Invalid input format"); 150 | logger.error("Invalid input format", ex); 151 | 152 | } else if (ex instanceof JsonMappingException) { 153 | /* 154 | * WARNING: JsonMappingException is coming from Jackson library 155 | * which we did not intend to depend on at compile time. However, 156 | * catching this specific exception allows us to return appropriate 157 | * HTTP code rather than throwing HTTP 500. 158 | * 159 | * Further usage of classes coming from Jackson library are not 160 | * recommended. 161 | */ 162 | error.setCode(Status.BAD_REQUEST.getStatusCode()); 163 | error.setMessage("Invalid input format"); 164 | logger.error("Invalid input format", ex); 165 | 166 | } else if (ex instanceof NotAllowedException) { 167 | error.setCode(Status.METHOD_NOT_ALLOWED.getStatusCode()); 168 | error.setMessage(ex.getLocalizedMessage()); 169 | 170 | } else if (ex instanceof NotAcceptableException) { 171 | error.setCode(Status.NOT_ACCEPTABLE.getStatusCode()); 172 | error.setMessage(ex.getLocalizedMessage()); 173 | 174 | } else if (ex instanceof NotSupportedException) { 175 | error.setCode(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode()); 176 | error.setMessage(ex.getLocalizedMessage()); 177 | 178 | } else if (ex instanceof NotAuthorizedException) { 179 | error.setCode(Status.UNAUTHORIZED.getStatusCode()); 180 | error.setMessage(ex.getLocalizedMessage()); 181 | 182 | } else if (ex instanceof WebApplicationException) { 183 | WebApplicationException e = (WebApplicationException) ex; 184 | error.setCode(e.getResponse().getStatus()); 185 | error.setMessage(ex.getLocalizedMessage()); 186 | logger.error("Internal error occured", ex); 187 | 188 | } else { 189 | error = null; 190 | } 191 | return error; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/web/ErrorInfo.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web; 2 | 3 | import java.io.Serializable; 4 | import java.util.Map; 5 | 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 10 | 11 | /** 12 | * A simple error bean used in sending JSON response body in case of error. 13 | * 14 | * @author Anurag Sharma 15 | */ 16 | @XmlRootElement 17 | @JsonInclude(Include.NON_NULL) 18 | public class ErrorInfo implements Serializable { 19 | private static final long serialVersionUID = 4409782805386039091L; 20 | 21 | /** must be a valid HTTP code. */ 22 | private int code; 23 | private String message; 24 | 25 | /** (field, message) or (name, value) pair. */ 26 | private Map additionalInfo; 27 | 28 | /** 29 | * Default constructor. 30 | */ 31 | public ErrorInfo() { 32 | } 33 | 34 | /** 35 | * Convenience constructor. 36 | * 37 | * @param code The error code 38 | * @param message The error that occurred 39 | */ 40 | public ErrorInfo(final int code, final String message) { 41 | this.code = code; 42 | this.message = message; 43 | } 44 | 45 | /** 46 | * @return the code 47 | */ 48 | public final int getCode() { 49 | return code; 50 | } 51 | /** 52 | * @param code the code to set 53 | */ 54 | public final void setCode(final int code) { 55 | this.code = code; 56 | } 57 | /** 58 | * @return the message 59 | */ 60 | public final String getMessage() { 61 | return message; 62 | } 63 | /** 64 | * @param message the message to set 65 | */ 66 | public final void setMessage(final String message) { 67 | this.message = message; 68 | } 69 | /** 70 | * @return the additionalInfo 71 | */ 72 | public final Map getAdditionalInfo() { 73 | return additionalInfo; 74 | } 75 | /** 76 | * @param additionalInfo the additionalInfo to set 77 | */ 78 | public final void setAdditionalInfo(final Map additionalInfo) { 79 | this.additionalInfo = additionalInfo; 80 | } 81 | /* (non-Javadoc) 82 | * @see java.lang.Object#toString() 83 | */ 84 | @Override 85 | public final String toString() { 86 | StringBuilder builder = new StringBuilder(); 87 | builder.append("ErrorInfo {") 88 | .append("code:").append(code).append(",") 89 | .append("message:").append(message).append(",") 90 | .append("additionalInfo:").append(additionalInfo) 91 | .append("}"); 92 | return builder.toString(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/web/PaginatedModel.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * A resource facade to encapsulate pagination information. 7 | * 8 | * @author Anurag Sharma 9 | * 10 | * @param 11 | */ 12 | public class PaginatedModel { 13 | private int offset; 14 | private int limit; 15 | private Long count; 16 | private List data; 17 | 18 | /** 19 | * Construct paginated wrapper of a list data. 20 | * 21 | * @param offset 22 | * The starting index requested 23 | * @param limit 24 | * Max number of elements requested 25 | * @param count 26 | * Total number of the elements available 27 | * @param data 28 | * Actual list of elements 29 | */ 30 | public PaginatedModel(int offset, int limit, Long count, List data) { 31 | this.offset = offset; 32 | this.limit = limit; 33 | this.count = count; 34 | this.data = data; 35 | } 36 | 37 | /** 38 | * @return the offset 39 | */ 40 | public int getOffset() { 41 | return offset; 42 | } 43 | /** 44 | * @return the limit 45 | */ 46 | public int getLimit() { 47 | return limit; 48 | } 49 | /** 50 | * @return the count 51 | */ 52 | public Long getCount() { 53 | return count; 54 | } 55 | /** 56 | * @return the data 57 | */ 58 | public List getData() { 59 | return data; 60 | } 61 | 62 | /** 63 | * @return true if previous exists, false otherwise 64 | */ 65 | public boolean hasPrev() { 66 | return (offset > 0); 67 | } 68 | /** 69 | * @return true if next exists, false otherwise 70 | */ 71 | public boolean hasNext() { 72 | return ((offset + limit) < count); 73 | } 74 | /** 75 | * @return offset of the first position 76 | */ 77 | public int first() { 78 | return 0; 79 | } 80 | /** 81 | * @return offset of the previous position 82 | */ 83 | public int prev() { 84 | return ((offset - limit) < 0) ? 0 : (offset - limit); 85 | } 86 | /** 87 | * @return offset of the next position 88 | */ 89 | public int next() { 90 | return (hasNext() ? (offset + limit) : offset); 91 | } 92 | /** 93 | * @return offset of the last position 94 | */ 95 | public int last() { 96 | return (int) ((count % limit > 0) 97 | ? (limit * (count / limit) + (offset % limit)) 98 | : (limit * ((count / limit) - 1))); 99 | } 100 | 101 | /* (non-Javadoc) 102 | * @see java.lang.Object#toString() 103 | */ 104 | @Override 105 | public String toString() { 106 | StringBuilder builder = new StringBuilder(); 107 | builder.append("PaginatedModel {") 108 | .append("offset:").append(offset).append(",") 109 | .append("limit:").append(limit).append(",") 110 | .append("count:").append(count).append(",") 111 | .append("data:").append(data) 112 | .append("}"); 113 | return builder.toString(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/web/SixturtleApplication.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import javax.ws.rs.ApplicationPath; 8 | import javax.ws.rs.core.Application; 9 | 10 | import com.sixturtle.web.service.PersonService; 11 | import com.sixturtle.web.service.UserService; 12 | 13 | 14 | /** 15 | * Responsible for handling the requests to RESTful resources. 16 | * 17 | * @author Anurag Sharma 18 | * @see Application 19 | * @see ApplicationPath 20 | */ 21 | @ApplicationPath("/api/") 22 | public class SixturtleApplication extends Application { 23 | 24 | private Set> classes = new HashSet>(); 25 | 26 | /* 27 | * (non-Javadoc) 28 | * @see javax.ws.rs.core.Application#getClasses() 29 | */ 30 | @Override 31 | public Set> getClasses() { 32 | /* 33 | * Add all the resource service classes here 34 | */ 35 | classes.addAll( 36 | Arrays.asList( 37 | PersonService.class, 38 | UserService.class 39 | )); 40 | return classes; 41 | } 42 | } -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/web/URLHelper.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.net.URLDecoder; 7 | 8 | import javax.ws.rs.core.Response.ResponseBuilder; 9 | import javax.ws.rs.core.UriInfo; 10 | 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * A helper class for building URLs for various use cases including 17 | * but not limited to link headers. 18 | * 19 | * @author Anurag Sharma 20 | */ 21 | public final class URLHelper { 22 | private static final Logger log = LoggerFactory.getLogger(URLHelper.class); 23 | 24 | public static final String URI_CURRENT = "."; 25 | 26 | public static final String PARAM_OFFSET = "offset"; 27 | public static final String PARAM_LIMIT = "limit"; 28 | public static final String PARAM_SORT = "sort"; 29 | 30 | public static final String DEFAULT_OFFSET = "0"; 31 | public static final String DEFAULT_LIMIT = "50"; 32 | 33 | public static final String HEADER_LINK_FIRST = "first"; 34 | public static final String HEADER_LINK_PREV = "prev"; 35 | public static final String HEADER_LINK_NEXT = "next"; 36 | public static final String HEADER_LINK_LAST = "last"; 37 | public static final String HEADER_TOTAL_COUNT = "X-total-count"; 38 | 39 | /** 40 | * Utility class, prohibit construction. 41 | */ 42 | private URLHelper() { 43 | // no constructor 44 | } 45 | 46 | /** 47 | * Generate a link to the resource. 48 | * 49 | * @param uriInfo 50 | * uriInfo 51 | * @param id 52 | * The resource ID 53 | * @param clazz 54 | * class 55 | * @return {@link URI} to the resource in case of success, null otherwise 56 | */ 57 | public static URI selfLink(UriInfo uriInfo, final String id, Class< ? > clazz) { 58 | URI uri = null; 59 | if (uriInfo == null) { 60 | try { 61 | uri = new URI("/" + id.toString()); 62 | } catch (URISyntaxException e) { 63 | log.error("URI Syntax error {}", e.getMessage()); 64 | } 65 | } /*else { 66 | uri = uriInfo.getBaseUriBuilder() 67 | .path(clazz) 68 | .path(id.toString()) 69 | .build(); 70 | }*/ 71 | return uri; 72 | } 73 | 74 | /** 75 | * Builds headers for a GET request to a collection of resource. 76 | * 77 | * @param builder 78 | * An instance of {@link ResponseBuilder} creating the response 79 | * @param uriInfo 80 | * Base URI of the resource collection 81 | * @param 82 | * Generic Type 83 | * @param list 84 | * An instance of {@link PaginatedModel} serving the request 85 | */ 86 | public static void addNavHeaders( 87 | final ResponseBuilder builder, 88 | final UriInfo uriInfo, 89 | final PaginatedModel list) { 90 | String urlTemplate = buildUrlTemplate(uriInfo); 91 | 92 | // Add total count 93 | builder.header(HEADER_TOTAL_COUNT, list.getCount()); 94 | 95 | // Add navigation links 96 | if (list.hasPrev()) { 97 | String first = String.format(urlTemplate.toString(), list.first(), list.getLimit()); 98 | String prev = String.format(urlTemplate.toString(), list.prev(), list.getLimit()); 99 | if (first.equals(prev)) { 100 | builder.link(URI.create(first), "first prev"); 101 | } else { 102 | builder.link(URI.create(first), "first"); 103 | builder.link(URI.create(prev), "prev"); 104 | } 105 | } else { 106 | if (list.hasNext()) { 107 | builder.link(URI.create(URI_CURRENT), "first"); 108 | } else { 109 | builder.link(URI.create(URI_CURRENT), "first last"); 110 | } 111 | } 112 | if (list.hasNext()) { 113 | String next = String.format(urlTemplate.toString(), list.next(), list.getLimit()); 114 | String last = String.format(urlTemplate.toString(), list.last(), list.getLimit()); 115 | if (last.equals(next)) { 116 | builder.link(URI.create(last), "next last"); 117 | } else { 118 | builder.link(URI.create(next), "next"); 119 | builder.link(URI.create(last), "last"); 120 | } 121 | } else { 122 | if (list.hasPrev()) { 123 | builder.link(URI.create(URI_CURRENT), "last"); 124 | } 125 | } 126 | } 127 | 128 | /** 129 | * Converts existing URL into a template where offset and limit 130 | * values can be updated. 131 | * 132 | * @param uriInfo The {@link UriInfo} for the current request 133 | * @return The relative URL for the resource with offset=%d&limit=%d 134 | */ 135 | protected static String buildUrlTemplate(final UriInfo uriInfo) { 136 | String urlTemplate = null; 137 | 138 | String url; 139 | try { 140 | url = URLDecoder.decode(uriInfo.getRequestUri().toASCIIString(), "UTF-8"); 141 | int pos = url.indexOf(uriInfo.getBaseUri().toASCIIString()); 142 | if (pos >= 0) { 143 | urlTemplate = url.substring(pos); 144 | } else { 145 | urlTemplate = url; 146 | } 147 | } catch (UnsupportedEncodingException e) { 148 | log.error("Error in decoding the Link header", e); 149 | } 150 | 151 | 152 | urlTemplate = updateQueryParam(urlTemplate, URLHelper.PARAM_OFFSET, "%d"); 153 | urlTemplate = updateQueryParam(urlTemplate, URLHelper.PARAM_LIMIT, "%d"); 154 | return urlTemplate; 155 | } 156 | 157 | /** 158 | * Given a URL with query parameter, it adds or updates given param/value. 159 | * 160 | * @param url Existing URL with query parameters 161 | * @param param The parameter name whose value needs to be added/updated 162 | * @param value The value of the parameter 163 | * @return Updated URL containing updated/added param/value 164 | */ 165 | protected static String updateQueryParam( 166 | final String url, 167 | final String param, 168 | final String value) { 169 | 170 | /* 171 | * Locate the param and update the value. 172 | * If not found then append as new param/value. 173 | * 174 | * Example URL: 175 | * "/resources?JSESSIONID=123234&sort='field1,-field2'&q='field1=value1,field2=value2'&offset=0&limit=50" 176 | */ 177 | 178 | // Find the boundaries of "param=value" 179 | int beginIndex = url.indexOf(param); 180 | int endIndex = -1; 181 | if (beginIndex >= 0) { // param found 182 | endIndex = url.indexOf('&', beginIndex + 1); 183 | if (endIndex < 0) { // no other params 184 | endIndex = url.length(); 185 | } 186 | } else { // param not found, need to append 187 | beginIndex = url.length(); 188 | endIndex = url.length(); 189 | } 190 | 191 | // Update the value in "param=value" 192 | String paramValue = url.substring(beginIndex, endIndex); 193 | String[] fields = null; 194 | if (StringUtils.isNotBlank(paramValue)) { 195 | fields = paramValue.split("="); 196 | if (fields.length == 1) { // param without value 197 | fields = null; // will construct in later step 198 | } else { 199 | if (fields[1].equals(value)) { 200 | return url; // done! no change needed 201 | } 202 | fields[1] = value; // update with new value 203 | } 204 | } 205 | if (fields == null) { 206 | fields = new String[2]; 207 | fields[0] = param; 208 | fields[1] = value; 209 | } 210 | 211 | // Split url into two parts - before and after "param=value" 212 | StringBuilder builder = new StringBuilder(url.substring(0, beginIndex)); 213 | 214 | // if it is the last param, make sure to put ? or & 215 | int pos = url.indexOf('?'); 216 | if (pos < 0) { 217 | builder.append('?'); 218 | } 219 | if (beginIndex == url.length() && url.indexOf('?') >= 0) { 220 | builder.append('&'); 221 | } 222 | // insert the updated "param=value" 223 | builder.append(fields[0]).append("=").append(fields[1]); 224 | builder.append(url.substring(endIndex, url.length())); 225 | 226 | return builder.toString(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/web/service/PersonService.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web.service; 2 | 3 | import java.util.List; 4 | 5 | import javax.inject.Inject; 6 | import javax.transaction.Transactional; 7 | import javax.validation.Valid; 8 | import javax.ws.rs.BadRequestException; 9 | import javax.ws.rs.Consumes; 10 | import javax.ws.rs.DELETE; 11 | import javax.ws.rs.DefaultValue; 12 | import javax.ws.rs.GET; 13 | import javax.ws.rs.HEAD; 14 | import javax.ws.rs.NotFoundException; 15 | import javax.ws.rs.POST; 16 | import javax.ws.rs.PUT; 17 | import javax.ws.rs.Path; 18 | import javax.ws.rs.PathParam; 19 | import javax.ws.rs.Produces; 20 | import javax.ws.rs.QueryParam; 21 | import javax.ws.rs.core.Context; 22 | import javax.ws.rs.core.MediaType; 23 | import javax.ws.rs.core.Response; 24 | import javax.ws.rs.core.UriInfo; 25 | import javax.ws.rs.core.Response.ResponseBuilder; 26 | import javax.ws.rs.core.Response.Status; 27 | 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | import com.sixturtle.db.PersonRepository; 32 | import com.sixturtle.exception.InvalidEntityException; 33 | import com.sixturtle.exception.UnknownEntityException; 34 | import com.sixturtle.model.PersonEntity; 35 | import com.sixturtle.web.PaginatedModel; 36 | import com.sixturtle.web.URLHelper; 37 | 38 | /** 39 | * Represents REST API for {@link PersonEntity}. 40 | * 41 | * @author Anurag Sharma 42 | */ 43 | @Transactional 44 | @Path("/persons") 45 | @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) 46 | @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) 47 | public class PersonService { 48 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 49 | 50 | @Context 51 | protected UriInfo uriInfo; 52 | 53 | @Inject 54 | private PersonRepository repository; 55 | 56 | /** 57 | * @param repository the repository to set 58 | */ 59 | public void setRepository(PersonRepository repository) { 60 | this.repository = repository; 61 | } 62 | 63 | /** 64 | * Represents POST operation to create a new resource. 65 | * 66 | * @param resource 67 | * The resource of type {@link PersonEntity} 68 | * 69 | * @return {@link Response} with one of the following codes. 70 | *

    71 | *
  • 201 in case success with no content
  • 72 | *
  • 400 if input validation fails
  • 73 | *
  • 500 in case of system error.
  • 74 | *
75 | */ 76 | @POST 77 | public Response createResource(@Valid final PersonEntity resource) { 78 | try { 79 | PersonEntity entity = repository.create(resource); 80 | log.debug("created entity: {}", entity); 81 | return Response.created(URLHelper.selfLink(uriInfo, entity.getId().toString(), this.getClass())) 82 | .build(); 83 | } catch (InvalidEntityException e) { 84 | throw new BadRequestException(e); 85 | } 86 | } 87 | 88 | /** 89 | * Represents PUT operation to update an existing resource. 90 | * 91 | * @param resourceId 92 | * The id of type {@link Long} for the resource of type {@link PersonEntity} 93 | * @param resource 94 | * The resource of type {@link PersonEntity} 95 | * 96 | * @return {@link Response} with one of the following codes. 97 | *
    98 | *
  • 204 in case success with no content
  • 99 | *
  • 404 if resourceId is not found
  • 100 | *
  • 400 if input validation fails
  • 101 | *
  • 500 in case of system error.
  • 102 | *
103 | */ 104 | @PUT 105 | @Path("{id}") 106 | public Response updateResource(@PathParam("id") final Long resourceId, @Valid final PersonEntity resource) { 107 | try { 108 | PersonEntity entity = repository.update(resourceId, resource); 109 | log.debug("updated entity: {}", entity); 110 | return Response.noContent().build(); 111 | } catch (InvalidEntityException e) { 112 | throw new BadRequestException(e); 113 | } catch (UnknownEntityException e) { 114 | throw new NotFoundException(e); 115 | } 116 | } 117 | 118 | /** 119 | * Represents DELETE operation to remove an existing resource. 120 | * 121 | * @param resourceId 122 | * The id of type {@link Long} for the resource of type {@link PersonEntity} 123 | * 124 | * @return {@link Response} with one of the following codes. 125 | *
    126 | *
  • 204 in case success with no content
  • 127 | *
  • 404 if resourceId is not found
  • 128 | *
  • 500 in case of system error.
  • 129 | *
130 | */ 131 | @DELETE 132 | @Path("{id}") 133 | public Response deleteResource(@PathParam("id") final Long resourceId) { 134 | try { 135 | repository.delete(resourceId); 136 | log.debug("deleted entity: {}", resourceId); 137 | return Response.noContent().build(); 138 | } catch (UnknownEntityException e) { 139 | throw new NotFoundException(e); 140 | } 141 | } 142 | 143 | /** 144 | * Represents GET operation to retrieve an existing resource. 145 | * 146 | * @param resourceId 147 | * The id of type {@link Long} for the resource of type {@link PersonEntity} 148 | * 149 | * @return {@link Response} with one of the following codes. 150 | *
    151 | *
  • 200 in case success with entity as T
  • 152 | *
  • 404 if resourceId is not found
  • 153 | *
  • 500 in case of system error.
  • 154 | *
155 | */ 156 | @GET 157 | @Path("{id}") 158 | public Response findResource(@PathParam("id")final Long resourceId) { 159 | PersonEntity entity = repository.find(resourceId); 160 | if (entity != null) { 161 | return Response.ok().entity(entity).build(); 162 | } else { 163 | throw new NotFoundException("Unable to find " + resourceId); 164 | } 165 | } 166 | 167 | /** 168 | * Represents HEAD operation to retrieve an existing resource. 169 | * 170 | * @param resourceId 171 | * The id of type {@link Long} for the resource of type {@link PersonEntity} 172 | * 173 | * @return {@link Response} with one of the following codes. 174 | *
    175 | *
  • 204 in case success with headers only
  • 176 | *
  • 404 if resourceId is not found
  • 177 | *
  • 500 in case of system error.
  • 178 | *
179 | */ 180 | @HEAD 181 | @Path("{id}") 182 | public Response checkResource(@PathParam("id")final Long resourceId) { 183 | PersonEntity entity = repository.find(resourceId); 184 | if (entity != null) { 185 | return Response.ok(URLHelper.selfLink(uriInfo, entity.getId().toString(), this.getClass())) 186 | .status(Status.NO_CONTENT) 187 | .build(); 188 | } else { 189 | throw new NotFoundException("Unable to find " + resourceId); 190 | } 191 | } 192 | 193 | /** 194 | * Represents GET operation to retrieve a list of resources of type PersonEntity. 195 | * 196 | * @param offset 197 | * The start index of the list 198 | * @param limit 199 | * Max elements in the list 200 | * 201 | * @return {@link Response} with one of the following codes. 202 | *
    203 | *
  • 200 in case success with a list of entities of type T and navigation headers
  • 204 | *
  • 500 in case of system error.
  • 205 | *
206 | */ 207 | @GET 208 | public Response listResources( 209 | @QueryParam(URLHelper.PARAM_OFFSET) @DefaultValue(URLHelper.DEFAULT_OFFSET)int offset, 210 | @QueryParam(URLHelper.PARAM_LIMIT) @DefaultValue(URLHelper.DEFAULT_LIMIT) int limit) { 211 | 212 | List data = repository.list(PersonEntity.QUERY_FIND_ALL, offset, limit); 213 | Long count = repository.count(PersonEntity.QUERY_COUNT_ALL); 214 | 215 | ResponseBuilder builder = Response.ok().entity(data); 216 | URLHelper.addNavHeaders(builder, uriInfo, new PaginatedModel<>(offset, limit, count, data)); 217 | 218 | return builder.build(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /jee-fuse/src/main/java/com/sixturtle/web/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web.service; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import javax.inject.Inject; 8 | import javax.transaction.Transactional; 9 | import javax.validation.Valid; 10 | import javax.ws.rs.BadRequestException; 11 | import javax.ws.rs.Consumes; 12 | import javax.ws.rs.DELETE; 13 | import javax.ws.rs.DefaultValue; 14 | import javax.ws.rs.GET; 15 | import javax.ws.rs.HEAD; 16 | import javax.ws.rs.NotFoundException; 17 | import javax.ws.rs.POST; 18 | import javax.ws.rs.PUT; 19 | import javax.ws.rs.Path; 20 | import javax.ws.rs.PathParam; 21 | import javax.ws.rs.Produces; 22 | import javax.ws.rs.QueryParam; 23 | import javax.ws.rs.core.Context; 24 | import javax.ws.rs.core.MediaType; 25 | import javax.ws.rs.core.Response; 26 | import javax.ws.rs.core.UriInfo; 27 | import javax.ws.rs.core.Response.ResponseBuilder; 28 | import javax.ws.rs.core.Response.Status; 29 | 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | 33 | import com.sixturtle.db.RoleRepository; 34 | import com.sixturtle.db.UserRepository; 35 | import com.sixturtle.exception.InvalidEntityException; 36 | import com.sixturtle.exception.UnknownEntityException; 37 | import com.sixturtle.model.RoleEntity; 38 | import com.sixturtle.model.UserEntity; 39 | import com.sixturtle.web.PaginatedModel; 40 | import com.sixturtle.web.URLHelper; 41 | 42 | /** 43 | * Represents REST API for {@link UserEntity}. 44 | * 45 | * @author Anurag Sharma 46 | */ 47 | @Transactional 48 | @Path("/users") 49 | @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) 50 | @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) 51 | public class UserService { 52 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 53 | 54 | @Context 55 | protected UriInfo uriInfo; 56 | 57 | @Inject 58 | private UserRepository userRepository; 59 | 60 | @Inject 61 | private RoleRepository roleRepository; 62 | 63 | /** 64 | * @param repository the repository to set 65 | */ 66 | public void setUserRepository(UserRepository repository) { 67 | this.userRepository = repository; 68 | } 69 | 70 | /** 71 | * @param roleRepo the role repository to set 72 | */ 73 | public void setRoleRepository(RoleRepository roleRepo) { 74 | this.roleRepository = roleRepo; 75 | } 76 | 77 | /** 78 | * Represents POST operation to create a new resource. 79 | * 80 | * @param resource 81 | * The resource of type {@link UserEntity} 82 | * 83 | * @return {@link Response} with one of the following codes. 84 | *
    85 | *
  • 201 in case success with no content
  • 86 | *
  • 400 if input validation fails
  • 87 | *
  • 500 in case of system error.
  • 88 | *
89 | */ 90 | @POST 91 | public Response createResource(@Valid final UserEntity resource) { 92 | try { 93 | Set validRoles = new HashSet<>(); 94 | 95 | Set roles = resource.getRoles(); 96 | List dbRoles = roleRepository.list(RoleEntity.QUERY_GET_ALL_ROLES, 0, 10); 97 | for (RoleEntity role : roles) { 98 | RoleEntity found = null; 99 | for (RoleEntity dbRole : dbRoles) { 100 | if (role.getName() == dbRole.getName()) { 101 | found = dbRole; 102 | break; 103 | } 104 | } 105 | if (found == null) { 106 | found = roleRepository.create(role); 107 | } 108 | validRoles.add(found); 109 | } 110 | resource.getRoles().clear(); 111 | resource.getRoles().addAll(validRoles); 112 | UserEntity entity = userRepository.create(resource); 113 | log.debug("created entity: {}", entity); 114 | return Response.created(URLHelper.selfLink(uriInfo, entity.getId().toString(), this.getClass())) 115 | .build(); 116 | } catch (InvalidEntityException e) { 117 | throw new BadRequestException(e); 118 | } 119 | } 120 | 121 | /** 122 | * Represents PUT operation to update an existing resource. 123 | * 124 | * @param resourceId 125 | * The id of type {@link Long} for the resource of type {@link UserEntity} 126 | * @param resource 127 | * The resource of type {@link UserEntity} 128 | * 129 | * @return {@link Response} with one of the following codes. 130 | *
    131 | *
  • 204 in case success with no content
  • 132 | *
  • 404 if resourceId is not found
  • 133 | *
  • 400 if input validation fails
  • 134 | *
  • 500 in case of system error.
  • 135 | *
136 | */ 137 | @PUT 138 | @Path("{id}") 139 | public Response updateResource(@PathParam("id") final Long resourceId, @Valid final UserEntity resource) { 140 | try { 141 | UserEntity entity = userRepository.update(resourceId, resource); 142 | log.debug("updated entity: {}", entity); 143 | return Response.noContent().build(); 144 | } catch (InvalidEntityException e) { 145 | throw new BadRequestException(e); 146 | } catch (UnknownEntityException e) { 147 | throw new NotFoundException(e); 148 | } 149 | } 150 | 151 | /** 152 | * Represents DELETE operation to remove an existing resource. 153 | * 154 | * @param resourceId 155 | * The id of type {@link Long} for the resource of type {@link UserEntity} 156 | * 157 | * @return {@link Response} with one of the following codes. 158 | *
    159 | *
  • 204 in case success with no content
  • 160 | *
  • 404 if resourceId is not found
  • 161 | *
  • 500 in case of system error.
  • 162 | *
163 | */ 164 | @DELETE 165 | @Path("{id}") 166 | public Response deleteResource(@PathParam("id") final Long resourceId) { 167 | try { 168 | userRepository.delete(resourceId); 169 | log.debug("deleted entity: {}", resourceId); 170 | return Response.noContent().build(); 171 | } catch (UnknownEntityException e) { 172 | throw new NotFoundException(e); 173 | } 174 | } 175 | 176 | /** 177 | * Represents GET operation to retrieve an existing resource. 178 | * 179 | * @param resourceId 180 | * The id of type {@link Long} for the resource of type {@link UserEntity} 181 | * 182 | * @return {@link Response} with one of the following codes. 183 | *
    184 | *
  • 200 in case success with entity as T
  • 185 | *
  • 404 if resourceId is not found
  • 186 | *
  • 500 in case of system error.
  • 187 | *
188 | */ 189 | @GET 190 | @Path("{id}") 191 | public Response findResource(@PathParam("id")final Long resourceId) { 192 | UserEntity entity = userRepository.find(resourceId); 193 | if (entity != null) { 194 | return Response.ok().entity(entity).build(); 195 | } else { 196 | throw new NotFoundException("Unable to find " + resourceId); 197 | } 198 | } 199 | 200 | /** 201 | * Represents HEAD operation to retrieve an existing resource. 202 | * 203 | * @param resourceId 204 | * The id of type {@link Long} for the resource of type {@link UserEntity} 205 | * 206 | * @return {@link Response} with one of the following codes. 207 | *
    208 | *
  • 204 in case success with headers only
  • 209 | *
  • 404 if resourceId is not found
  • 210 | *
  • 500 in case of system error.
  • 211 | *
212 | */ 213 | @HEAD 214 | @Path("{id}") 215 | public Response checkResource(@PathParam("id")final Long resourceId) { 216 | UserEntity entity = userRepository.find(resourceId); 217 | if (entity != null) { 218 | return Response.ok(URLHelper.selfLink(uriInfo, entity.getId().toString(), this.getClass())) 219 | .status(Status.NO_CONTENT) 220 | .build(); 221 | } else { 222 | throw new NotFoundException("Unable to find " + resourceId); 223 | } 224 | } 225 | 226 | /** 227 | * Represents GET operation to retrieve a list of resources of type UserEntity. 228 | * 229 | * @param offset 230 | * The start index of the list 231 | * @param limit 232 | * Max elements in the list 233 | * 234 | * @return {@link Response} with one of the following codes. 235 | *
    236 | *
  • 200 in case success with a list of entities of type T and navigation headers
  • 237 | *
  • 500 in case of system error.
  • 238 | *
239 | */ 240 | @GET 241 | public Response listResources( 242 | @QueryParam(URLHelper.PARAM_OFFSET) @DefaultValue(URLHelper.DEFAULT_OFFSET)int offset, 243 | @QueryParam(URLHelper.PARAM_LIMIT) @DefaultValue(URLHelper.DEFAULT_LIMIT) int limit) { 244 | 245 | List data = userRepository.list(UserEntity.QUERY_FIND_ALL, offset, limit); 246 | Long count = userRepository.count(UserEntity.QUERY_COUNT_ALL); 247 | 248 | ResponseBuilder builder = Response.ok().entity(data); 249 | URLHelper.addNavHeaders(builder, uriInfo, new PaginatedModel<>(offset, limit, count, data)); 250 | 251 | return builder.build(); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /jee-fuse/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | Application Persistent Unit 11 | org.hibernate.jpa.HibernatePersistenceProvider 12 | java:jboss/datasources/ExampleDS 13 | 14 | com.sixturtle.model.Address 15 | com.sixturtle.model.PersonEntity 16 | com.sixturtle.model.RoleEntity 17 | com.sixturtle.model.UserEntity 18 | 19 | ENABLE_SELECTIVE 20 | CALLBACK 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /jee-fuse/src/main/resources/config/mashupConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8089", 3 | "apiPath": "/emailvalidator/validateEmail", 4 | "authcode": "*********" 5 | } -------------------------------------------------------------------------------- /jee-fuse/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /jee-fuse/src/main/webapp/WEB-INF/jboss-deployment-structure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /jee-fuse/src/main/webapp/WEB-INF/jboss-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | sixturtle 4 | -------------------------------------------------------------------------------- /jee-fuse/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | Sixturtle, LLC. - A Sample JAX-RS Application 11 | 12 | 13 | 14 | JAX-RS Providers 15 | resteasy.providers 16 | com.sixturtle.web.BasicExceptionMapper 17 | 18 | 19 | resteasy.role.based.security 20 | true 21 | 22 | 23 | -------------------------------------------------------------------------------- /jee-fuse/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sixturtle, LLC. 6 | 7 | 8 |

Welcome to REST API Documentation Page.

9 | 10 | -------------------------------------------------------------------------------- /jee-fuse/src/test/java/com/sixturtle/common/BasicJPATest.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.common; 2 | 3 | import java.io.File; 4 | import java.net.URL; 5 | 6 | import javax.persistence.EntityManager; 7 | import javax.persistence.FlushModeType; 8 | import javax.persistence.Persistence; 9 | import javax.validation.Validation; 10 | import javax.validation.Validator; 11 | import javax.validation.ValidatorFactory; 12 | 13 | import org.dbunit.database.DatabaseConnection; 14 | import org.dbunit.database.IDatabaseConnection; 15 | import org.dbunit.dataset.IDataSet; 16 | import org.dbunit.dataset.ITable; 17 | import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; 18 | import org.dbunit.operation.DatabaseOperation; 19 | import org.hibernate.internal.SessionImpl; 20 | import org.junit.After; 21 | import org.junit.AfterClass; 22 | import org.junit.Before; 23 | import org.junit.BeforeClass; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | /** 28 | * Defines fixture for starting and stopping the transaction for jUnit. The 29 | * transaction is wrapped around the test method and it is rolledback at the end 30 | * of each test. 31 | * 32 | *
33 | * 34 | * This class has been enhanced to include DbUnit framework for providing a test 35 | * data set specific to a test and removes use of hibernate import.sql which was 36 | * providing a test data set common of all the tests. 37 | * 38 | * @author Anurag Sharma 39 | */ 40 | public abstract class BasicJPATest { 41 | protected Logger log = LoggerFactory.getLogger(this.getClass()); 42 | 43 | protected static EntityManager em = null; 44 | protected static Validator validator = null; 45 | protected static IDatabaseConnection connection; 46 | 47 | /** 48 | * Runs once at the beginning of the test suite. 49 | * 50 | * @throws Exception 51 | * When there is a setup error 52 | */ 53 | @BeforeClass 54 | public static void setUpClass() throws Exception { 55 | if (em == null) { 56 | em = Persistence.createEntityManagerFactory("jUnitPersistenceUnit").createEntityManager(); 57 | em.setFlushMode(FlushModeType.AUTO); 58 | } 59 | 60 | final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); 61 | validator = validatorFactory.getValidator(); 62 | 63 | // Configure DbUnit using the same data source as JPA 64 | connection = new DatabaseConnection(((SessionImpl) (em.getDelegate())).connection()); 65 | } 66 | 67 | /** 68 | * Runs at the end of the test suite. 69 | */ 70 | @AfterClass 71 | public static void tearDownClass() { 72 | em = null; 73 | } 74 | 75 | /** 76 | * Start the transaction before the test. 77 | * @throws Exception 78 | */ 79 | @Before 80 | public void setup() { 81 | // Load DbUnit based test data set 82 | try { 83 | URL fileUrl = getDbUnitDataUrl(); 84 | log.debug("Loading DbUnit data file: {}", fileUrl); 85 | if (fileUrl != null) { 86 | IDataSet dataset = new FlatXmlDataSetBuilder().setColumnSensing(true) 87 | .build(new File(fileUrl.getPath())); 88 | DatabaseOperation.CLEAN_INSERT.execute(connection, dataset); 89 | } 90 | } catch (Exception e) { 91 | throw new RuntimeException(e); 92 | } 93 | 94 | log.debug("Starting a transaction"); 95 | em.getTransaction().begin(); 96 | } 97 | 98 | /** 99 | * Rollback the transaction before the test. 100 | */ 101 | @After 102 | public void teardown() { 103 | if (em.getTransaction().isActive()) { 104 | log.debug("Rolling back transaction"); 105 | em.getTransaction().rollback(); 106 | } 107 | } 108 | 109 | protected URL getDbUnitDataUrl() { 110 | return null; 111 | } 112 | 113 | protected ITable getDbUnitTable(String tableName) throws Exception { 114 | em.flush(); 115 | return connection.createDataSet().getTable(tableName); 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /jee-fuse/src/test/java/com/sixturtle/common/RestApiTest.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.common; 2 | 3 | import com.sixturtle.web.BasicExceptionMapper; 4 | import io.undertow.Undertow; 5 | 6 | import java.net.URL; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | import javax.ws.rs.ApplicationPath; 13 | import javax.ws.rs.client.Client; 14 | import javax.ws.rs.client.ClientBuilder; 15 | import javax.ws.rs.core.Application; 16 | 17 | import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; 18 | import org.junit.After; 19 | import org.junit.AfterClass; 20 | import org.junit.Before; 21 | import org.junit.BeforeClass; 22 | 23 | import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; 24 | 25 | /** 26 | * A base class for implementing integration tests for a set of REST API. 27 | * 28 | *

29 | * It uses {@link UndertowJaxrsServer} to deploy a local JAX-RS application and 30 | * allows a concrete test to inject a set of singletons for the application. 31 | *
32 | * This allows running a servlet in memory and exposing REST APIs as it would 33 | * have been running in a JEE container like Wildfly. 34 | *

35 | * 36 | *

37 | * It is also a subclass of {@link BasicJPATest} to leverage the running in 38 | * memory database. In memory database together with in memory 39 | * {@link UndertowJaxrsServer} provides end-to-end integration test capabilities 40 | * from within jUnit. 41 | *

42 | * 43 | *

44 | * It also provides a JAX-RS client instance to invoke APIs available from 45 | * {@link UndertowJaxrsServer}. This class takes care of registering 46 | * {@link JacksonJsonProvider} and {@link BasicExceptionMapper} for both server 47 | * and client side. 48 | *

49 | * 50 | * @author Anurag Sharma 51 | */ 52 | public abstract class RestApiTest extends BasicJPATest { 53 | private static final String HTTP_HOST = "localhost"; 54 | private static final int HTTP_PORT = 7788; 55 | 56 | protected static UndertowJaxrsServer server; 57 | protected static Set jaxrsSingletons = new HashSet<>(); 58 | private Object api; 59 | /** 60 | * Configures {@link BasicJPATest} and {@link UndertowJaxrsServer} with 61 | * {@link JacksonJsonProvider} and {@link BasicExceptionMapper} 62 | * 63 | * @throws Exception 64 | * in case of setup errors 65 | */ 66 | @BeforeClass 67 | public static void setUpClass() throws Exception { 68 | BasicJPATest.setUpClass(); 69 | 70 | jaxrsSingletons.add(new BasicExceptionMapper()); 71 | jaxrsSingletons.add(new JacksonJsonProvider()); 72 | 73 | server = new UndertowJaxrsServer().start( 74 | Undertow.builder() 75 | .addHttpListener(HTTP_PORT, HTTP_HOST)); 76 | } 77 | 78 | /** 79 | * Cleans {@link BasicJPATest} and stops {@link UndertowJaxrsServer} 80 | */ 81 | @AfterClass 82 | public static void tearDownClass() { 83 | BasicJPATest.tearDownClass(); 84 | server.stop(); 85 | } 86 | 87 | /** 88 | * Configures {@link BasicJPATest} and deploys {@link #buildJaxRsApiInstance()} 89 | * to {@link UndertowJaxrsServer}. It also instantiates {@link Client} with 90 | * the registered {@link JacksonJsonProvider}. 91 | */ 92 | @Before 93 | public void setup() { 94 | super.setup(); 95 | 96 | api = buildJaxRsApiInstance(); 97 | jaxrsSingletons.add(api); 98 | server.deploy(JunitRestApp.class); 99 | } 100 | 101 | /** 102 | * Cleans {@link BasicJPATest} and closes {@link Client}. 103 | */ 104 | @After 105 | public void teardown() { 106 | jaxrsSingletons.remove(api); 107 | super.teardown(); 108 | } 109 | 110 | /** 111 | * @return The base URL of {@link UndertowJaxrsServer} 112 | */ 113 | protected String getBaseUrl() { 114 | return "http://" + HTTP_HOST + ":" + HTTP_PORT + "/api"; 115 | } 116 | 117 | /** 118 | * The concrete test must implement this method to inject a CDI free 119 | * instance of JAX-RS API class. 120 | * 121 | * @return An instance of JAX-RS API class 122 | */ 123 | protected abstract Object buildJaxRsApiInstance(); 124 | 125 | /** 126 | * Represents a JAX-RS application available via {@link UndertowJaxrsServer}. 127 | * 128 | * @author Anurag Sharma 129 | */ 130 | @ApplicationPath("/api") 131 | public static class JunitRestApp extends Application { 132 | /* 133 | * (non-Javadoc) 134 | * @see javax.ws.rs.core.Application#getSingletons() 135 | */ 136 | @Override 137 | public Set getSingletons() { 138 | return jaxrsSingletons; 139 | } 140 | } 141 | 142 | /** 143 | * Loads content from a file. 144 | * 145 | * @param filepath The filepath 146 | * 147 | * @return The content of the file 148 | * 149 | * @throws Exception in case of IO error 150 | */ 151 | protected byte[] loadFile(String filepath) throws Exception { 152 | URL path = this.getClass().getResource(filepath); 153 | log.debug("Loading filepath: {}", path); 154 | byte[] json = Files.readAllBytes(Paths.get(path.toURI())); 155 | return json; 156 | } 157 | 158 | protected Client createClient() { 159 | return ClientBuilder.newClient().register(JacksonJsonProvider.class); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /jee-fuse/src/test/java/com/sixturtle/remote/service/EmailValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.remote.service; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import javax.ws.rs.core.HttpHeaders; 6 | import javax.ws.rs.core.MediaType; 7 | import javax.ws.rs.core.Response; 8 | 9 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 10 | 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | 14 | import com.github.tomakehurst.wiremock.junit.WireMockRule; 15 | 16 | /** 17 | * jUnit tests for {@link EmailValidator} API. 18 | * 19 | * @author Anurag Sharma 20 | */ 21 | public class EmailValidatorTest { 22 | protected WireMockRule wireMockRule = new WireMockRule(8089); 23 | 24 | @Rule 25 | public WireMockRule getWireMock() { 26 | return wireMockRule; 27 | } 28 | 29 | @Test 30 | public void testEmailSuccess() throws Exception { 31 | EmailValidatorImpl client = new EmailValidatorImpl(); 32 | String email = "john.doe@domain.com"; 33 | 34 | stubFor(get(urlPathEqualTo(client.getContext().getApiPath())) 35 | .withQueryParam("email", equalTo(email)) 36 | .willReturn( 37 | aResponse().withStatus(Response.Status.OK.getStatusCode()) 38 | .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) 39 | .withBodyFile("/valid-response.json"))); 40 | 41 | assertTrue("invalid response", client.isValidEmail(email)); 42 | } 43 | 44 | @Test 45 | public void testEmailFail() throws Exception { 46 | EmailValidatorImpl client = new EmailValidatorImpl(); 47 | String email = "invalid@domain.com"; 48 | 49 | stubFor(get(urlPathEqualTo(client.getContext().getApiPath())) 50 | .withQueryParam("email", equalTo(email)) 51 | .willReturn( 52 | aResponse().withStatus(Response.Status.OK.getStatusCode()) 53 | .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) 54 | .withBodyFile("/invalid-response.json"))); 55 | 56 | assertFalse("invalid response", client.isValidEmail(email)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jee-fuse/src/test/java/com/sixturtle/web/service/PersonServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web.service; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.Assert.fail; 6 | 7 | import java.net.URL; 8 | 9 | import javax.ws.rs.client.Client; 10 | import javax.ws.rs.client.Entity; 11 | import javax.ws.rs.core.MediaType; 12 | import javax.ws.rs.core.Response; 13 | 14 | import org.junit.Test; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.sixturtle.common.RestApiTest; 19 | import com.sixturtle.db.PersonRepository; 20 | import com.sixturtle.model.PersonEntity; 21 | 22 | /** 23 | * JUnit Test for PersonController. 24 | * 25 | * @author Anurag Sharma 26 | */ 27 | public class PersonServiceTest extends RestApiTest { 28 | private static final Logger log = LoggerFactory.getLogger(PersonServiceTest.class); 29 | 30 | /* 31 | * (non-Javadoc) 32 | * @see com.sixturtle.common.RestApiTest#buildJaxRsApiInstance() 33 | */ 34 | @Override 35 | protected Object buildJaxRsApiInstance() { 36 | PersonRepository repository = new PersonRepository(); 37 | repository.setEntityManager(em); 38 | repository.setValidator(validator); 39 | 40 | PersonService service = new PersonService(); 41 | service.setRepository(repository); 42 | return service; 43 | } 44 | 45 | /* 46 | * (non-Javadoc) 47 | * @see com.sixturtle.common.BasicJPATest#getDbUnitDataUrl() 48 | */ 49 | @Override 50 | protected URL getDbUnitDataUrl() { 51 | return this.getClass().getResource("/dbunit/person-test.xml"); 52 | } 53 | 54 | @Test 55 | public void testListPersons() { 56 | Client client = createClient(); 57 | 58 | try { 59 | int count = getDbUnitTable("Person").getRowCount(); 60 | Response r = client.target(getBaseUrl() + "/persons") 61 | .request() 62 | .accept(MediaType.APPLICATION_JSON) 63 | .get(); 64 | PersonEntity[] persons = r.readEntity(PersonEntity[].class); 65 | log.debug("\n headers: {}, \n persons: {}", r.getHeaders(), persons); 66 | assertTrue("Person count does not match", persons.length == count); 67 | } catch (Exception e) { 68 | log.error(e.getMessage(), e); 69 | fail(e.getMessage()); 70 | } finally { 71 | client.close(); 72 | } 73 | } 74 | 75 | @Test 76 | public void testCreatePerson() { 77 | Client client = createClient(); 78 | 79 | try { 80 | int count = getDbUnitTable("Person").getRowCount(); 81 | Response r = client.target(getBaseUrl() + "/persons") 82 | .request() 83 | .accept(MediaType.APPLICATION_JSON) 84 | .post(Entity.json(loadFile("/json/person-create.json"))); 85 | int newCount = getDbUnitTable("Person").getRowCount(); 86 | 87 | assertEquals("Invalid response code", Response.Status.CREATED.getStatusCode(), r.getStatus()); 88 | assertTrue("Created person not found in the table", count + 1 == newCount); 89 | } catch (Exception e) { 90 | log.error(e.getMessage(), e); 91 | fail(e.getMessage()); 92 | } finally { 93 | client.close(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /jee-fuse/src/test/java/com/sixturtle/web/service/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sixturtle.web.service; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.Assert.fail; 6 | 7 | import java.net.URL; 8 | 9 | import javax.ws.rs.client.Client; 10 | import javax.ws.rs.client.Entity; 11 | import javax.ws.rs.core.MediaType; 12 | import javax.ws.rs.core.Response; 13 | 14 | import org.junit.Test; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.sixturtle.common.RestApiTest; 19 | import com.sixturtle.db.RoleRepository; 20 | import com.sixturtle.db.UserRepository; 21 | import com.sixturtle.model.UserEntity; 22 | 23 | /** 24 | * JUnit Test for UserController. 25 | * 26 | * @author Anurag Sharma 27 | */ 28 | public class UserServiceTest extends RestApiTest { 29 | private static final Logger log = LoggerFactory.getLogger(UserServiceTest.class); 30 | 31 | /* 32 | * (non-Javadoc) 33 | * @see com.sixturtle.common.RestApiTest#buildJaxRsApiInstance() 34 | */ 35 | @Override 36 | protected Object buildJaxRsApiInstance() { 37 | UserRepository userRepository = new UserRepository(); 38 | userRepository.setEntityManager(em); 39 | userRepository.setValidator(validator); 40 | 41 | RoleRepository roleRepository = new RoleRepository(); 42 | roleRepository.setEntityManager(em); 43 | roleRepository.setValidator(validator); 44 | 45 | UserService service = new UserService(); 46 | service.setUserRepository(userRepository); 47 | service.setRoleRepository(roleRepository); 48 | return service; 49 | } 50 | 51 | /* 52 | * (non-Javadoc) 53 | * @see com.sixturtle.common.BasicJPATest#getDbUnitDataUrl() 54 | */ 55 | @Override 56 | protected URL getDbUnitDataUrl() { 57 | return this.getClass().getResource("/dbunit/user-test.xml"); 58 | } 59 | 60 | @Test 61 | public void testListUsers() { 62 | Client client = createClient(); 63 | 64 | try { 65 | int count = getDbUnitTable("User").getRowCount(); 66 | Response r = client.target(getBaseUrl() + "/users") 67 | .request() 68 | .accept(MediaType.APPLICATION_JSON) 69 | .get(); 70 | UserEntity[] users = r.readEntity(UserEntity[].class); 71 | log.debug("\n headers: {}, \n users: {}", r.getHeaders(), users); 72 | assertTrue("Users count does not match", users.length == count); 73 | } catch (Exception e) { 74 | log.error(e.getMessage(), e); 75 | fail(e.getMessage()); 76 | } finally { 77 | client.close(); 78 | } 79 | } 80 | 81 | @Test 82 | public void testCreateUser() { 83 | Client client = createClient(); 84 | 85 | try { 86 | int count = getDbUnitTable("User").getRowCount(); 87 | Response r = client.target(getBaseUrl() + "/users") 88 | .request() 89 | .accept(MediaType.APPLICATION_JSON) 90 | .post(Entity.json(loadFile("/json/user-create.json"))); 91 | int newCount = getDbUnitTable("User").getRowCount(); 92 | 93 | assertEquals("Invalid response code", Response.Status.CREATED.getStatusCode(), r.getStatus()); 94 | assertTrue("Created user not found in the table", count + 1 == newCount); 95 | } catch (Exception e) { 96 | log.error(e.getMessage(), e); 97 | fail(e.getMessage()); 98 | } finally { 99 | client.close(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | jUnit Persistent Unit 11 | 12 | org.hibernate.jpa.HibernatePersistenceProvider 13 | 14 | com.sixturtle.model.Address 15 | com.sixturtle.model.PersonEntity 16 | com.sixturtle.model.RoleEntity 17 | com.sixturtle.model.UserEntity 18 | 19 | CALLBACK 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/__files/invalid-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "isValid": false 3 | } -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/__files/valid-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "isValid": true 3 | } -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/dbunit/person-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/dbunit/user-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/json/person-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "firstName": "First", 3 | "lastName": "Last", 4 | "email": "first.last@sixturtle.com", 5 | "phone": "404-111-2222" 6 | } -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/json/user-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "person": { 3 | "firstName": "First", 4 | "lastName": "Last", 5 | "email": "first.last@sixturtle.com", 6 | "phone": "404-111-2222" 7 | }, 8 | "roles": [ 9 | { 10 | "name": "User", 11 | "description": "User Role" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /jee-fuse/src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /rest-spring-tomcat/.gitignore: -------------------------------------------------------------------------------- 1 | .eclipse/ 2 | .gradle/ 3 | .idea/ 4 | .settings/ 5 | bin/ 6 | build/ 7 | .classpath 8 | .project 9 | *.eml 10 | *.idea 11 | *.iml 12 | *~ 13 | #* 14 | 15 | /bin-test/ 16 | -------------------------------------------------------------------------------- /rest-spring-tomcat/build.gradle: -------------------------------------------------------------------------------- 1 | // Project definition 2 | description = 'Spring MVC for REST Api' 3 | group = "sixturtle" 4 | version = 1.0 5 | 6 | 7 | // Standard plugins 8 | apply plugin: 'war' 9 | apply plugin: 'eclipse-wtp' 10 | apply plugin: 'jacoco' 11 | apply plugin: 'checkstyle' 12 | 13 | 14 | // project properties appended to gradle ext namespace 15 | ext { 16 | // Provided libs 17 | servletVersion = '3.1.0' 18 | springVersion = '4.2.5.RELEASE' 19 | jacksonVersion = '2.5.3' 20 | jaxbVersion = '2.2.11' 21 | 22 | hibernateVersion = '5.1.0.Final' 23 | jpa21Version = '1.0.0.Final' 24 | hsqldbVersion = '2.3.2' 25 | 26 | slf4jVersion = "1.7.19" 27 | log4jVersion = "1.2.17" 28 | 29 | // jUnit libs 30 | junitVersion = "4.11" 31 | } 32 | 33 | // Java compatibility 34 | sourceCompatibility = 1.8 35 | targetCompatibility = 1.8 36 | 37 | // repositories locations 38 | repositories { 39 | mavenLocal() 40 | mavenCentral() 41 | } 42 | 43 | // define a custom scope for dependencies 44 | configurations { 45 | provided 46 | } 47 | 48 | configurations.all { 49 | // To examine dependencies, comment failOnVersionConflict() and run following commands to see conflicts 50 | // $ gradle dependencies --configuration testRuntime 51 | // $ gradle dependencyInsight --configuration testRuntime --dependency "org.apache.httpcomponents" 52 | resolutionStrategy { 53 | // fail eagerly on version conflict (includes transitive dependencies) 54 | // e.g. multiple different versions of the same dependency (group and name are equal) 55 | failOnVersionConflict() 56 | } 57 | } 58 | 59 | // configure custom scope of dependencies on the sourceSets 60 | sourceSets { 61 | main { compileClasspath += configurations.provided } 62 | } 63 | eclipse { 64 | classpath { 65 | plusConfigurations += [ configurations.provided ] 66 | } 67 | } 68 | 69 | // checkstyle configuration 70 | checkstyle { 71 | ignoreFailures = false 72 | configFile = new File(rootDir, "etc/checkstyle/checkstyle.xml") 73 | configProperties.checkstyleConfigDir = configFile.parentFile 74 | } 75 | 76 | // define standard dependencies common to all sub projects 77 | dependencies { 78 | // required for compilation and also packaged 79 | compile ( 80 | ["org.springframework:spring-webmvc:$springVersion"], 81 | ["org.springframework.data:spring-data-jpa:1.10.1.RELEASE"], 82 | 83 | ["com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"], 84 | ["javax.xml.bind:jaxb-api:$jaxbVersion"], 85 | ["com.sun.xml.bind:jaxb-impl:$jaxbVersion"], 86 | ["com.sun.xml.bind:jaxb-core:$jaxbVersion"], 87 | 88 | ["org.slf4j:slf4j-api:$slf4jVersion"], 89 | ["org.slf4j:slf4j-log4j12:$slf4jVersion"], 90 | 91 | ["org.hibernate:hibernate-entitymanager:$hibernateVersion"], 92 | ["org.hibernate.javax.persistence:hibernate-jpa-2.1-api:$jpa21Version"], 93 | 94 | ["org.hsqldb:hsqldb:$hsqldbVersion"], 95 | ["net.sourceforge.jtds:jtds:1.3.1"], 96 | ) 97 | 98 | // required for compilation but not packaged 99 | providedCompile ( 100 | ["javax.servlet:javax.servlet-api:$servletVersion"] 101 | ) 102 | 103 | // required only for test 104 | testCompile ( 105 | ["junit:junit:$junitVersion"], 106 | ) 107 | } 108 | 109 | // customize war package 110 | war { 111 | archiveName "sixturtle.war" 112 | } 113 | -------------------------------------------------------------------------------- /rest-spring-tomcat/etc/azure/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "hostingPlanName": { 6 | "type": "string", 7 | "defaultValue": "casino-service-plan", 8 | "minLength": 1, 9 | "metadata": { 10 | "description": "Name of the hosting plan to use in Azure." 11 | } 12 | }, 13 | "webSiteName": { 14 | "type": "string", 15 | "defaultValue": "casinoworld", 16 | "minLength": 1, 17 | "metadata": { 18 | "description": "Name of the Azure Web app to create." 19 | } 20 | }, 21 | "skuName": { 22 | "type": "string", 23 | "defaultValue": "S1", 24 | "allowedValues": [ 25 | "F1", 26 | "D1", 27 | "B1", 28 | "B2", 29 | "B3", 30 | "S1", 31 | "S2", 32 | "S3", 33 | "P1", 34 | "P2", 35 | "P3", 36 | "P4" 37 | ], 38 | "metadata": { 39 | "description": "Describes plan's pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/" 40 | } 41 | }, 42 | "skuCapacity": { 43 | "type": "int", 44 | "defaultValue": 1, 45 | "minValue": 1, 46 | "metadata": { 47 | "description": "Describes plan's instance count" 48 | } 49 | } 50 | }, 51 | "resources": [ 52 | { 53 | "apiVersion": "2015-08-01", 54 | "name": "[parameters('hostingPlanName')]", 55 | "type": "Microsoft.Web/serverfarms", 56 | "location": "[resourceGroup().location]", 57 | "tags": { 58 | "displayName": "HostingPlan" 59 | }, 60 | "sku": { 61 | "name": "[parameters('skuName')]", 62 | "capacity": "[parameters('skuCapacity')]" 63 | }, 64 | "properties": { 65 | "name": "[parameters('hostingPlanName')]" 66 | } 67 | }, 68 | { 69 | "apiVersion": "2015-08-01", 70 | "name": "[parameters('webSiteName')]", 71 | "type": "Microsoft.Web/sites", 72 | "location": "[resourceGroup().location]", 73 | "tags": { 74 | "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", 75 | "displayName": "Website" 76 | }, 77 | "dependsOn": [ 78 | "[concat('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]" 79 | ], 80 | "properties": { 81 | "name": "[parameters('webSiteName')]", 82 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]" 83 | }, 84 | "resources": [ 85 | { 86 | "apiVersion": "2015-08-01", 87 | "name": "web", 88 | "type": "config", 89 | "dependsOn": [ 90 | "[concat('Microsoft.Web/sites/', parameters('webSiteName'))]" 91 | ], 92 | "properties": { 93 | "javaVersion": "1.8", 94 | "javaContainer": "TOMCAT", 95 | "javaContainerVersion": "8.0" 96 | } 97 | }, 98 | { 99 | "apiVersion": "2015-08-01", 100 | "name": "appsettings", 101 | "type": "config", 102 | "dependsOn": [ 103 | "[concat('Microsoft.Web/sites/', parameters('webSiteName'))]" 104 | ], 105 | "properties": { 106 | "WEBSITE_NODE_DEFAULT_VERSION": "4.2.3", 107 | "config.dir": "d:\\home\\site\\wwwroot" 108 | } 109 | } 110 | ] 111 | } 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /rest-spring-tomcat/etc/azure/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "hostingPlanName": { 6 | "value": "casino-service-plan" 7 | }, 8 | "skuName": { 9 | "value": "S1" 10 | }, 11 | "webSiteName": { 12 | "value": "casinoworld" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /rest-spring-tomcat/etc/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /rest-spring-tomcat/etc/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /rest-spring-tomcat/gradle.properties: -------------------------------------------------------------------------------- 1 | currentVersion=1.0.0 2 | org.gradle.daemon=true 3 | -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/java/com/sixturtle/configuration/ApplicationConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, Sixturtle, LLC. 3 | * All rights reserved 4 | */ 5 | package com.sixturtle.configuration; 6 | 7 | import java.util.Properties; 8 | 9 | import javax.persistence.EntityManagerFactory; 10 | import javax.sql.DataSource; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.ComponentScan; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.context.annotation.PropertySource; 19 | import org.springframework.core.env.Environment; 20 | import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; 21 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 22 | import org.springframework.jdbc.datasource.DriverManagerDataSource; 23 | import org.springframework.orm.jpa.JpaTransactionManager; 24 | import org.springframework.orm.jpa.JpaVendorAdapter; 25 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 26 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 27 | import org.springframework.transaction.PlatformTransactionManager; 28 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 29 | 30 | /** 31 | * @author Anurag Sharma 32 | */ 33 | @Configuration 34 | @EnableWebMvc 35 | @ComponentScan(basePackages = { "com.sixturtle" }) 36 | @EnableJpaRepositories(basePackages = { "com.sixturtle.service" }) 37 | @PropertySource(value = { 38 | "classpath:application.properties", 39 | "file:${config.dir}/application.properties" 40 | }, ignoreResourceNotFound = true) 41 | public class ApplicationConfiguration { 42 | private Logger log = LoggerFactory.getLogger(ApplicationConfiguration.class); 43 | 44 | @Autowired 45 | Environment env; 46 | 47 | /** 48 | * @return {@link LocalContainerEntityManagerFactoryBean} 49 | */ 50 | @Bean 51 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 52 | log.debug("Catalina Home: {}", env.getProperty("catalina.home")); 53 | log.debug("all env content: {}", env); 54 | 55 | LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); 56 | em.setDataSource(dataSource()); 57 | em.setPackagesToScan(new String[] { "com.sixturtle.model" }); 58 | 59 | JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); 60 | em.setJpaVendorAdapter(vendorAdapter); 61 | em.setJpaProperties(additionalProperties()); 62 | 63 | return em; 64 | } 65 | 66 | /** 67 | * @return {@link DataSource} 68 | */ 69 | @Bean 70 | public DataSource dataSource() { 71 | System.out.println(env); 72 | 73 | DriverManagerDataSource dataSource = new DriverManagerDataSource(); 74 | dataSource.setDriverClassName(env.getRequiredProperty("db.driver")); 75 | dataSource.setUrl(env.getRequiredProperty("db.url")); 76 | dataSource.setUsername(env.getRequiredProperty("db.username")); 77 | dataSource.setPassword(env.getRequiredProperty("db.password")); 78 | return dataSource; 79 | } 80 | 81 | /** 82 | * @param emf {@link EntityManagerFactory} 83 | * @return {@link PlatformTransactionManager} 84 | */ 85 | @Bean 86 | public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { 87 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 88 | transactionManager.setEntityManagerFactory(emf); 89 | 90 | return transactionManager; 91 | } 92 | 93 | /** 94 | * @return {@link PersistenceExceptionTranslationPostProcessor} 95 | */ 96 | @Bean 97 | public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { 98 | return new PersistenceExceptionTranslationPostProcessor(); 99 | } 100 | 101 | /** 102 | * @return {@link Properties} 103 | */ 104 | Properties additionalProperties() { 105 | Properties properties = new Properties(); 106 | properties.setProperty("hibernate.hbm2ddl.auto", env.getRequiredProperty("hibernate.hbm2ddl.auto")); 107 | properties.setProperty("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql")); 108 | properties.setProperty("hibernate.format_sql", env.getRequiredProperty("hibernate.format_sql")); 109 | return properties; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/java/com/sixturtle/configuration/ApplicationInitializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, Sixturtle, LLC. 3 | * All rights reserved 4 | */ 5 | package com.sixturtle.configuration; 6 | 7 | import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; 8 | 9 | /** 10 | * @author Anurag Sharma 11 | */ 12 | public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { 13 | 14 | /* 15 | * (non-Javadoc) 16 | * @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#getRootConfigClasses() 17 | */ 18 | @Override 19 | protected Class[] getRootConfigClasses() { 20 | return new Class[] { ApplicationConfiguration.class }; 21 | } 22 | 23 | /* 24 | * (non-Javadoc) 25 | * @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#getServletConfigClasses() 26 | */ 27 | @Override 28 | protected Class[] getServletConfigClasses() { 29 | return null; 30 | } 31 | 32 | /* 33 | * (non-Javadoc) 34 | * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#getServletMappings() 35 | */ 36 | @Override 37 | protected String[] getServletMappings() { 38 | return new String[] { "/" }; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/java/com/sixturtle/controller/PersonController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, Sixturtle, LLC. 3 | * All rights reserved 4 | */ 5 | package com.sixturtle.controller; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.util.StringUtils; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.ResponseBody; 16 | import org.springframework.web.bind.annotation.ResponseStatus; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import com.sixturtle.model.Person; 20 | import com.sixturtle.model.PersonList; 21 | import com.sixturtle.service.PersonRepository; 22 | 23 | /** 24 | * @author Anurag Sharma 25 | */ 26 | @RestController 27 | @RequestMapping (value = "/persons") 28 | class PersonController { 29 | 30 | @Autowired 31 | PersonRepository personRepository; 32 | 33 | /** 34 | * @param email The person email address 35 | * @return {@link PersonList} 36 | */ 37 | @RequestMapping (method = RequestMethod.GET) 38 | @ResponseBody 39 | public PersonList listAll(@RequestParam(value = "email", required = false) String email) { 40 | PersonList persons = new PersonList(); 41 | 42 | if (!StringUtils.isEmpty(email)) { 43 | Person p = personRepository.findByEmail(email); 44 | persons.getPersons().add(p); 45 | } else { 46 | Iterable result = personRepository.findAll(); 47 | for (Person p : result) { 48 | persons.getPersons().add(p); 49 | } 50 | } 51 | 52 | return persons; 53 | } 54 | 55 | /** 56 | * @param id The person ID 57 | * @return {@link Person} 58 | */ 59 | @RequestMapping (value = "{id}", method = RequestMethod.GET) 60 | @ResponseBody 61 | public Person findById(@PathVariable("id") long id) { 62 | return personRepository.findOne(id); 63 | } 64 | 65 | /** 66 | * @param p {@link Person} 67 | */ 68 | @RequestMapping (method = RequestMethod.POST) 69 | @ResponseStatus (code = HttpStatus.CREATED) 70 | public void create(@RequestBody Person p) { 71 | personRepository.save(p); 72 | } 73 | 74 | /** 75 | * @param p {@link Person} 76 | */ 77 | @RequestMapping (method = RequestMethod.PUT) 78 | public void update(@RequestBody Person p) { 79 | personRepository.save(p); 80 | } 81 | 82 | /** 83 | * @param id {@link Person} ID 84 | */ 85 | @RequestMapping (method = RequestMethod.DELETE) 86 | public void delete(@PathVariable("id") long id) { 87 | personRepository.delete(id); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/java/com/sixturtle/model/Person.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, Sixturtle, LLC. 3 | * All rights reserved 4 | */ 5 | package com.sixturtle.model; 6 | 7 | import java.io.Serializable; 8 | 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.xml.bind.annotation.XmlRootElement; 14 | 15 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 16 | import com.fasterxml.jackson.annotation.JsonInclude; 17 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 18 | 19 | /** 20 | * @author Anurag Sharma 21 | */ 22 | @Entity 23 | @XmlRootElement (name = "person") 24 | @JsonInclude(Include.NON_NULL) 25 | @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) 26 | public class Person implements Serializable { 27 | private static final long serialVersionUID = 3767104162666435761L; 28 | 29 | @Id 30 | @GeneratedValue (strategy = GenerationType.AUTO) 31 | private long id; 32 | 33 | private String firstName; 34 | private String middleName; 35 | private String lastName; 36 | private String email; 37 | private String phone; 38 | 39 | /** 40 | * Default constructor 41 | */ 42 | public Person() { 43 | } 44 | 45 | /** 46 | * Field constructor 47 | * 48 | * @param firstName 49 | * The first name 50 | * @param middleName 51 | * The middle name 52 | * @param lastName 53 | * The last name 54 | * @param email 55 | * The email address 56 | * @param phone 57 | * The phone number 58 | */ 59 | public Person( 60 | String firstName, 61 | String middleName, 62 | String lastName, 63 | String email, 64 | String phone) { 65 | super(); 66 | this.firstName = firstName; 67 | this.middleName = middleName; 68 | this.lastName = lastName; 69 | this.email = email; 70 | this.phone = phone; 71 | } 72 | 73 | 74 | /** 75 | * @return the firstName 76 | */ 77 | public String getFirstName() { 78 | return firstName; 79 | } 80 | /** 81 | * @param firstName the firstName to set 82 | */ 83 | public void setFirstName(String firstName) { 84 | this.firstName = firstName; 85 | } 86 | /** 87 | * @return the middleName 88 | */ 89 | public String getMiddleName() { 90 | return middleName; 91 | } 92 | /** 93 | * @param middleName the middleName to set 94 | */ 95 | public void setMiddleName(String middleName) { 96 | this.middleName = middleName; 97 | } 98 | /** 99 | * @return the lastName 100 | */ 101 | public String getLastName() { 102 | return lastName; 103 | } 104 | /** 105 | * @param lastName the lastName to set 106 | */ 107 | public void setLastName(String lastName) { 108 | this.lastName = lastName; 109 | } 110 | /** 111 | * @return the email 112 | */ 113 | public String getEmail() { 114 | return email; 115 | } 116 | /** 117 | * @param email the email to set 118 | */ 119 | public void setEmail(String email) { 120 | this.email = email; 121 | } 122 | /** 123 | * @return the phone 124 | */ 125 | public String getPhone() { 126 | return phone; 127 | } 128 | /** 129 | * @param phone the phone to set 130 | */ 131 | public void setPhone(String phone) { 132 | this.phone = phone; 133 | } 134 | /* (non-Javadoc) 135 | * @see java.lang.Object#toString() 136 | */ 137 | @Override 138 | public String toString() { 139 | StringBuilder builder = new StringBuilder(); 140 | builder.append("Person [firstName=").append(firstName).append(", middleName=").append(middleName) 141 | .append(", lastName=").append(lastName).append(", email=").append(email).append(", phone=") 142 | .append(phone).append("]"); 143 | return builder.toString(); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/java/com/sixturtle/model/PersonList.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, Sixturtle, LLC. 3 | * All rights reserved 4 | */ 5 | package com.sixturtle.model; 6 | 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import javax.xml.bind.annotation.XmlElement; 12 | import javax.xml.bind.annotation.XmlRootElement; 13 | 14 | /** 15 | * @author Anurag Sharma 16 | */ 17 | @XmlRootElement (name = "persons") 18 | public class PersonList implements Serializable { 19 | private static final long serialVersionUID = -7602890372816844053L; 20 | List persons = new ArrayList<>(); 21 | /** 22 | * @return the persons 23 | */ 24 | @XmlElement (name = "person") 25 | public List getPersons() { 26 | return persons; 27 | } 28 | /** 29 | * @param persons the persons to set 30 | */ 31 | public void setPersons(List persons) { 32 | this.persons = persons; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/java/com/sixturtle/service/PersonRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, Sixturtle, LLC. 3 | * All rights reserved 4 | */ 5 | package com.sixturtle.service; 6 | 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import com.sixturtle.model.Person; 12 | 13 | /** 14 | * @author Anurag Sharma 15 | */ 16 | @Service ("personRepository") 17 | @Transactional 18 | public interface PersonRepository extends JpaRepository { 19 | /** 20 | * @param email The email address 21 | * @return {@link Person} 22 | */ 23 | Person findByEmail(String email); 24 | } 25 | -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #Database Configuration 2 | 3 | # HSQL Database 4 | db.driver=org.hsqldb.jdbcDriver 5 | db.url=jdbc:hsqldb:mem:sixturtledb 6 | db.username=sa 7 | db.password= 8 | 9 | #Hibernate Configuration 10 | hibernate.hbm2ddl.auto=create-drop 11 | hibernate.show_sql=true 12 | hibernate.format_sql=false -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO PERSON (ID, FIRSTNAME, MIDDLENAME, LASTNAME, EMAIL, PHONE) VALUES (1, 'John', null, 'Doe', 'john.doe@sixturtle.com', '(512) 456-1001'); 2 | INSERT INTO PERSON (ID, FIRSTNAME, MIDDLENAME, LASTNAME, EMAIL, PHONE) VALUES (2, 'Mary', null, 'Doe', 'mary.doe@sixturtle.com', '(512) 456-1002'); 3 | ALTER SEQUENCE hibernate_sequence RESTART WITH 3; -------------------------------------------------------------------------------- /rest-spring-tomcat/src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'rest-spring-tomcat' 2 | --------------------------------------------------------------------------------