SlideShare a Scribd company logo
1 of 46
Download to read offline
@jennstrater#Devoxx #restwithgrails3
Creating RESTful Web Services
with Grails 3
Jennifer Strater
Object Partners, Inc.
@jennstrater#Devoxx #restwithgrails3
About Me
• Senior Consultant at Object Partners, Inc.
• Co-Founder of Gr8Ladies
@jennstrater#Devoxx #restwithgrails3
Agenda
• Getting Started
• Resource Annotation
• RESTful Controllers
• Key Features
• Supporting Multiple Media Types
• Custom Response Formats
• Versioning
@jennstrater#Devoxx #restwithgrails3
New in Grails 3
• Based on Spring Boot
• Switched to Gradle for build system
• Major structural changes
• configuration
• tests
• scripts directory
@jennstrater#Devoxx #restwithgrails3
Web API profile
• Introduced in Grails 3.0.5
• Removes Unnecessary Features
• no GSPs
• New Features
• Grails Command line support
• domain classes with resource annotation
• RESTful controllers
• json/gson views* (Grails 3.1+)
@jennstrater#Devoxx #restwithgrails3
Getting Started
• gvm use grails 3.0.9
• grails create-app gr8data --profile=web-api
@jennstrater#Devoxx #restwithgrails3
@jennstrater#Devoxx #restwithgrails3
• grails>run-app
@jennstrater#Devoxx #restwithgrails3
@jennstrater#Devoxx #restwithgrails3
The Task - Gr8Data
• Capture and display gender ratios at companies using Groovy
and related technology around the world
• Currently available at: http://jlstrater.github.io/gr8ladies-d3/
• Data stored in a json file with contributions via github pull
requests
• Demo for this talk: http://github.com/jlstrater/gr8data
@jennstrater#Devoxx #restwithgrails3
Data Model
• Company
• name
• Country
• name
• abbreviation
• continent
• various stats
@jennstrater#Devoxx #restwithgrails3
First Endpoint
grails> create-domain-resource gr8data.Country
| Created grails-app/domain/gr8data/Country.groovy
| Created src/test/groovy/gr8data/CountrySpec.groovy
@jennstrater#Devoxx #restwithgrails3
package gr8data



import grails.rest.*



@Resource(readOnly = false, formats = ["json", "xml"])

class Country {



}
Country.groovy
@jennstrater#Devoxx #restwithgrails3
package gr8data



import grails.rest.*



@Resource(+ uri="/countries", readOnly = false, formats = ["json", "xml"])

class Country {

+ String name

+ String abbreviation

+ String continent

}
@jennstrater#Devoxx #restwithgrails3
GET
• curl http://localhost:8080/countries
• []
@jennstrater#Devoxx #restwithgrails3
POST (with errors)
curl -H "Content-Type: application/json" -X POST 
—data "{"name": "Belgium"}" http://localhost:8080/countries
Status: 422 Unprocessable Entity
@jennstrater#Devoxx #restwithgrails3
POST (successful)
curl
-H "Content-Type: application/json"
-X POST
-d "{"name": "Belgium", "abbreviation": "BE", "continent":
"Europe"}" http://localhost:8080/countries
Status: 201 Created
@jennstrater#Devoxx #restwithgrails3
PUT
curl
-H "Content-Type: application/json"
-X PUT
-d "{"name": "United States of America", "abbreviation": "USA",
"continent": "North America"}"
http://localhost:8080/countries
@jennstrater#Devoxx #restwithgrails3
PUT
curl
-H "Content-Type: application/json"
-X PUT
-d "{"name": "United States of America", "abbreviation": "USA",
"continent": "North America"}"
http://localhost:8080/countries/2
status: 200 OK
@jennstrater#Devoxx #restwithgrails3
PATCH
curl
-H "Content-Type: application/json"
-X PATCH
-d "{"abbreviation": "US"}"
http://localhost:8080/countries/2
status: 200 OK
@jennstrater#Devoxx #restwithgrails3
GET
curl http://localhost:8080/countries
@jennstrater#Devoxx #restwithgrails3
Resource Annotation Recap
• Automatic Mappings
• No controllers created
• URLMappings.groovy not updated
• HTTP Status Codes
• HTTP Methods
@jennstrater#Devoxx #restwithgrails3
RESTful Controllers
New Domain Class
grails> create-domain-class gr8data.Company
| Created grails-app/domain/gr8data/Company.groovy
| Created src/test/groovy/gr8data/CompanySpec.groovy
@jennstrater#Devoxx #restwithgrails3
package gr8data



