Wiki source code of Scripting

Last modified by Simon Urli on 2023/10/10

Hide last authors
OCTAGRAM 24.1 1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
Caleb James DeLisle 23.1 4
Vincent Massol 66.1 5 Scripting allows you to create basic to complex web applications at the XWiki page (or view) layer without the need for compiling code or deploying software components. In other words, you can use scripting syntax in addition to wiki markup inside the content of an XWiki page (e.g. see the [[script syntax for the XWiki 2.1 syntax>>Documentation.UserGuide.Features.XWikiSyntax.WebHome?syntax=2.1&section=Macros]]).
Vincent Massol 1.1 6
Vincent Massol 49.2 7 XWiki integrates [[jsr-223>>http://jcp.org/en/jsr/detail?id=223]] scripting. You can script using several available languages by using the generic [[Script Macro>>extensions:Extension.Script Macro]] or one of following specific macros:
OCTAGRAM 24.1 8
Vincent Massol 50.4 9 * [[Velocity Macro>>extensions:Extension.Velocity Macro]] (installed by default in XWiki)
10 * [[Groovy Macro>>extensions:Extension.Groovy Macro]] (installed by default in XWiki)
11 * [[Python Macro>>extensions:Extension.Python Macro]] (installed by default in XWiki)
12 * [[Ruby Macro>>extensions:Extension.Ruby Macro]] (not installed by default in XWiki)
13 * [[PHP Macro>>extensions:Extension.PHP Macro]] (not installed by default in XWiki)
Vincent Massol 20.4 14
Vincent Massol 49.2 15 = Permissions =
16
Mohammad Humayun Khan 66.2 17 Starting with XWiki 7.2M1, a user needs to have the [[Script Permission in order to be able to write Script>>extensions:Extension.Script Macro#HRights]].
Vincent Massol 49.2 18
19 In addition, all scripting languages other than Velocity also require Programming Rights (see below for more details).
20
Vincent Massol 38.1 21 = Choosing a Scripting language =
22
Sergiu Dumitriu 39.1 23 Since XWiki supports several scripting languages you might be wondering which one to use. Most of the code written by XWiki developers is in Velocity, with a few more complex extensions written in Groovy; these two are thoroughly tried and tested, so they are both a safe bet. The other languages //should// work just as well, but there are less developers that could help answering any questions.
Vincent Massol 38.1 24
Sergiu Dumitriu 39.1 25 == Velocity ==
26
Vincent Massol 38.1 27 The first thing to know is that Velocity is different from the other scripting languages on 2 aspects:
Manuel Smeria 45.4 28
Sergiu Dumitriu 39.1 29 * It's a templating language rather than a pure scripting language, which means that its content is actually wiki markup interspersed with Velocity directives, whereas pure scripting languages are written in that language and they need to explicitly output wiki markup. For example:(((
Vincent Massol 38.1 30 Velocity:
31
32 {{code}}
33 {{velocity}}
34 Your username is $xcontext.getUser(), welcome to the site.
35 {{/velocity}}
36 {{/code}}
37
38 Groovy:
39
40 {{code}}
41 {{groovy}}
42 println("Your username is " + xcontext.getUser() + " welcome to the site.");
43 {{/groovy}}
44 {{/code}}
Vincent Massol 50.4 45 )))
Simon Urli 67.4 46 * It doesn't require special permissions (other than Script Permission starting with XWiki 7.2M1) since it runs in a Sandbox, with access to only a few safe objects, and each API call will check the rights configured in the wiki, forbidding access to resources or actions that the current user shouldn't be allowed to retrieve/perform. Other scripting language require the user that wrote the script to have Programming Rights to execute them, but except this initial precondition, access is granted to all the resources on the server. Note that starting with XWiki 4.1 we've introduced a [[Sandbox for Groovy>>xwiki:Documentation.AdminGuide.Configuration#HSecuringGroovyScripts]] too, but it's still in an early stage and is currently very restrictive.
Vincent Massol 38.1 47
Sergiu Dumitriu 40.1 48 Being a templating engine, Velocity doesn't offer many means of structuring code. In fact, there's only one useful directive in this regard, ###macro##. However, because it is a templating engine, its syntax is much simpler and easier to understand by non-developers, which means that it's accessible to a wider range of users, without a serious background in programming.
Sergiu Dumitriu 39.1 49
Vincent Massol 49.2 50 Without Programming Rights, it's impossible to instantiate new objects, except literals and those safely offered by the XWiki APIs. Nevertheless, the XWiki API is powerful enough to allow a wide range of applications to be safely developed, if "the XWiki way" is properly followed.
Sergiu Dumitriu 40.1 51
Sergiu Dumitriu 39.2 52 Velocity is also available in some other parts of XWiki: it is the language in which all the templates that generate the HTML UI of XWiki are written, it can be optionally activated in skin extensions, and it is executed when sending CSS and JavaScript skin resources from the filesystem.
Sergiu Dumitriu 39.1 53
54 In conclusion, **Velocity is suited for projects with small to medium complexity, and which don't require access to other resources except the provided XWiki API and registered script services. It allows very quick and easy development, offers good security and decent performance, and can easily be packaged and distributed as a XAR.**
55
56 == Groovy ==
57
58 Groovy is a full-fledged scripting language, which supports almost the entire Java syntax, and provides its own syntax delicacies and custom APIs that enhance the Java language even further. While it is recommended that complex code be written in Java as components accessible via script services, Groovy has the advantage that it is written live in the wiki, without requiring compilation, deployment and server restarts, thus enabling faster development.
59
Sergiu Dumitriu 39.3 60 The XWiki API is available in the context when executing Groovy scripts, but unlike in Velocity, the code isn't limited to this API, and any other classes or objects can be accessed freely. New classes can be defined in Groovy, compatible with Java classes, and this allows more structured code to be written, unlike in Velocity. A particular case of classes is new component roles and component implementations, which allows, for example, new script services or new event listeners to be defined in the wiki. It is possible to load attached jar files into the classpath of an executing script, which means that a wiki document can contain a complex program AND its required libraries not already provided by the platform.
Sergiu Dumitriu 39.1 61
62 Other than being available as a scripting language for writing custom code, it is also the language in which scheduler tasks are written.
63
64 In conclusion, **Groovy is suited for complex projects or for custom wiki enhancement through new components, when speedy live development is required. Being written in wiki documents, it can also be easily packaged and distributed as a XAR.**
65
Vincent Massol 38.1 66 After taking into account these considerations and if requiring Programming Rights isn't an issue for you, you should pick the script language that you're most familiar with!
67
OCTAGRAM 24.1 68 = XWiki Scripting API =
Caleb James DeLisle 20.1 69
Vincent Massol 59.1 70 The API is documented in Javadoc format and can be accessed here: [[XWiki API Javadoc>>Documentation.DevGuide.API.WebHome]]. If you are not familiar with Java or object oriented programming, you will probably be confused by the API documentation. It is not within the scope of our documentation to teach you all the details about Java, or object oriented programming. You can find all of that information already online. You can also explore the page code found throughout the [[Extensions wiki>>extensions:Main.WebHome]] area to see how others have figured out how to achieve a variety of results.
Caleb James DeLisle 20.1 71
Vincent Massol 55.1 72 We're also making available an [[API Guide>>Documentation.DevGuide.Scripting.APIGuide.WebHome]] with examples about using the XWiki API.
Vincent Massol 48.1 73
Vincent Massol 30.3 74 == [[Bindings>>extensions:Extension.Script Macro#HBindings]] ==
Caleb James DeLisle 20.1 75
Vincent Massol 49.2 76 The main objects available to you in scripting languages are:
OCTAGRAM 24.1 77
Thomas Mortagne 46.1 78 * The current Document: **##doc##**
79 * The Context of the request: **##xcontext##**
80 * The Request object: **##request##**
81 * The Response object: **##response##**
82 * The XWiki object: **##xwiki##**
83 * The XWiki utils: **##util##** (this is deprecated)
Vincent Massol 41.1 84 * Various [[Script Services>>extensions:Extension.Script Module]]: **##services##**
Caleb James DeLisle 20.1 85
Simon Urli 67.4 86 See [[Scripting Reference Documentation>>xwiki:ScriptingDocumentation.WebHome]] for a complete list.
Thomas Mortagne 46.1 87
Vincent Massol 30.3 88 == [[XWiki Component>>extensions:Extension.Component Module]] Access ==
Caleb James DeLisle 20.1 89
Vincent Massol 52.1 90 Since XWiki 4.1M2+ there's a Script Service to access the Component Manager (see also: [[Accessing components from Groovy>>Documentation.DevGuide.Tutorials.WritingComponents.WebHome#HFromwikipages]]).
Caleb James DeLisle 30.1 91
Vincent Massol 41.1 92 For example using Groovy you'd write:
93
94 {{code language="java"}}
95 {{groovy}}
96 def greeter = services.component.getInstance(org.xwiki.component.HelloWorld.class)
97 println greeter.sayHello()
98 {{/groovy}}
99 {{/code}}
100
101 You can also get the ComponentManager with:
102
103 {{code language="java"}}
104 {{groovy}}
105 def cm = services.component.componentManager
106 {{/groovy}}
107 {{/code}}
108
Eduard Moraru 35.4 109 {{info}}
Vincent Massol 41.1 110 With versions of XWiki older than 4.1M2 you'd use (in Groovy):
Vincent Massol 34.1 111
coffeemug13 31.1 112 {{code language="java"}}
113 {{groovy}}
Vincent Massol 41.1 114 def greeter = com.xpn.xwiki.web.Utils.getComponent(org.xwiki.component.HelloWorld.class)
115 println greeter.sayHello()
coffeemug13 31.1 116 {{/groovy}}
117 {{/code}}
Vincent Massol 41.1 118 {{/info}}
Caleb James DeLisle 20.1 119
Vincent Massol 20.3 120 == XWiki Core Access ==
Caleb James DeLisle 20.1 121
Vincent Massol 34.1 122 Sometimes the XWiki Api doesn't provide the methods which you need for your application. You can gain raw access the core of XWiki but it presents an increased security risk and requires **Programming Rights** to be able to save the page containing the script (Programming Rights are not required for viewing a page containing a script requiring Programming Rights, rights are only needed at save time). Using the core should be avoided if at all possible.
Caleb James DeLisle 30.1 123
coffeemug13 31.1 124 {{code language="java"}}
125 {{groovy}}
Caleb James DeLisle 20.1 126 def xc = xcontext.getContext();
127 def wiki = xc.getWiki();
128 def xdoc = doc.getDocument();
coffeemug13 31.1 129 {{/groovy}}
130 {{/code}}
Caleb James DeLisle 30.1 131
Caleb James DeLisle 20.1 132 After using this snippet, you will have 3 new objects:
OCTAGRAM 24.1 133
Vincent Massol 49.3 134 * {{scm path="xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWikiContext.java"}}The underlying XWikiContext behind the Context object{{/scm}}: **##xc##**
135 * {{scm path="xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/XWiki.java"}}The underlying XWiki object which backs the **##xwiki##** object{{/scm}}: **##wiki##**
136 * {{scm path="xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java"}}The underlying XWikiDocument behind the current Document{{/scm}}: **##xdoc##**
Caleb James DeLisle 20.1 137
Caleb James DeLisle 22.2 138 You will find that many of the methods in **##wiki##** and **##xdoc##** require an instance of the XWikiContext, this is the underlying xcontext **##xc##** not the Api context **##xcontext##**.
139
Caleb James DeLisle 22.1 140 Again, these methods are only for the rare cases when functionality is not provided by the public Api. We put a lot of effort into preserving the behavior of the public Api and much less into preserving the behavior of core methods so you may find that core methods are deprecated, removed, or their behavior is changed in subsequent versions.
Caleb James DeLisle 20.1 141
Vincent Massol 23.2 142 == Querying XWiki's Model ==
143
Manuel Smeria 45.4 144 From your script you can query the full XWiki Model. Check the [[Query Module>>extensions:Extension.Query Module]] for more information.
Vincent Massol 23.2 145
Vincent Massol 58.1 146 See also this [[HQL tutorial in Velocity>>Documentation.DevGuide.Scripting.velocityHqlExamples.WebHome]].
Vincent Massol 57.1 147
OCTAGRAM 24.1 148 {{id name="velocity"/}}
149
Vincent Massol 20.3 150 = Velocity Specific Information =
Vincent Massol 1.1 151
Simon Urli 67.4 152 Velocity is currently the only scripting language which can be used without Programming [[xwiki:Documentation.AdminGuide.Access Rights]]. This means you can save Velocity scripts using a user with less permissions and nobody will be able to exploit your script to perform a security breach.
Vincent Massol 1.1 153
Vincent Massol 65.2 154 You can [[gain access to the XWiki core>>#HXWikiCoreAccess]] from Velocity but this will require Programming Rights. Strictly speaking, protected APIs are only available when the page that contains them was last saved by someone who had Programming Rights (see above).
Vincent Massol 34.1 155
Vincent Massol 54.2 156 In Velocity you can't import classes and as such you cannot gain direct access to XWiki components as shown [[above>>Documentation.DevGuide.WebHome#HXWikiComponentAccess]]. This leaves you with the provided [[bindings>>Documentation.DevGuide.WebHome#HBindings]] (NOTE: In Velocity, these bindings all start with **##$##** as with all other Velocity variables)
Vincent Massol 1.1 157
Vincent Massol 56.1 158 For more information about programming in the Velocity language, you can refer to the [[Velocity User Guide>>http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html]]. See also this [[Velocity Training Presentation>>Documentation.DevGuide.Scripting.XWikiVelocityTraining.WebHome]].
Vincent Massol 1.1 159
Vincent Massol 36.2 160 For more details on using Velocity check the [[Velocity Module Documentation>>extensions:Extension.Velocity Module]] which also contains the full list of Velocity Tools that you can use in your scripts.
OCTAGRAM 24.1 161
162 {{info}}
163 If you wish to add new Velocity tools you'll need to edit your ##xwiki.properties## file and follow the instructions in there.
164 {{/info}}
Vincent Massol 9.2 165
Vincent Massol 53.1 166 To include Velocity scripts in other Velocity scripts, see [[How to include a Velocity page into another page>>FAQ.IncludeInVelocity]].
jeanvivienmaurice 1.20 167
Vincent Massol 65.1 168 == Velocity Macros ==
169
Vincent Massol 65.2 170 See [[available Velocity macros>>extensions:Extension.Web Resources for XWiki.WebHome]].
Vincent Massol 65.1 171
Vincent Massol 20.3 172 == Other Velocity Variables ==
Vincent Massol 13.1 173
Vincent Massol 60.2 174 === Controlling Page Tabs ===
Vincent Massol 13.1 175
Vincent Massol 60.1 176 You can control whether to display Comments/Annotations/History/Attachment/Information tabs or not by setting some velocity variables to ##false##:
Vincent Massol 13.1 177
Thomas Mortagne 33.1 178 {{code language="velocity"}}
Vincent Massol 47.1 179 #set ($showcomments = false)
Vincent Massol 60.1 180 #set ($showannotations = false)
Vincent Massol 47.1 181 #set ($showattachments = false)
182 #set ($showhistory = false)
183 #set ($showinformation = false)
Caleb James DeLisle 16.1 184 {{/code}}
Vincent Massol 13.1 185
186 To remove them all you can set:
187
Thomas Mortagne 33.1 188 {{code language="velocity"}}
Vincent Massol 60.1 189 #set($displayDocExtra = false)
Caleb James DeLisle 16.1 190 {{/code}}
Vincent Massol 13.1 191
Vincent Massol 62.5 192 You can also control whether shortcuts links are displayed in the page menu:
Vincent Massol 60.1 193
194 {{code language="velocity"}}
195 #set($displayShortcuts = false)
196 {{/code}}
197
Vincent Massol 61.1 198 === Control Content Footer ===
199
Vincent Massol 62.2 200 {{info}}XWiki 9.8{{/info}} The content footer can be controlled through the ##displayContentFooter## binding.
Vincent Massol 61.1 201
Vincent Massol 62.3 202 {{image reference="contentFooterAndDocextra.png"/}}
Vincent Massol 61.1 203
Marta Girdea 28.2 204 === Information about the current user ===
Marta Girdea 28.1 205
Mohammad Humayun Khan 66.2 206 The following variables (set in the {{scm path="xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/xwikivars.vm"}}xwikivars.vm{{/scm}} template) are shortcuts for checking various information **for the current user**:
Marta Girdea 28.1 207
208 * ##$isGuest##: checks if the current user is ##XWiki.XWikiGuest##
209 * ##$isSuperAdmin##: checks if the current user is the special user ##superadmin##
210
211 * ##$hasComment##: checks comment rights on the current document
212 * ##$hasEdit##: checks edit rights on the current document
213 * ##$hasWatch##: checks if the user is authenticated and the watch service is available
214
215 * ##$hasAdmin##: checks admin rights on the current document
216 * ##$hasSpaceAdmin##: checks admin rights on the ##XWikiPreferences## document of the current space
217 * ##$hasGlobalAdmin##: checks admin rights on ##XWiki.XWikiPreferences##
218
219 * ##$hasCreateSpace##: checks edit rights on that page that does not exist, in a space that doesn't exist
220 * ##$hasCreatePage##: checks edit rights on that page that does not exist, in the current space
221
222 * ##$hasProgramming##: checks if the current user has programming rights
223
224 * ##$isAdvancedUser##: advanced users: ##superadmin##, users with the ##usertype## property set to "Advanced", guest users with admin rights
225
Marta Girdea 28.2 226 Example:
Caleb James DeLisle 30.1 227
Thomas Mortagne 33.1 228 {{code language="velocity"}}
coffeemug13 31.1 229 {{velocity}}
Marta Girdea 28.2 230 #if ($hasAdmin)
231 ## This link will only be visible to users that have admin rights on this document
232 [[Do some admin action>>Some.Document]]
233 #end
coffeemug13 31.1 234 {{/velocity}}
235 {{/code}}
Marta Girdea 28.2 236
Marta Girdea 28.1 237 === Information about the current wiki ===
238
Mohammad Humayun Khan 66.2 239 The following variables (set in the {{scm path="xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/xwikivars.vm"}}xwikivars.vm{{/scm}} template) are shortcuts for checking various information **about the current wiki**:
Marta Girdea 28.1 240
241 * ##$isReadOnly##
242 * ##$isInServletMode##
243 * ##$isInPortletMode##
244
Vincent Massol 20.3 245 = Groovy Specific Information =
Vincent Massol 1.1 246
Eduard Moraru 35.4 247 {{info}}
Manuel Smeria 45.4 248 Currently all non Velocity scripting languages are only allowed to be used by users having Programming Rights.
Eduard Moraru 35.4 249 {{/info}}
Vincent Massol 1.1 250
Clemens Robbenhaar 63.1 251 * See Groovy snippets in the [[Snippets wiki>>snippets:Main.WebHome]] (click on the "Groovy" tag in the Tag Cloud)
Simpel 67.3 252 * [[Groovy web site>>https://groovy-lang.org]]
Vincent Massol 1.1 253
Vincent Massol 20.3 254 == Groovy Example ==
gfiorentino 1.17 255
Caleb James DeLisle 18.1 256 The following example demonstrates how to use a groovy script to interact with velocity code in your page. This example performs a DNS lookup from the velocity variable ##$hostname## and stores the result in the variable ##$address##.
Vincent Massol 8.2 257
Vincent Massol 34.1 258 **Using XWiki Syntax 2.0:**
259
Caleb James DeLisle 19.1 260 Objects can be passed back and forth between scripting languages by storing them in commonly available objects. One such commonly available object which only lasts the length of the request is the context object, known as xcontext.
Caleb James DeLisle 30.1 261
Thomas Mortagne 33.1 262 {{code language="velocity"}}
coffeemug13 31.1 263 {{velocity}}
Caleb James DeLisle 19.1 264 #set($hostname = "www.xwiki.org")
265 Host Name: $hostname
266 $xcontext.put("hostname", $hostname)
267 {{/velocity}}
268 {{groovy}}
269 import java.net.InetAddress;
270 host = xcontext.get("hostname");
271 InetAddress addr = InetAddress.getByName(host);
272 String address = addr.getHostAddress();
273 xcontext.put("address", address);
274 {{/groovy}}
275 {{velocity}}
276 IP Address: $xcontext.get("address")
coffeemug13 31.1 277 {{/velocity}}
278 {{/code}}
Caleb James DeLisle 19.1 279
Vincent Massol 34.1 280 **Using XWiki Syntax 1.0:**
281
Caleb James DeLisle 19.1 282 Because Groovy and Velocity code are parsed together, variables defined in Groovy can be used directly in velocity without storing in and retrieving from the context.
Caleb James DeLisle 30.1 283
Thomas Mortagne 33.1 284 {{code language="velocity"}}
coffeemug13 31.1 285 #set ($hostname = "www.xwiki.org")
Caleb James DeLisle 18.1 286 Host Name: $hostname
Vincent Massol 8.2 287 <%
288 import java.net.InetAddress;
289 vcontext = context.get("vcontext");
Caleb James DeLisle 18.1 290 host = vcontext.get("hostname");
291 InetAddress addr = InetAddress.getByName(host);
292 String address = addr.getHostAddress();
Vincent Massol 8.2 293 %>
coffeemug13 31.1 294 IP Address: $address
295 {{/code}}
Caleb James DeLisle 20.1 296
Caleb James DeLisle 26.1 297 = Python Specific Information =
Marta Girdea 28.1 298
Vincent Massol 34.1 299 You can run Python code in XWiki just like Velocity or Groovy.
Caleb James DeLisle 30.1 300
coffeemug13 31.1 301 {{code language="python"}}
302 {{python}}
Caleb James DeLisle 20.1 303 print "The full name of this document is " + doc.getFullName()
coffeemug13 31.1 304 {{/python}}
305 {{/code}}
Caleb James DeLisle 20.1 306
Thomas Mortagne 42.1 307 = Share variable between languages =
308
Vincent Massol 47.6 309 Most JSR223 based scripting languages reinject the created variable in the current ##ScriptContext## which means you can define a variable in a Groovy script and reuse it in a following Python script for example.
Thomas Mortagne 42.1 310
Vincent Massol 50.1 311 Since the Velocity implementation isn't based on JSR223, we have also set up a bridge so that Velocity scripts can access the current ##ScriptContext## variables. This allows for example to define a variable in a Groovy script and then reuse it in a Velocity one:
Thomas Mortagne 42.1 312
313 {{code}}
314 {{groovy}}
Vincent Massol 50.3 315 var = "foo"
Thomas Mortagne 42.1 316 {{/groovy}}
317
318 {{velocity}}
319 $var
320 {{/velocity}}
321 {{/code}}
322
Vincent Massol 50.1 323 Starting with XWiki 8.3 the Velocity bridge has been improved and it's now possible to do the opposite, i.e. define a variable in Velocity and access it from another scripting language. For example:
324
Vincent Massol 50.2 325 {{code}}
Vincent Massol 50.1 326 {{velocity}}
Vincent Massol 50.3 327 #set($myvar = "foo")
Vincent Massol 50.1 328 {{/velocity}}
329
330 {{groovy}}
331 print myvar
332 {{/groovy}}
Vincent Massol 50.2 333 {{/code}}
Vincent Massol 50.1 334
Vincent Massol 47.6 335 Note that you can also share variables by setting them in the XWiki Context (##xcontext## binding).
336
Vincent Massol 20.3 337 = Scripting In XWiki Syntax 1.0 =
Caleb James DeLisle 20.1 338
Manuel Smeria 45.4 339 XWiki Syntax 1.0 is rendered by an old rendering engine which is still supported but for which no further development is planned (it will eventually be removed). Syntax 1.0 has some idiosyncrasies which were solved by syntax 2.0.
Caleb James DeLisle 20.1 340
341 * The only scripting languages available to you are Velocity and Groovy.
342 * In Groovy, the context is known as: **##context##** not **##xcontext##**
Vincent Massol 30.3 343 * The beginning and end of Groovy scripts are denoted by <% and %> rather than through the [[extensions:Extension.Groovy Macro]] (using ~{~{groovy}} and ~{~{/groovy}})
344 * Velocity is parsed in a page no matter what (there is no need to invoke the [[extensions:Extension.Velocity Macro]] using ~{~{velocity}} and ~{~{/velocity}})
Caleb James DeLisle 20.1 345
Manuel Smeria 45.4 346 The last part is important because it means you need to be careful when using **$** and **#** in your document. This is still true inside of **<%** and **%>** so you have to be careful when writing Groovy.

Get Connected