Accessing server resources via RESTful endpoints

So far, we have created models, components, services, and service implementations, as well as integrated Spring Security into the messenger application. One thing we have not done is actually created any means by which external clients can communicate with the messenger API. We are going to do this by creating controller classes that handles requests from different HTTP request paths. As always, the first thing we must do is create a package to contain the controllers we are about to create. Create a controllers package now.

The first controller we will implement is the UserController. This controller maps HTTP requests pertaining to a user resource to in-class actions that handle and respond to the HTTP request. First and foremost, we need an endpoint to facilitate the registration of new users. We will call the action that handles such a registration request create. The following is the UserController code with the create action:

package com.example.messenger.api.controllers

import com.example.messenger.api.models.User
import com.example.messenger.api.repositories.UserRepository
import com.example.messenger.api.services.UserServiceImpl
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import javax.servlet.http.HttpServletRequest

@RestController
@RequestMapping("/users")
class UserController(val userService: UserServiceImpl,
val
userRepository: UserRepository) {

@PostMapping
@RequestMapping("/registrations")
fun create(@Validated @RequestBody userDetails: User):
ResponseEntity<User> {
val user = userService.attemptRegistration(userDetails)
return ResponseEntity.ok(user)
}
}

The controller class is annotated with @RestController and @RequestMapping. The @RestController annotation specifies that a class is a REST controller. @RequestMapping, as it is used with the UserController class earlier, maps all requests with paths starting with /users to UserController

The create function is annotated with @PostMapping and @RequestMapping("/registrations"). The combination of these two annotations maps all POST requests with the /users/registrations path to the create function. A User instance annotated with @Validated and @RequestBody is passed to create@RequestBody binds the JSON values sent in the body of the POST request to userDetails. @Validated ensures that the JSON parameters are validated. Now that we have an endpoint up and running, let's test it out. Start the application and navigate to your terminal window. Send a request to the messenger API using CURL, as follows:

curl -H "Content-Type: application/json" -X POST -d '{"username":"kevin.stacey",
"phoneNumber":"5472457893",
"password":"Hello123"}'
http://localhost:8080/users/registrations

The server will create the user and send you a response similar to the following:

{
"username":"kevin.stacey",
"phoneNumber":"5472457893",
"password":"XXX XXXX XXX",
"status":"available",
"accountStatus":"activated",
"id":6,"createdAt":1508579448634
}

That's all fine and good, but we can see there are a number of unwanted values in the HTTP response, such as the password and accountStatus response parameters. In addition to this, we'd like for createdAt to contain a human-readable date. We are going to do all these things using an assembler and a value object.

First, let's make the value object. The value object we are creating is going to contain the data of the user that we want to be sent to the client in its appropriate form and nothing more. Create a helpers.objects package with a ValueObjects.kt file in it:

package com.example.messenger.api.helpers.objects

data class UserVO(
val id: Long,
val username: String,
val phoneNumber: String,
val status: String,
val createdAt: String
)

As you can see, UserVO is a data class that models the information we want to be sent to the user and nothing more. While we are at it, let's add value objects for some other responses we will cater for later, to avoid coming back to this file:

package com.example.messenger.api.helpers.objects

data class UserVO(
val id: Long,
val username: String,
val phoneNumber: String,
val status: String,
val createdAt: String
)

data class UserListVO(
val users: List<UserVO>
)

data class MessageVO(
val id: Long,
val senderId: Long?,
val recipientId: Long?,
val conversationId: Long?,
val body: String?,
val createdAt: String
)

data class ConversationVO(
val conversationId: Long,
val secondPartyUsername: String,
val messages: ArrayList<MessageVO>
)

data class ConversationListVO(
val conversations: List<ConversationVO>
)

Now that we have the required value objects set, let's create an assembler for UserVO. An assembler is simply a component that assembles a required object value. We will call the assembler we are creating UserAssembler. As it's a component, it belongs in the components package:

package com.example.messenger.api.components

import com.example.messenger.api.helpers.objects.UserListVO
import com.example.messenger.api.helpers.objects.UserVO
import com.example.messenger.api.models.User
import org.springframework.stereotype.Component

@Component
class UserAssembler {

fun toUserVO(user: User): UserVO {
return UserVO(user.id, user.username, user.phoneNumber,
user.status, user.createdAt.toString())
}

fun toUserListVO(users: List<User>): UserListVO {
val userVOList = users.map { toUserVO(it) }
return UserListVO(userVOList)
}
}

The assembler has a single toUserVO() function that takes a User as its argument and returns a corresponding UserVOtoUserListVO() takes a list of User instances and returns a corresponding UserListVO.

Now let's edit the create endpoint to make use of UserAssembler and UserVO:

package com.example.messenger.api.controllers

import com.example.messenger.api.components.UserAssembler
import com.example.messenger.api.helpers.objects.UserVO
import com.example.messenger.api.models.User
import com.example.messenger.api.repositories.UserRepository
import com.example.messenger.api.services.UserServiceImpl
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import javax.servlet.http.HttpServletRequest