class Company {



static constraints = {

}

}

RESTful Controllers
@jennstrater#Devoxx #restwithgrails3
package gr8data



class Company {
+ String name
+ Country country



static constraints = {

}

}

RESTful Controllers
@jennstrater#Devoxx #restwithgrails3
RESTful Controllers
grails> create-restful-controller gr8data.Company
| Created grails-app/controllers/gr8data/CompanyController.groovy
*REMEMBER* to update UrlMappings.groovy
+ "/companies"(controller: "company", action: "index", method: "GET")

+ "/companies"(controller: "company", action: "save", method: "POST")

+ "/companies/$id"(controller: "company", action: "update", method: "PUT")

+ "/companies/$id"(controller: "company", action: "patch", method: "PATCH")

+ "/companies/$id"(controller: "company", action: "delete", method: "DELETE")
…
@jennstrater#Devoxx #restwithgrails3
package gr8data



import grails.rest.*

import grails.converters.*



class CompanyController extends RestfulController {

static responseFormats = ["json", "xml"]

CompanyController() {

super(Company)

}

}

@jennstrater#Devoxx #restwithgrails3
URI Customization
• Resource Annotation
• parameters
• i.e. @Resouce(uri="/countries", readOnly=true)
• UrlMappings.groovy
• resources
• uri
• methods
• response formats
• etc
@jennstrater#Devoxx #restwithgrails3
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
Default Mapping
@jennstrater#Devoxx #restwithgrails3
URL Mappings Report
grails>url-mappings-report
@jennstrater#Devoxx #restwithgrails3
HTTP Methods
• via mappings
• "/custom"(controller="custom", action=“index”, method=“GET")
• via controllers
• static allowedMethods = [save: "POST", update: "PUT"]
@jennstrater#Devoxx #restwithgrails3
Default Media Types
• xml if not specified
• on mappings and controllers
• formats = ["json","xml"]
• extension
• /countries.xml
• /countries.json
• Requesting a format not included returns 406 Not Acceptable
@jennstrater#Devoxx #restwithgrails3
Custom Media Types
• Add to Mime Types
@jennstrater#Devoxx #restwithgrails3
Custom Response Formats
• Marshallers/Renderers
• registration
• bootstrap.groovy
• beans
• JSON views*
@jennstrater#Devoxx #restwithgrails3
import grails.converters.JSON
class Bootstrap {
def init = { servletContext ->
JSON.registerObjectMarshaller(Country) {
return [
id: it.id,
name: it.name,
abbrev: it.abbreviation,
continent: it.continent
]
}
}
}
grails-app/init/Bootstrap.groovy
@jennstrater#Devoxx #restwithgrails3
import grails.converters.JSON
import gr8data.CompanyJsonRenderer
beans = {
companyJsonRenderer CompanyJsonRenderer
}
conf/spring/resources.groovy
@jennstrater#Devoxx #restwithgrails3
class CompanyJsonRenderer extends AbstractRenderer<Company> {

CompanyJsonRenderer() {

super(Company, [MimeType.JSON, MimeType.TEXT_JSON] as MimeType[])

}



void render(Company company, RenderContext context) {

context.contentType = MimeType.JSON.name



JsonBuilder builder = new JsonBuilder(id: company.id, name: company.name,
country: company.country.name)

builder.writeTo(context.writer)

}

}
Json Renderer
@jennstrater#Devoxx #restwithgrails3
class CompanyCollectionJsonRenderer implements ContainerRenderer<List, Company> {

Class<List> getTargetType() { List }



Class<Company> getComponentType() { Company }



MimeType[] getMimeTypes() { [MimeType.JSON] as MimeType[] }



void render(List companies, RenderContext context) {

…
}

}
Json Collection Renderer
@jennstrater#Devoxx #restwithgrails3
JSON view*
• JSON views replace GSPs in web-api profile
• Convention Based
• index.gsp -> index.gson
*Grails 3.1+
@jennstrater#Devoxx #restwithgrails3
json {
message "Not Found"
error 404
}
JSON views*
@jennstrater#Devoxx #restwithgrails3
Versioning
• URI
• http://api.example.com/v1/resource/{id}
• http://example.com/api/v1/resource/{id}
• Custom Header
•
• Content Type
@jennstrater#Devoxx #restwithgrails3
Versioning in Grails
• URI
• "/hello/v1"(controller="hello", namespace: "v1")
• "/hello/v2"(controller="hello", namespace: "v2")
• Accept Header
• "/hello"(version:"1.0", controller="hello", namespace: "v1")
• "/hello"(version:"2.0",controller="hello", namespace: "v2")
@jennstrater#Devoxx #restwithgrails3
Version By Namespace
package gr8data.v1
class HelloController {
static namespace = "v1"
def index() {
render "Hello, World (v1)"
}
}
package gr8data.v2
class HelloController {
static namespace = "v2"
def index() {
render "Hello, World (v2)"
}
}
@jennstrater#Devoxx #restwithgrails3
Security
• Spring Security Rest Plugin by Álvaro Sánchez Mariscal
• Token based authentication
• Greach 2014Video
• Grails 2 only
• Spring Security Plugin for Grails 3
• OAuth for Spring Boot
• Screencast by Bobby Warner of Agile Orbit
@jennstrater#Devoxx #restwithgrails3
Conclusion
• Grails web-api profile streamlines API creation.
• Grails defaults support HTTP status codes, HTTP methods,
custom media types, custom response formats, and versioning
fairly easily.
• More exciting changes coming to the web-api profile with
Grails 3.1!
@jennstrater#Devoxx #restwithgrails3
To Learn More
• Restful Web Services in Grails 3 at Gr8Conf US
• YouTube / Slides
• Restful Grails 3 at SpringOne2GX by Jeff Brown
• YouTube / Slides
• Grails 3.X Update at SpringOne2GX by Graeme Rocher
• YouTube / Slides
• Grails Documentation
• REST

More Related Content

What's hot

Using React with Grails 3
Using React with Grails 3Using React with Grails 3
Using React with Grails 3
Zachary Klein
 

What's hot (20)

Desarrollo de aplicaciones con Grails 3, Angular JS y Spring Security
Desarrollo de aplicaciones con Grails 3, Angular JS y Spring SecurityDesarrollo de aplicaciones con Grails 3, Angular JS y Spring Security
Desarrollo de aplicaciones con Grails 3, Angular JS y Spring Security
 
Efficient HTTP applications on the JVM with Ratpack - Voxxed Days Berlin 2016
Efficient HTTP applications on the JVM with Ratpack - Voxxed Days Berlin 2016Efficient HTTP applications on the JVM with Ratpack - Voxxed Days Berlin 2016
Efficient HTTP applications on the JVM with Ratpack - Voxxed Days Berlin 2016
 
Introduction To Grails
Introduction To GrailsIntroduction To Grails
Introduction To Grails
 
Capybara + RSpec - ruby dsl-based web ui qa automation
Capybara + RSpec - ruby dsl-based web ui qa automationCapybara + RSpec - ruby dsl-based web ui qa automation
Capybara + RSpec - ruby dsl-based web ui qa automation
 
Indexing and searching NuGet.org with Azure Functions and Search - .NET fwday...
Indexing and searching NuGet.org with Azure Functions and Search - .NET fwday...Indexing and searching NuGet.org with Azure Functions and Search - .NET fwday...
Indexing and searching NuGet.org with Azure Functions and Search - .NET fwday...
 
Maarten Balliauw "Indexing and searching NuGet.org with Azure Functions and S...
Maarten Balliauw "Indexing and searching NuGet.org with Azure Functions and S...Maarten Balliauw "Indexing and searching NuGet.org with Azure Functions and S...
Maarten Balliauw "Indexing and searching NuGet.org with Azure Functions and S...
 
Using React with Grails 3
Using React with Grails 3Using React with Grails 3
Using React with Grails 3
 
Capybara
CapybaraCapybara
Capybara
 
Introduction to Grails
Introduction to GrailsIntroduction to Grails
Introduction to Grails
 
Capybara with Rspec
Capybara with RspecCapybara with Rspec
Capybara with Rspec
 
深入淺出RoR
深入淺出RoR深入淺出RoR
深入淺出RoR
 
Getting Started With Angular
Getting Started With AngularGetting Started With Angular
Getting Started With Angular
 
API Days Australia - Automatic Testing of (RESTful) API Documentation
API Days Australia  - Automatic Testing of (RESTful) API DocumentationAPI Days Australia  - Automatic Testing of (RESTful) API Documentation
API Days Australia - Automatic Testing of (RESTful) API Documentation
 
Git journey from mars to neon EclipseCon North America - 2016-03-08
Git journey from mars to neon   EclipseCon North America - 2016-03-08Git journey from mars to neon   EclipseCon North America - 2016-03-08
Git journey from mars to neon EclipseCon North America - 2016-03-08
 
SpringMVC
SpringMVCSpringMVC
SpringMVC
 
Automated Duplicate Content Consolidation with Google Cloud Functions
Automated Duplicate Content Consolidation with Google Cloud FunctionsAutomated Duplicate Content Consolidation with Google Cloud Functions
Automated Duplicate Content Consolidation with Google Cloud Functions
 
Alfresco Development Framework Basic
Alfresco Development Framework BasicAlfresco Development Framework Basic
Alfresco Development Framework Basic
 
REST Easy with AngularJS - ng-grid CRUD EXAMPLE
REST Easy with AngularJS - ng-grid CRUD EXAMPLEREST Easy with AngularJS - ng-grid CRUD EXAMPLE
REST Easy with AngularJS - ng-grid CRUD EXAMPLE
 
Git in a nutshell
Git in a nutshellGit in a nutshell
Git in a nutshell
 
Redux Universal
Redux UniversalRedux Universal
Redux Universal
 

Viewers also liked

Grails Monolith to Microservice to FaaS
Grails Monolith to Microservice to FaaSGrails Monolith to Microservice to FaaS
Grails Monolith to Microservice to FaaS
Mike Wyszinski
 

Viewers also liked (6)

Microservices: Uma visão Pragmática e Prática - Parte 2
Microservices: Uma visão Pragmática e Prática - Parte 2Microservices: Uma visão Pragmática e Prática - Parte 2
Microservices: Uma visão Pragmática e Prática - Parte 2
 
Grails gotchas and best practices
Grails gotchas and best practicesGrails gotchas and best practices
Grails gotchas and best practices
 
Building a scalable API with Grails
Building a scalable API with GrailsBuilding a scalable API with Grails
Building a scalable API with Grails
 
Grails Monolith to Microservice to FaaS
Grails Monolith to Microservice to FaaSGrails Monolith to Microservice to FaaS
Grails Monolith to Microservice to FaaS
 
Spring IO '15 - Developing microservices, Spring Boot or Grails?
Spring IO '15 - Developing microservices, Spring Boot or Grails?Spring IO '15 - Developing microservices, Spring Boot or Grails?
Spring IO '15 - Developing microservices, Spring Boot or Grails?
 
REST - padrões e melhores práticas
REST - padrões e melhores práticasREST - padrões e melhores práticas
REST - padrões e melhores práticas
 

Similar to Rest with grails 3

Dev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDBDev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDB
MongoDB
 
Ajax Performance Tuning and Best Practices
Ajax Performance Tuning and Best PracticesAjax Performance Tuning and Best Practices
Ajax Performance Tuning and Best Practices
Doris Chen
 
JAVA EE DEVELOPMENT (JSP and Servlets)
JAVA EE DEVELOPMENT (JSP and Servlets)JAVA EE DEVELOPMENT (JSP and Servlets)
JAVA EE DEVELOPMENT (JSP and Servlets)
Talha Ocakçı
 

Similar to Rest with grails 3 (20)

Boost Development With Java EE7 On EAP7 (Demitris Andreadis)
Boost Development With Java EE7 On EAP7 (Demitris Andreadis)Boost Development With Java EE7 On EAP7 (Demitris Andreadis)
Boost Development With Java EE7 On EAP7 (Demitris Andreadis)
 
Full Stack Toronto - the 3R Stack
Full Stack Toronto - the 3R StackFull Stack Toronto - the 3R Stack
Full Stack Toronto - the 3R Stack
 
Lean React - Patterns for High Performance [ploneconf2017]
Lean React - Patterns for High Performance [ploneconf2017]Lean React - Patterns for High Performance [ploneconf2017]
Lean React - Patterns for High Performance [ploneconf2017]
 
IBM Connect 2016 - Break out of the Box
IBM Connect 2016 - Break out of the BoxIBM Connect 2016 - Break out of the Box
IBM Connect 2016 - Break out of the Box
 
GWT Enterprise Edition
GWT Enterprise EditionGWT Enterprise Edition
GWT Enterprise Edition
 
Server side rendering with React and Symfony
Server side rendering with React and SymfonyServer side rendering with React and Symfony
Server side rendering with React and Symfony
 
SenchaCon 2016: Ext JS + React: A Match Made in UX Heaven - Mark Brocato
SenchaCon 2016: Ext JS + React: A Match Made in UX Heaven - Mark BrocatoSenchaCon 2016: Ext JS + React: A Match Made in UX Heaven - Mark Brocato
SenchaCon 2016: Ext JS + React: A Match Made in UX Heaven - Mark Brocato
 
Annotation processing and code gen
Annotation processing and code genAnnotation processing and code gen
Annotation processing and code gen
 
Advanced #2 networking
Advanced #2   networkingAdvanced #2   networking
Advanced #2 networking
 
The Big Picture and How to Get Started
The Big Picture and How to Get StartedThe Big Picture and How to Get Started
The Big Picture and How to Get Started
 
Apache Calcite Tutorial - BOSS 21
Apache Calcite Tutorial - BOSS 21Apache Calcite Tutorial - BOSS 21
Apache Calcite Tutorial - BOSS 21
 
React js
React jsReact js
React js
 
Dev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDBDev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDB
 
Break out of The Box - Part 2
Break out of The Box - Part 2Break out of The Box - Part 2
Break out of The Box - Part 2
 
Angular js
Angular jsAngular js
Angular js
 
ADO.NET Entity Framework by Jose A. Blakeley and Michael Pizzo
ADO.NET Entity Framework by Jose A. Blakeley and Michael PizzoADO.NET Entity Framework by Jose A. Blakeley and Michael Pizzo
ADO.NET Entity Framework by Jose A. Blakeley and Michael Pizzo
 
Ajax Performance Tuning and Best Practices
Ajax Performance Tuning and Best PracticesAjax Performance Tuning and Best Practices
Ajax Performance Tuning and Best Practices
 
Android and REST
Android and RESTAndroid and REST
Android and REST
 
JAVA EE DEVELOPMENT (JSP and Servlets)
JAVA EE DEVELOPMENT (JSP and Servlets)JAVA EE DEVELOPMENT (JSP and Servlets)
JAVA EE DEVELOPMENT (JSP and Servlets)
 
Implementation of GUI Framework part3
Implementation of GUI Framework part3Implementation of GUI Framework part3
Implementation of GUI Framework part3
 

Recently uploaded

Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 

Recently uploaded (20)

What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 

Rest with grails 3