Groovy API
A lot of functionality and site customization can be done through Groovy scripts in Crafter, no need to code in Java! By using Groovy scripts, you can create RESTful services, MVC controllers, code that runs before a page or component is rendered, servlet filters and scheduled jobs. Crafter also provides a bunch of useful global variables that can be used in all the different types of scripts available:
Name |
Description |
Type |
---|---|---|
siteItemService
|
Allows access to the site content.
|
|
urlTransformationService
|
Service for transforming URLs, like
transforming the content URL of a
page to the web or render URL.
|
|
searchService
|
Service that can be used to execute
search queries against
Crafter Search.
|
|
elasticsearch
|
Service that can be used to execute
search queries against
Elasticsearch.
|
|
applicationContext
|
Provides access to the Crafter
Engine’s Spring beans and site beans
defined in
config/spring/application-context.xml
|
|
globalProperties
|
Provides access to global
configuration properties defined in
server-config.properties.
|
|
navBreadcrumbBuilder
|
Helper class that returns the list of
path components in an URL, to create
navigation breadcrumbs.
|
|
navTreeBuilder
|
Helper class that creates navigation
trees to facilitate rendering
|
|
tenantsResolver
|
Can be used to retrieve the
Profile tenants associated to the
current site.
|
|
profileService
|
Provides access to the Crafter
Profile API for profiles.
|
|
tenantService
|
Provides access to the Crafter
Profile API for tenants.
|
|
authenticationService
|
Provides access to the Crafter
Profile API for authentication.
|
|
authenticationManager
|
Manages Crafter Security Provider
based authentications.
|
|
textEncryptor
|
Utility class for encrypting/
decrypting text with AES.
|
|
modePreview
|
Flag that indicates that Engine is
being executed in preview mode
(also the value of the
crafter.engine.preview property) |
Boolean
|
crafterEnv
|
Indicates the value of the
crafter.engine.environment property
|
String
|
logger
|
The GroovyUtils SLF4J logger
|
|
siteConfig
|
The current site Configuration,
loaded from /config/site.xml.
|
|
siteContext
|
The current SiteContext
|
There are also several other variables available to scripts that are executed during the scope of a request (REST scripts, controller scripts, page/component scripts and filter scripts):
Name |
Description |
API |
---|---|---|
request |
The current request
|
|
response |
The current response
|
|
params |
The parameter values for the current
request
|
|
headers |
The header values for the current
request
|
|
cookies |
The cookie values for the current
request
|
|
session |
The current session, if it has been
created
|
|
locale |
The current locale for the current
user
|
|
authToken |
The current authentication (if the
user has logged in), created by
Spring Security
|
The following variables are provided for backward compatibility when using Crafter Profile, should be replaced
with authToken
if possible:
Name |
Description |
API |
---|---|---|
authentication |
The current authentication (if the
user has logged in), created by the
Crafter Security Provider
|
|
profile |
The current profile (if the user
has logged in), created by the
Crafter Security Provider
|
Note
The variables
profile
andauthentication
listed above will be null in most cases and should not be used anymore
The following variables are restricted by default, to use them see Access to Services
Name |
Description |
API |
---|---|---|
application |
The servlet context
|
All scripts are executed in a sandbox to prevent insecure code from running, to change the configuration see Script Sandbox Configuration
To create unit tests for your groovy code, see Unit Testing CrafterCMS Groovy Code
Create a Script in Studio
Todo
Write how to create a script in Studio
Types of Scripts
There are different types of scripts you can create, depending on the subfolder under Scripts where they’re placed. The following are the ones currently supported:
REST Scripts
REST scripts function just like RESTful services. They just need to return the object to serialize back to the caller. REST scripts must be placed in any folder under Scripts > rest.
A REST script URL has the following format: it starts with /api/1/services, then contains all the folders that are part of the hierarchy for the particular script, and ends with the script name, the HTTP method and the .groovy extension. So, a script file at Scripts > rest > myfolder > myscript.get.groovy will respond to GET method calls at http://mysite/api/1/services/myfolder/myscript.json.
The following is a very simple sample script that returns a date attribute saved in the session. If no attribute is set yet, the current date is set as the attribute. Assume that the REST script exists under Scripts > rest > session_date.get.groovy
1import java.util.Date
2
3if (!session) {
4 session = request.getSession(true)
5}
6
7def date = session.getAttribute("date")
8if (!date) {
9 date = new Date()
10
11 session.setAttribute("date", date)
12}
13
14return ["date": date]
Controller Scripts
Controller scripts are very similar to REST scripts. They have the same variables available, but instead of returning an object, they return a string with the view to render. Most of the time, this is just the template path, like in the following snippet:
return "/templates/web/registration.ftl"
Controller scripts basically work like a controller in the MVC pattern. They should be put under Scripts > controllers, and similarly to REST scripts, the URL is made up of the directory hierarchy, the script name and the HTTP method. For example, a script at Scripts > controllers > myfolder > mycontroller.get.groovy will respond to GET calls at http://mysite/myfolder/mycontroller.
The following is a very simple example script that will do the sum of 2 parameters, put the result in the templateModel
and return
the path of the FTL template that will render the result:
templateModel.result = Integer.parseInt(params.num1) + Integer.parseInt(params.num2)
return "/templates/web/sum.ftl"
One very common controller script is the sitemap.groovy. A sitemap is used by search engines to have a better idea of how to “crawl” a website. A sitemap is an XML with references to most of the site’s pages, and basically looks like this:
1<?xml version="1.0" encoding="UTF-8"?>
2<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3 <url>
4 <loc>http://www.domain.com /</loc>
5 <lastmod>2008-01-01</lastmod>
6 <changefreq>weekly</changefreq>
7 <priority>0.8</priority>
8 </url>
9 <url>
10 <loc>http://www.domain.com/catalog?item=vacation_hawaii</loc>
11 <changefreq>weekly</changefreq>
12 </url>
13 <url>
14 <loc>http://www.domain.com/catalog?item=vacation_new_zealand</loc>
15 <lastmod>2008-12-23</lastmod>
16 <changefreq>weekly</changefreq>
17 </url>
18 <url>
19 <loc>http://www.domain.com/catalog?item=vacation_newfoundland</loc>
20 <lastmod>2008-12-23T18:00:15+00:00</lastmod>
21 <priority>0.3</priority>
22 </url>
23 <url>
24 <loc>http://www.domain.com/catalog?item=vacation_usa</loc>
25 <lastmod>2008-11-23</lastmod>
26 </url>
27</urlset>
Search engines look for the sitemap just after the domain, so a sitemap URL would look like www.domain.com/sitemap. The sitemap controller then must be placed in Scripts > controllers > sitemap.groovy. The code would be similar to this:
1import groovy.xml.MarkupBuilder
2import groovy.xml.MarkupBuilderHelper
3
4def sitemap = []
5def excludeContentTypes = ['/component/level-descriptor']
6
7parseSiteItem = { siteItem ->
8 if (siteItem.isFolder()) {
9 def children = siteItem.childItems;
10 children.each { child ->
11 parseSiteItem(child);
12 }
13 } else {
14 def contentType = siteItem.queryValue('content-type')
15 if (!excludeContentTypes.contains(contentType)) {
16 def storeUrl = siteItem.getStoreUrl();
17 def location = urlTransformationService.transform('storeUrlToFullRenderUrl', storeUrl);
18 sitemap.add(location);
19 }
20 }
21}
22
23def siteTree = siteItemService.getSiteTree("/site/website", -1)
24if (siteTree) {
25 def items = siteTree.childItems;
26 items.each { siteItem ->
27 parseSiteItem(siteItem);
28 }
29}
30
31response.setContentType("application/xml;charset=UTF-8")
32
33def writer = response.getWriter()
34def xml = new MarkupBuilder(writer)
35def xmlHelper = new MarkupBuilderHelper(xml)
36
37xmlHelper.xmlDeclaration(version:"1.0", encoding:"UTF-8")
38
39xml.urlset(xmlns:"http://www.sitemaps.org/schemas/sitemap/0.9") {
40 sitemap.each { location ->
41 url {
42 loc(location)
43 changefreq("daily")
44 }
45 }
46}
47
48response.flushBuffer()
49
50return null
Page and Component Scripts
Crafter page and components can have their own controller scripts too, that are executed before the page or component is rendered, and that can contribute to the model of the template. These scripts, besides the common variables, have the following model related variables:
Model Related Variable
|
Description
|
---|---|
contentModel |
The XML descriptor content
It is an instance of the SiteItem class
|
templateModel |
The actual map model of the template
It is an instance of the AllHttpScopesAndAppContextHashModel class
|
As mentioned in the table above, the templateModel
is the actual map model of the template, and any variable
put in it will be accessible directly in the template, eg. if the script has the line templateModel.var = 5
,
then in the template the var’s value can be printed with ${var}
.
The scripts don’t have to return any result, just populate the templateModel
.
There are 2 ways in which you can “bind” a script to a page or component:
Put the script under Scripts > pages or Scripts > components, and name it after the page or component content type.
When creating the content type for the page or component, add an Item Selector with the variable name
scripts
. Later when creating a page or component of that type, you can select multiple scripts that will be associated to the page or component.
The following is an example of a component script. The component content type is /component/upcoming-events
. We can then place the
script in Scripts > components > upcoming-events.groovy so that it is executed for all components of that type.
1 import org.craftercms.engine.service.context.SiteContext
2
3 import utils.DateUtils
4
5 def now = DateUtils.formatDateAsIso(new Date())
6 def queryStr = "crafterSite:\"${siteContext.siteName}\" AND content-type:\"/component/event\" AND disabled:\"false\" AND date_dt:[${now} TO *]"
7 def start = 0
8 def rows = 1000
9 def sort = "date_dt asc"
10 def query = searchService.createQuery()
11
12 query.setQuery(queryStr)
13 query.setStart(start)
14 query.setRows(rows)
15 query.addParam("sort", sort)
16 query.addParam("fl", "localId")
17
18 def events = []
19 def searchResults = searchService.search(query)
20 if (searchResults.response) {
21 searchResults.response.documents.each {
22 def event = [:]
23 def item = siteItemService.getSiteItem(it.localId)
24
25 event.image = item.image.text
26 event.title = item.title_s.text
27 event.date = DateUtils.parseModelValue(item.date_dt.text)
28 event.summary = item.summary_html.text
29
30 events.add(event)
31 }
32 }
33
34 templateModel.events = events
In the above example, you will see that we’re importing a utils.DateUtils
class. This class is not part of CrafterCMS, but instead it is a site specific class written in Groovy.
The class is/needs to be located at the following path scripts > classes > utils
and is placed in a file called DateUtils.groovy
, like below:
1 package utils
2
3 import java.text.SimpleDateFormat
4
5 class DateUtils {
6
7 static def parseModelValue(value){
8 def dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss")
9 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
10 return dateFormat.parse(value)
11 }
12
13 static def formatDateAsIso(date) {
14 def dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
15 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
16 return dateFormat.format(date)
17 }
18
19 }
Filter Scripts
Crafter Engine can handle special Groovy filters that work basically in the same way as Servlet filters. These are
scripts very similar to page scripts, and have basically the same variables available (except the templateModel
and contentModel
variables), but instead of updating the template model, they call filterChain.doFilter
(request, response)
, just like in Java Servlet filters, to continue with the filter chain. You can even stop the
request filtering and return the response directly, like in this example:
1if (!authentication) {
2 response.sendError(400, "You're not a subscriber")
3} else {
4 filterChain.doFilter(request, response)
5}
All this filter scripts should be put under Scripts > filters, and their mappings should be defined in Config > site.xml. The order in which the mappings appear is the order in which the filters will be applied.
1<filters>
2 <filter>
3 <script>/scripts/filters/testFilter1.groovy</script>
4 <mapping>
5 <include>/**</include>
6 </mapping>
7 </filter>
8 <filter>
9 <script>/scripts/filters/testFilter2.groovy</script>
10 <mapping>
11 <include>/**</include>
12 </mapping>
13 </filter>
14 <filter>
15 <script>/scripts/filters/testFilter3.groovy</script>
16 <mapping>
17 <include>/**</include>
18 <exclude>/static-assets/**</exclude>
19 </mapping>
20 </filter>
21</filters>
The following is an example script that adds a display name attribute to the current profile if the attribute doesn’t exist yet. Assume that the script is placed in Scripts > filters > addDisplayName.groovy.
1if (profile) {
2 def displayName = profile.getAttribute("displayName")
3 if (!displayName) {
4 def id = profile.id.toString()
5 def firstName = profile.getAttribute("firstName")
6 def lastName = profile.getAttribute("lastName")
7 def newAttributes = [:]
8
9 newAttributes["displayName"] = "${firstName} ${lastName}".toString()
10
11 profileService.updateAttributes(id, newAttributes)
12
13 logger.info("Display name added to profile '${id}'")
14 }
15}
16
17filterChain.doFilter(request, response)
To enable this filter, we need to configure its mapping in Config > site.xml. The mapping would look as the following.
1<?xml version="1.0" encoding="UTF-8"?>
2<site>
3 <filters>
4 <filter>
5 <script>/scripts/filters/addDisplayName.groovy</script>
6 <mapping>
7 <include>/**</include>
8 </mapping>
9 </filter>
10 </filters>
11</site>
Scheduled Script Jobs
Scripts can also be scheduled as jobs in Crafter Engine. These scripts only have the common global variables and the logger variable. They don’t need to return any result. Engine allows 3 different ways to configure script jobs:
By placing the scripts under one of the following folders in Scripts > jobs: hourly, daily, weekly and monthly. As the names imply, scripts under these folders will be scheduled to run every hour (hourly), at 12:00 am every day (daily), at 12:00 am every Monday (weekly), or at 12:00 am every first day of the month (monthly).
By adding one or more
<jobFolder>
configuration elements under<jobs>
in Config > site.xml. Under<jobFolder>
you can specify a<path>
and a<cronExpression>
, and every script under that folder will be scheduled using the cron expression.1<jobs> 2 <jobFolder> 3 <path>/scripts/jobs/morejobs</path> 4 <cronExpression>0 0/15 * * * ?</cronExpression> 5 </jobFolder> 6</jobs>
By adding one or more
<job>
configuration elements under<jobs>
in Config > site.xml. With the<path>
and<cronExpression>
elements, you specify the job script path and the cron expression for scheduling.1<jobs> 2 <job> 3 <path>/scripts/jobs/testJob.groovy</path> 4 <cronExpression>0 0/15 * * * ?</cronExpression> 5 </job> 6</jobs>