@RestController
@RequestMapping("/users")
class UserController(val userService: UserServiceImpl,
val userAssembler: UserAssembler,
val
userRepository: UserRepository) {

@PostMapping
@RequestMapping("/registrations")
fun create(@Validated @RequestBody userDetails: User):
ResponseEntity<UserVO> {
val user = userService.attemptRegistration(userDetails)
return ResponseEntity.ok(userAssembler.toUserVO(user))
}
}

Restart the server and send a new request to register a User. We will get a response that is much more appropriate from the API:

{
"id":6,
"username":"kevin.stacey",
"phoneNumber":"5472457893",
"status":"available",
"createdAt":"Sat Oct 21 11:11:36 WAT 2017"
}

Let's wrap up our endpoint creation process by creating all the necessary endpoints for the messenger Android application. Firstly, let's add endpoints to show the details of a User, list all users, get the details of the current user, and update the status of a User to UserController:

package com.example.messenger.api.controllers

import com.example.messenger.api.components.UserAssembler
import com.example.messenger.api.helpers.objects.UserListVO
import com.example.messenger.api.helpers.objects.UserVO
import com.example.messenger.api.models.User
import com.example.messenger.api.repositories.UserRepository
import com.example.messenger.api.services.UserServiceImpl
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import javax.servlet.http.HttpServletRequest

@RestController
@RequestMapping("/users")
class UserController(val userService: UserServiceImpl,
val userAssembler: UserAssembler,
val
userRepository: UserRepository) {

@PostMapping
@RequestMapping("/registrations")
fun create(@Validated @RequestBody userDetails: User):
ResponseEntity<UserVO> {
val user = userService.attemptRegistration(userDetails)
return ResponseEntity.ok(userAssembler.toUserVO(user))
}

@GetMapping
@RequestMapping("/{user_id}")
fun show(@PathVariable("user_id") userId: Long):
ResponseEntity<UserVO> {
val user = userService.retrieveUserData(userId)
return ResponseEntity.ok(userAssembler.toUserVO(user))
}

@GetMapping
@RequestMapping("/details")
fun echoDetails(request: HttpServletRequest): ResponseEntity<UserVO>{
val user = userRepository.findByUsername
(request.userPrincipal.name) as User
return ResponseEntity.ok(userAssembler.toUserVO(user))
}

@GetMapping
fun index(request: HttpServletRequest): ResponseEntity<UserListVO> {
val user = userRepository.findByUsername
(request.userPrincipal.name) as User
val users = userService.listUsers(user)

return ResponseEntity.ok(userAssembler.toUserListVO(users))
}

@PutMapping
fun update(@RequestBody updateDetails: User,
request: HttpServletRequest): ResponseEntity<UserVO> {
val currentUser = userRepository.findByUsername
(request.userPrincipal.name)
userService.updateUserStatus(currentUser as User, updateDetails)
return ResponseEntity.ok(userAssembler.toUserVO(currentUser))
}
}

Now we are going to create controllers to handle message resources and conversation resources. These will be MessageController and ConversationController, respectively. Before creating the controllers, let's assemblers that will be used to assemble value objects from JPA entities. The following is the MessageAssembler:

package com.example.messenger.api.components

import com.example.messenger.api.helpers.objects.MessageVO
import com.example.messenger.api.models.Message
import org.springframework.stereotype.Component

@Component
class MessageAssembler {
fun toMessageVO(message: Message): MessageVO {
return MessageVO(message.id, message.sender?.id,
message.recipient?.id, message.conversation?.id,
message.body, message.createdAt.toString())
}
}

And now, let's create the ConversationAssembler, as follows:

package com.example.messenger.api.components

import com.example.messenger.api.helpers.objects.ConversationListVO
import com.example.messenger.api.helpers.objects.ConversationVO
import com.example.messenger.api.helpers.objects.MessageVO
import com.example.messenger.api.models.Conversation
import com.example.messenger.api.services.ConversationServiceImpl
import org.springframework.stereotype.Component


@Component
class ConversationAssembler(val conversationService:
ConversationServiceImpl,
val
messageAssembler: MessageAssembler) {

fun toConversationVO(conversation: Conversation, userId: Long): ConversationVO {
val conversationMessages: ArrayList<MessageVO> = ArrayList()
conversation.messages.mapTo(conversationMessages) {
messageAssembler.toMessageVO(it)
}
return ConversationVO(conversation.id, conversationService
.nameSecondParty(conversation, userId),
conversationMessages)
}

fun toConversationListVO(conversations: ArrayList<Conversation>,
userId: Long): ConversationListVO {
val conversationVOList = conversations.map { toConversationVO(it,
userId) }
return ConversationListVO(conversationVOList)
}
}

All is in place for MessageController and ConversationController. For our simple messenger app, we only need to have a message creation action for MessageController. The following is MessageController with the message creation action, create:

package com.example.messenger.api.controllers

import com.example.messenger.api.components.MessageAssembler
import com.example.messenger.api.helpers.objects.MessageVO
import com.example.messenger.api.models.User
import com.example.messenger.api.repositories.UserRepository
import com.example.messenger.api.services.MessageServiceImpl
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import javax.servlet.http.HttpServletRequest

@RestController
@RequestMapping("/messages")
class MessageController(val messageService: MessageServiceImpl,
val userRepository: UserRepository,
val
messageAssembler: MessageAssembler) {

@PostMapping
fun create(@RequestBody messageDetails: MessageRequest,
request: HttpServletRequest): ResponseEntity<MessageVO> {
val principal = request.userPrincipal
val sender = userRepository.findByUsername(principal.name) as User
val message = messageService.sendMessage(sender,
messageDetails.recipientId, messageDetails.message)
return ResponseEntity.ok(messageAssembler.toMessageVO(message))
}

data class MessageRequest(val recipientId: Long, val message: String)
}

Lastly, we must create ConversationController. We need only two endpoints: one to list all the active conversations of a user and the other to get the messages existing in a conversation thread. These endpoints will be catered to by the list() and show() actions, respectively. The following is the ConversationController class:

package com.example.messenger.api.controllers

import com.example.messenger.api.components.ConversationAssembler
import com.example.messenger.api.helpers.objects.ConversationListVO
import com.example.messenger.api.helpers.objects.ConversationVO
import com.example.messenger.api.models.User
import com.example.messenger.api.repositories.UserRepository
import com.example.messenger.api.services.ConversationServiceImpl
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import javax.servlet.http.HttpServletRequest

@RestController
@RequestMapping("/conversations")
class ConversationController(
val conversationService: ConversationServiceImpl,
val conversationAssembler: ConversationAssembler,
val userRepository: UserRepository
) {

@GetMapping
fun list(request: HttpServletRequest): ResponseEntity<ConversationListVO> {
val user = userRepository.findByUsername(request
.userPrincipal.name) as User
val conversations = conversationService.listUserConversations
(user.id)
return ResponseEntity.ok(conversationAssembler
.toConversationListVO(conversations, user.id))
}


@GetMapping
@RequestMapping("/{conversation_id}")
fun show(@PathVariable(name = "conversation_id") conversationId: Long,
request: HttpServletRequest): ResponseEntity<ConversationVO> {
val user = userRepository.findByUsername(request
.userPrincipal.name) as User
val conversationThread = conversationService.retrieveThread
(conversationId)
return ResponseEntity.ok(conversationAssembler
.toConversationVO(conversationThread, user.id))
}
}

All is looking great! There's only one tiny problem. Remember, a user has an account status and it is possible for the account to be deactivated, right? In such a scenario, we, as API creators, will not want a deactivated user to be able to use our platform. As such, we have to come up with a way to prevent such a user from interacting with our API. There are a number of ways this can be done, but for this example we are going to use an interceptor. An interceptor intercepts an HTTP request and performs one or more operations on it before it continues down the request chain. Similar to assemblers, an interceptor is a component. We will call our interceptor that checks the validity of an account AccountValidityInterceptor. The following is the interceptor class (remember, it belongs in the components package):

package com.example.messenger.api.components

import com.example.messenger.api.exceptions.UserDeactivatedException
import com.example.messenger.api.models.User
import com.example.messenger.api.repositories.UserRepository
import org.springframework.stereotype.Component
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter
import java.security.Principal
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

@Component
class AccountValidityInterceptor(val userRepository: UserRepository) :
HandlerInterceptorAdapter() {

@Throws(UserDeactivatedException::class)
override fun preHandle(request: HttpServletRequest,
response: HttpServletResponse, handler: Any?): Boolean {
val principal: Principal? = request.userPrincipal

if (principal != null) {
val user = userRepository.findByUsername(principal.name)
as User

if (user.accountStatus == "deactivated") {
throw UserDeactivatedException("The account of this user has
been deactivated."
)
}
}
return super.preHandle(request, response, handler)
}
}

The AccountValidityInterceptor class overrides the preHandle() function of its super class. This function will be called to carry out some operations prior to the routing of the request to its necessary controller action. After the creation of an interceptor, the interceptor must be registered with the Spring application. This configuration can be done using a WebMvcConfigurer. Add an AppConfig file to the config package in the project. Input the following code within the file:

package com.example.messenger.api.config

import com.example.messenger.api.components.AccountValidityInterceptor
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration
class AppConfig : WebMvcConfigurer {

@Autowired
lateinit var accountValidityInterceptor: AccountValidityInterceptor

override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(accountValidityInterceptor)
super.addInterceptors(registry)
}
}

AppConfig is a subclass of WebMvcConfigurer and overrides the addInterceptor(InterceptorRegistry) function in its superclass. accountValidityInterceptor is added to the interceptor registry with registry.addInterceptor().

We are now done with all the code required to provide web resources to the messenger Android application. We must now deploy this code to a remote server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.206.225