Groovy Luminary Certification
Welcome, Cadet. You've embarked on a journey to master Groovy—the dynamic, powerful language that drives Spaceport. Whether you've completed the Developer Onboarding and built your Tic-Tac-Toe application, or you're the type who prefers to dive straight into deep technical waters, this certification course will transform you into a Groovy expert.
This is your Starfleet Academy for Groovy. By the end of this training program, you'll have mastered the concepts of everything from basic syntax to advanced metaprogramming, earning your certification as a Groovy Luminary—ready to command sophisticated Spaceport applications with confidence and elegance.
Course Philosophy: Groovy is designed to make developers more productive. It reduces boilerplate, embraces dynamic typing where helpful, and provides powerful features built on the solid foundation of the JVM. Whether you're coming from Java, JavaScript, or you jumped straight into Spaceport, this course will show you the full power of the language underneath.
# Course Structure
This certification is organized as a series of missions, each building on the last:
- Mission 01: First Contact
Core Groovy syntax and fundamentals will be covered here, ensuring everyone has a solid foundation. Groovy concepts like closures, Groovy Truth, collections, and object-oriented programming will be explained in detail.
- Mission 02: The Spaceport Core
How Groovy powers Spaceport's architecture is fundamental to your success. This mission covers how Groovy works with Spaceport's Source Modules, Alerts, and Class Enhancements.
- Mission 03: Language Interoperability - Groovy for Java and JavaScript developers
- Mission 04: Engineering (Metaprogramming) - Runtime enhancement and DSLs
- Mission 05: The Bridge (Groovy in Launchpad) - UI development patterns
- Final Examination - Practical coding challenges
- Certification - Your Groovy Luminary credential
# Prerequisites
Before beginning this certification, you should have:
- Working knowledge of either Java or JavaScript (or have completed the Tic-Tac-Toe tutorial)
- Basic understanding of object-oriented programming concepts
- Familiarity with variables, functions, loops, and conditionals
- A willingness to embrace syntactic sugar and convention over configuration
Note: If you haven't gone through the Developer Onboarding yet, that's okay! Many developers prefer to "jump in" and learn by doing. This course will give you the theoretical foundation while you experiment with code.
# Mission Brief: What is Groovy?
Apache Groovy is a powerful, optionally typed, and dynamic JVM language designed for the Java platform. It compiles to Java bytecode and runs on the Java Virtual Machine, which means:
- Full Java Compatibility: Every valid Java class is also a valid Groovy class
- Seamless Interoperability: You can call Java libraries from Groovy and vice versa
- Dynamic Features: Optional typing, closures, and meta-programming capabilities
- Reduced Boilerplate: Concise syntax that eliminates much of Java's verbosity
For Java developers, Groovy feels like Java with the friction removed. For JavaScript developers, Groovy offers familiar dynamic features with the power and safety of the JVM. For hands-on learners who built the Tic-Tac-Toe game, this course will explain the "magic" you've already been using.
# Mission 01: First Contact
Mission Briefing: Before we engage the warp drive, we must ensure all systems are nominal. This mission covers Groovy's core syntax and fundamental concepts—the "plasma" that fuels the Spaceport engine.
Whether you're a seasoned Java developer, a JavaScript wizard, or you learned by building the Tic-Tac-Toe game, this module ensures everyone starts with the same solid foundation.
Objectives:
- Master Groovy's syntax fundamentals
- Understand closures—Groovy's most powerful feature
- Learn the concept of Groovy Truth
- Explore the GDK (Groovy Development Kit)
- Work with collections and strings
## Core Syntax Essentials
### Semicolons are Optional
Unlike Java, semicolons at the end of statements are optional in Groovy.
// Java style (still valid)
def name = "Spaceport";
println(name);
// Groovy style (preferred)
def name = "Spaceport"
println name
### Parentheses are Often Optional
When calling methods, you can often omit parentheses, making code read more naturally.
// Traditional style
println("Greetings, Commander!")
// Groovy style
println "Greetings, Commander!"
// Multiple parameters still need parentheses around arguments
Math.max(5, 10)
### Return Statements are Implicit
The last expression in a method or closure is automatically returned.
// Explicit return (Java style)
def multiply(a, b) {
return a * b
}
// Implicit return (Groovy style - preferred)
def multiply(a, b) {
a * b
}
## Variables and Types
### Dynamic Typing with def
The def keyword declares a dynamically-typed variable. The type is determined at runtime.
def name = "Commander" // String
def age = 35 // Integer
def isActive = true // Boolean
def coordinates = [10, 20] // List
### Static Typing
You can still use explicit types when you want compile-time type checking and better IDE support.
String rank = "Captain"
Integer shipId = 74205
List<String> crewMembers = ["Sisko", "Kira", "Dax"]
### Type Coercion
Groovy provides explicit type conversion using the as operator or conversion methods.
def number = "42"
// Explicit coercion using 'as'
Integer result = number as Integer
// Or use conversion methods
Integer result = number.toInteger()
Double decimal = number.toDouble()
Note: Spaceport features like Transmissions and Cargo provide additional type-safe getter methods that handle coercion automatically:
getBool()- Converts to boolean (handles "true", "on", "yes", checkbox states)getNumber()- Converts to Long or Double depending on decimal pointgetInteger()- Converts to IntegergetString()- Safely converts to StringgetList()- Converts to List (parses comma-separated strings, JSON arrays, or wraps single items)
These methods are especially useful when working with form data or client-side transmissions where values arrive as strings, but more on that later.
## Strings
Groovy provides multiple ways to work with strings, each with its own superpowers.
### Single-Quoted Strings
Simple, literal strings without interpolation.
def message = 'Hello, World!'
### Double-Quoted Strings with GString Interpolation
Use ${} to embed expressions inside strings. This is similar to JavaScript template literals.
def name = "Captain"
def rank = "Commander"
// GString interpolation
def greeting = "Greetings, ${rank} ${name}!"
println greeting // "Greetings, Commander Captain!"
// For simple variables, you can omit the braces
def simple = "Hello, $name"
### Multi-Line Strings
Use triple quotes for multi-line strings.
def report = """
Mission Status: Active
Coordinates: Alpha Quadrant
Ship Status: Operational
"""
### Slashy Strings
Useful for regular expressions, as backslashes don't need escaping.
// No need to escape backslashes, unlike "\\d{3}-\\d{2}-\\d{4}"
def pattern = /\d{3}-\d{2}-\d{4}/
## Collections: Lists and Maps
Groovy makes working with collections remarkably simple.
### Lists
Lists in Groovy are dynamic arrays, similar to JavaScript arrays or Java's ArrayList.
// Creating a list
def crew = ["Sisko", "Kira", "Dax"]
// Accessing elements (zero-based index)
println crew[0] // "Sisko"
println crew[-1] // "Dax" (negative index from end)
// Adding elements
crew << "Bashir" // Append using left-shift operator
crew.add("O'Brien") // Using add method
// List operations
println crew.size() // 5
println crew.contains("Kira") // true
### Maps
Maps are key-value pairs, similar to JavaScript objects or Java's HashMap.
// Creating a map
def ship = [
name: "Defiant",
registry: "NX-74205",
captain: "Sisko"
]
// Accessing values
println ship.name // "Defiant" (property notation)
println ship['registry'] // "NX-74205" (subscript notation)
println ship.get('captain') // "Sisko" (method notation)
// Adding/updating entries
ship.crew = 50
ship['class'] = "Escort"
// Checking for keys
if (ship.containsKey('captain')) {
println "Captain: ${ship.captain}"
}
## The Universal Translator: Closures
In Groovy, a closure is the single most important concept to master. It's an anonymous, nestable block of code that can be passed around as a variable, just like an object. Think of it as a "command packet" that you can hand off to other parts of your system to be executed at a later time.
In Spaceport, you use closures everywhere:
- As Server Actions in Launchpad (e.g., on-click=${ _{ ... }})
- For iterating over lists (.each { }, .collect { })
- In conditional logic (.find { }, .any { })
### Basic Closure Syntax
A closure is defined by curly braces {}.
// A simple closure assigned to a variable
def greeting = {
println "Hello from inside a closure!"
}
// You execute it just like a method
greeting()
// Output: Hello from inside a closure!
// Closures can take parameters
def personalizedGreeting = { name ->
println "Hello, ${name}!"
}
personalizedGreeting("Cadet")
// Output: Hello, Cadet!
### The it Parameter
If a closure takes only one parameter, you don't even need to name it. Groovy provides an implicit parameter named it. This is extremely common in Spaceport.
def numbers = [1, 2, 3, 4]
// Using a named parameter 'n'
def doubled = numbers.collect { n ->
return n * 2
}
// Using the implicit 'it' parameter (much cleaner)
def tripled = numbers.collect {
return it * 3
}
// Groovy also has implicit 'return' on the last line
def quadrupled = numbers.collect { it * 4 }
// doubled == [2, 4, 6, 8]
// tripled == [3, 6, 9, 12]
// quadrupled == [4, 8, 12, 16]
This concise syntax is why on-click=${ _{ ... }} in Launchpad is so powerful. The it (or t in Spaceport conventions) in that context is the Transmission object, giving you direct access to the client request data.
### Closures as Method Parameters
Closures can be passed as arguments to methods, enabling powerful functional programming patterns.
def crew = ["Sisko", "Kira", "Dax"]
// Filter with findAll
def commanders = crew.findAll { it.startsWith("S") }
println commanders // ["Sisko"]
// Transform with collect
def lengths = crew.collect { it.length() }
println lengths // [5, 4, 3]
// Custom iteration
def announceAll = { list, closure ->
list.each { item ->
closure(item)
}
}
announceAll(crew) { member ->
println "Crew member on deck: $member"
}
### Closure Scope and Variables
Closures can access variables from their surrounding scope.
def multiplier = 2
def multiply = { number ->
number * multiplier // Accesses 'multiplier' from outer scope
}
println multiply(5) // 10
multiplier = 3
println multiply(5) // 15 (uses updated value)
## Groovy Truth: "It's True, but Not as We Know It"
In Java, a conditional if (...) statement must evaluate a boolean (true or false). This leads to lots of if (myString != null && !myString.isEmpty()) or if (myList.size() > 0).
Groovy simplifies this with Groovy Truth. Many different types can be evaluated as true or false.
This is false in Groovy:
- null
- The boolean false
- The number 0 (or 0.0)
- An empty String ("")
- An empty Collection ([], [:])
- An empty Matcher ("abc" =~ /xyz/ with no matches)
Everything else is true!
### Practical Application
This dramatically cleans up your code, especially in Launchpad templates.
Java-style (Verbose):
if (user != null && user.getRole() != null && user.getRole().equals("admin")) {
// ... show admin button
}
if (errors != null && !errors.isEmpty()) {
// ... show error messages
}
Groovy-style (Clean & Idiomatic):
// The '?' is the safe-navigation operator.
// If 'user' or 'user.role' is null, the expression stops and returns null (which is false).
if (user?.role == "admin") {
// ... show admin button
}
// If 'errors' is null OR an empty list, this is false.
if (errors) {
// ... show error messages
}
You will use this constantly to check for data before rendering HTML in Launchpad templates.
## Control Structures
### Elvis Operator
The ?: operator provides a concise way to handle null or false values (similar to JavaScript's ||).
def name = null
def displayName = name ?: "Unknown"
println displayName // "Unknown"
// Longer equivalent
def displayName = name ? name : "Unknown"
### Safe Navigation Operator
The ?. operator prevents NullPointerExceptions by short-circuiting if the left side is null.
def user = null
println user?.name // null (doesn't throw exception)
def ship = [captain: [name: "Sisko"]]
println ship.captain?.name // "Sisko"
println ship.crew?.size() // null (crew doesn't exist)
### Loops
#### Enhanced For Loop
// Iterating over a list
def crew = ["Sisko", "Kira", "Dax"]
for (member in crew) {
println member
}
// Iterating over a range
for (i in 1..5) {
println "Count: $i"
}
// Iterating over a map
def ship = [name: "Defiant", registry: "NX-74205"]
for (entry in ship) {
println "${entry.key}: ${entry.value}"
}
#### Each Method
The .each() method is more idiomatic in Groovy and takes a closure.
def crew = ["Sisko", "Kira", "Dax"]
// With implicit parameter 'it'
crew.each {
println it
}
// With explicit parameter
crew.each { member ->
println "Crew member: $member"
}
// Maps with key and value
def ship = [name: "Defiant", registry: "NX-74205"]
ship.each { key, value ->
println "$key: $value"
}
## Ranges
Ranges are a powerful feature for creating sequences.
// Inclusive range (1, 2, 3, 4, 5)
def range1 = 1..5
// Exclusive range (1, 2, 3, 4)
def range2 = 1..<5
// Using ranges in loops
for (i in 1..3) {
println "Iteration $i"
}
// Converting to list
def numbers = (1..5).toList() // [1, 2, 3, 4, 5]
// Character ranges
def letters = ('a'..'e').toList() // ['a', 'b', 'c', 'd', 'e']
## Operators
### Spaceship Operator
The <=> operator compares two values and returns -1, 0, or 1 (useful for sorting).
println 5 <=> 3 // 1 (5 is greater)
println 3 <=> 5 // -1 (3 is less)
println 5 <=> 5 // 0 (equal)
// Great for sorting
def numbers = [3, 1, 4, 1, 5, 9]
numbers.sort { a, b -> a <=> b }
println numbers // [1, 1, 3, 4, 5, 9]
### Spread Operator
The *. operator calls a method or accesses a property on all items in a collection.
def crew = [
[name: "Sisko", rank: "Captain"],
[name: "Kira", rank: "Major"],
[name: "Bashir", rank: "Doctor"]
]
// Get all names
def names = crew*.name
println names // ["Sisko", "Kira", "Bashir"]
// Call a method on all items
def lengths = names*.length()
println lengths // [5, 4, 6]
## The GDK (Groovy Development Kit)
The GDK is Groovy's "away team" equipment. Groovy takes standard Java classes (like java.util.List, java.lang.String, java.lang.Object) and adds dozens of helpful new methods to them.
This means that even when you're working with a plain Java object, you have a whole new set of "Groovy tools" available on it.
### Example: java.util.List
def crew = ['Sisko', 'Kira', 'Dax', 'Bashir']
// GDK method: .each
crew.each {
println "${it} reporting for duty!"
}
// GDK method: .find
def doctor = crew.find { it == 'Bashir' }
// doctor == "Bashir"
// GDK method: .join
def manifest = crew.join(', ')
// manifest == "Sisko, Kira, Dax, Bashir"
// GDK method: .last
def lastOnBridge = crew.last()
// lastOnBridge == "Bashir"
### Example: java.lang.String
def myString = "spaceport"
// GDK method: .capitalize()
println myString.capitalize() // "Spaceport"
// GDK method: .isInteger()
println "123".isInteger() // true
println "abc".isInteger() // false
Spaceport takes this concept one step further with its own Class Enhancements, adding even more methods that are specific to web development. We'll cover those in Mission 02.
## Object-Oriented Programming
### Classes and Objects
Groovy classes are similar to Java classes but with less boilerplate.
class Starship {
String name
String registry
Integer crew
// Constructor (automatically generated for properties)
// Groovy creates getters, setters, and a constructor
def launch() {
println "$name ($registry) is launching with $crew crew members"
}
}
// Creating and using objects
def defiant = new Starship(
name: "Defiant",
registry: "NX-74205",
crew: 50
)
defiant.launch()
println defiant.name // Uses auto-generated getter
### Properties vs Fields
In Groovy, public fields automatically become properties with getters and setters.
class Officer {
String name // Property (public, with getter/setter)
private int age // Field (truly private)
def introduce() {
println "I am $name, age $age"
}
}
def officer = new Officer()
officer.name = "Dax" // Uses generated setter
// officer.age = 35 // Compile error! Private field
officer.introduce()
### Methods
Methods in Groovy have optional return types and can use implicit returns.
class Calculator {
// Method with explicit return type
Integer add(Integer a, Integer b) {
a + b // Implicit return
}
// Method with def (dynamic return type)
def multiply(a, b) {
a * b
}
// Method with optional parameters and default values
def greet(String name = "Officer") {
"Hello, $name"
}
}
def calc = new Calculator()
println calc.add(5, 3) // 8
println calc.multiply(4, 2) // 8
println calc.greet() // "Hello, Officer"
println calc.greet("Commander") // "Hello, Commander"
### Inheritance
Groovy supports standard object-oriented inheritance.
class Vehicle {
String type
def move() {
println "$type is moving"
}
}
class Starship extends Vehicle {
String name
Starship(String name) {
this.type = "Starship"
this.name = name
}
@Override
def move() {
println "$name is warping through space"
}
}
def ship = new Starship("Defiant")
ship.move() // "Defiant is warping through space"
### Traits
Traits are similar to interfaces but can contain method implementations (like Java 8+ default methods).
trait Communicator {
def sendMessage(String message) {
println "Transmitting: $message"
}
}
trait Navigator {
def plotCourse(String destination) {
println "Course set for $destination"
}
}
class Starship implements Communicator, Navigator {
String name
}
def ship = new Starship(name: "Defiant")
ship.sendMessage("We come in peace")
ship.plotCourse("Bajor")
## Mission Debrief
You have recalibrated your understanding of Groovy's core mechanics. You've mastered:
- ✅ Groovy's clean, optional syntax
- ✅ Closures—the heart of Groovy's power
- ✅ Groovy Truth for elegant conditionals
- ✅ The GDK's collection of useful methods
- ✅ Lists, maps, and data structures
- ✅ Object-oriented programming in Groovy
- ✅ Powerful operators unique to Groovy
These concepts are the "plasma" that fuels the Spaceport engine. With this foundation, you are ready to proceed to the next module and see how Groovy powers the Spaceport framework itself.
# Mission 02: The Spaceport Core
Mission Briefing:
Cadet, you've passed your fundamental review. It's time to leave the simulators and report to Engineering. Your assignment is to understand the "warp core" of Spaceport—how it manages, loads, and enhances your Groovy code.
In this mission, we'll examine the three systems that form the heart of Spaceport's power: Source Modules (the "nacelles" that house your logic), Alerts (the "internal comms" that let systems talk), and Class Enhancements (the "subspace shortcuts" that make the ship fly faster).
Understanding this core architecture is the difference between being a passenger and being an engineer.
Objectives:
- Understand how Source Modules are loaded and manage application state
- Master the critical distinction between
staticand instance - Learn to use Alerts for clean, event-driven communication
- Leverage Spaceport's Class Enhancements for more powerful code
# Source Modules: The "Decoupled Nacelles"
In a starship, the engines (nacelles) are separate from the main hull. They provide the power, but they are self-contained, modular, and can be serviced or even swapped.
Source Modules are the "nacelles" of your Spaceport application. They are Groovy classes that Spaceport dynamically
loads from your /modules directory. This is where you write all your core application logic.
If you built the Tic-Tac-Toe game, you already created a Source Module (Game.groovy). Now you'll understand how it really works under the hood.
## Static vs. Instance: The Critical Difference
This is a core concept that developers often stumble over. How you define your variables and methods in a Groovy Source Module has a massive impact on how your application behaves.
### static (Shared State - The "Ship's Computer")
A static variable or method belongs to the class itself. In Spaceport, this means there is only one instance of
it for the entire running application.
Use Case: Caches, application-wide settings, shared game states (like in the Tic-Tac-Toe tutorial), or utility methods.
Analogy: It's a "ship-wide" system. When one crew member updates the ship's main computer, it's updated for everyone.
Be careful! Since the state is shared, it is not "thread-safe" by default. Multiple users accessing it at the same time can cause conflicts. For thread-safe shared state, use Java's concurrent collections like ConcurrentHashMap, CopyOnWriteArrayList, or wrap access with proper synchronization. Groovy also provides @Synchronized annotations and the Collections.synchronizedMap() utility for simpler cases.
// Located in /modules/ShipSystems.groovy
class ShipSystems {
// This variable is shared across ALL users and ALL requests because it's STATIC.
static def system = [
shieldFrequency: 50.0,
enginePower: 75.0,
frequencyStable: true
] as ConcurrentHashMap
// This is a utility method, it doesn't need instance data.
static def isFrequencyStable(def frequency) {
return system.frequency > 40.0
}
static def changeShieldFrequency(def newFrequency) {
system.shieldFrequency = newFrequency
// Also set stability based on new frequency
system.frequencyStable = isFrequencyStable(newFrequency)
}
}
### instance (Request-Scoped State - "Your PADD")
An instance variable (any variable defined without static) belongs to an object, or instance, of the class. Spaceport's default behavior is to create a new instance of your Source Module for every incoming HTTP request.
Use Case: Handling form data, managing a specific user's request, or performing any operation that should be isolated to a single request.
Analogy: It's your personal "PADD" (tablet). You can write notes on it, and it doesn't affect anyone else's PADD. When your "mission" (the request) is over, the PADD is wiped clean.
// Located in /modules/RequestProcessor.groovy
import spaceport.computer.alerts.Alert
import spaceport.computer.alerts.results.*
class RequestProcessor {
// This variable is NEW for every request
String userName
// This method can access the instance variable
def greetUser() {
println "Hello, ${userName}"
}
// This Alert handles the request, creating a new instance
@Alert("on /greet hit")
static _process(HttpResult r) {
// 1. Create a new instance of this class
def processor = new RequestProcessor()
// 2. Populate its INSTANCE data
processor.userName = r.context.data.name
// 3. Call its INSTANCE method
processor.greetUser()
// 4. Send a response
r.writeToClient("User ${processor.userName} greeted.")
}
}
### When to Use Static vs. Instance
| Scenario | Use Static | Use Instance |
|---|---|---|
| Application-wide cache | ✅ | ❌ |
| Game state shared by all players | ✅ | ❌ |
| Utility/helper methods | ✅ | ❌ |
| Request-specific form data | ❌ | ✅ |
| User session data (when not in Cargo) | ❌ | ✅ |
| Temporary calculation variables | ❌ | ✅ |
Tic-Tac-Toe Example: In the tutorial, you used static variables for the game board because there was only one game shared by everyone. For a multi-player version, you'd want instance variables or Cargo objects to isolate each game.
Mastering this distinction is the key to managing state and preventing data "leaks" between user sessions.
# Alerts: The "Internal Comms"
A ship's computer doesn't have one giant main method. Instead, it reacts to events: "Red Alert!", "Intruder Alert!", "Incoming Transmission!"
Alerts are Spaceport's event system. They are the "internal communications" that allow your decoupled modules to talk to each other without being directly tied together. This is what you use to handle HTTP requests, respond to server startup, or process data.
An Alert handler is just a static Groovy method annotated with @Alert.
## Basic Alert Usage
// Located in /modules/CommsOfficer.groovy
import spaceport.computer.alerts.Alert
import spaceport.computer.alerts.results.*
class CommsOfficer {
// This method "listens" for requests to the root path "/"
@Alert("on / hit")
static _handleHomepage(HttpResult r) {
// Set the response
r.writeToClient("Welcome to the Spaceport, Captain!")
}
// This method "listens" for requests to "/bridge"
@Alert("on /bridge hit")
static _handleBridge(HttpResult r) {
// You can also "fire" new, custom Alerts
// Another module might be listening for this!
Alert.fire("INTRUDER_ON_BRIDGE")
r.writeToClient("Bridge access granted.")
}
}
## Custom Alerts
You can create your own custom alerts to build an event-driven architecture.
// Module A: Security.groovy
import spaceport.computer.alerts.Alert
class Security {
@Alert("INTRUDER_ON_BRIDGE")
static _handleIntruder(Result r) {
println "SECURITY ALERT: Intruder detected on bridge!"
// Log to database, notify admins, etc.
}
}
Using Alerts lets you build complex applications where logic is neatly separated. Your Security module can listen for INTRUDER_ON_BRIDGE without the CommsOfficer module even knowing it exists. This is powerful decoupling.
For more on Alerts and routing, see the Alerts Documentation.
# Class Enhancements: "Subspace Shortcuts"
You learned about the GDK in Mission 01—how Groovy adds methods to standard Java classes.
Spaceport takes this to the next level. Class Enhancements are special methods Spaceport adds to the GDK, specifically designed for web development. Think of them as "subspace shortcuts" that our engineers built into the ship's computer to make common tasks faster.
You've already seen some of these if you built the Tic-Tac-Toe game or went through the onboarding guide.
## String Enhancements
Commonly used in Launchpad templates for formatting and URL handling.
def myString = "Captain Sisko"
def myUrl = "My Great Blog Post!"
// .kebab() -> Converts to kebab-case (perfect for CSS classes)
println myString.kebab() // "captain-sisko"
// .slugify() -> Converts to a URL-friendly "slug"
println myUrl.slugify() // "my-great-blog-post"
// .clean() -> Sanitizes HTML to prevent XSS attacks
def userInput = "<script>alert('xss')</script>Hello"
println userInput.clean() // "Hello"
// .quote() -> Wraps in double quotes and escapes internal quotes
def jsString = "Hello \"World\""
println jsString.quote() // "\"Hello \\\"World\\\"\""
## Number Enhancements
Commonly used for formatting display values.
def price = 47.5
def rank = 4
// .money() -> Formats as currency
println price.money() // "$47.50" (in US Locale)
// .ordinal() -> Gets the ordinal string
println rank.ordinal() // "4th"
// .days(), .hours(), .minutes(), .seconds() -> Convert to milliseconds
def timeout = 5.minutes()
println timeout // 300000
## Conditional Enhancements
The .if() enhancement is a Spaceport superpower. It's a clean replacement for ternary operators or if blocks inside strings, and it understands Groovy Truth.
def userRole = "admin"
def noUser = null
// The .if() method takes a closure.
// If the closure is "true", it returns the original string.
// If "false", it returns an empty string.
// 'userRole == "admin"' is true, so it returns the string
def adminLink = "<a href='/admin'>Admin Panel</a>".if { userRole == "admin" }
// 'noUser' is null, which is "false" in Groovy Truth.
// This returns an empty string.
def userLink = "<a href='/user'>Profile</a>".if { noUser }
/*
adminLink == "<a href='/admin'>Admin Panel</a>"
userLink == ""
*/
This enhancement is invaluable for building clean Launchpad Templates without cluttering them with <% if(...) { } %> blocks.
Example in a Template:
<div class="navigation">
${ "<a href='/admin'>Admin</a>".if { user?.role == "admin" }}
${ "<a href='/profile'>Profile</a>".if { user }}
</div>
## Collection Enhancements
def crew = ["Sisko", "Kira", "Dax"]
// .combine() -> Collect results and join into a string
def html = crew.combine { "<li>${it}</li>" }
// html == "<li>Sisko</li><li>Kira</li><li>Dax</li>"
// .snap() -> Temporarily add an item for a duration
def messages = []
messages.snap(5000, "This disappears in 5 seconds")
// After 5 seconds, the item is automatically removed
// .including() -> Fluently add items and return the collection
def updated = crew.including("Bashir").including("O'Brien")
println updated // ["Sisko", "Kira", "Dax", "Bashir", "O'Brien"]
For a complete list of Class Enhancements, see the Class Enhancements Documentation.
# Mission Debrief
You now understand the "engine core" of Spaceport. You've mastered:
- ✅ How Source Modules are loaded and structured
- ✅ The critical distinction between
staticand instance state - ✅ Using Alerts for event-driven, decoupled architecture
- ✅ Leveraging Spaceport's Class Enhancements for cleaner code
With this knowledge, you understand how your Tic-Tac-Toe game (or any Spaceport app) actually works under the hood. You're ready to explore how Groovy compares to other languages you may know.
- --
Mission 03: Language Interoperability(mission-03)
Mission Briefing: You've learned Groovy's syntax and how it powers Spaceport. But you're not starting from zero—you already know Java, JavaScript, or you've been learning by doing. This mission will show you exactly how Groovy relates to the languages you already know.
This comparative approach will help you leverage your existing knowledge while understanding Groovy's unique advantages.
Objectives: - Understand Groovy's relationship to Java - Compare Groovy with JavaScript concepts - Master interoperability patterns - Know when to use which language features
# Groovy for Java Developers
If you're coming from Java, Groovy will feel like Java with all the friction removed. Every valid Java class is also a valid Groovy class, but Groovy adds powerful features that make your code more concise and expressive.
## Key Differences from Java
| Feature | Java | Groovy |
|---|---|---|
| Semicolons | Required | Optional |
| Parentheses | Required for method calls | Optional for top-level calls |
| Return Statement | Required | Optional (implicit) |
| Getters/Setters | Manual (or IDE-generated) | Auto-generated for properties |
| Type Declaration | Always required | Optional with def |
| Default Access Modifier | package-private | public |
| Operator Overloading | Not supported | Fully supported |
| Closures | Lambda expressions (Java 8+) | First-class, powerful closures |
| String Interpolation | String concatenation | GString with ${} |
| Collection Literals | Verbose constructors | [] for lists, [:] for maps |
## Side-by-Side Comparison
Creating a List
Java:
List<String> crew = new ArrayList<>();
crew.add("Sisko");
crew.add("Kira");
crew.add("Dax");
Groovy:
def crew = ["Sisko", "Kira", "Dax"]
Iterating Over a Collection
Java:
for (String member : crew) {
System.out.println("Crew member: " + member);
}
Groovy:
crew.each { println "Crew member: $it" }
Creating a POJO/POGO
Java:
public class Starship {
private String name;
private Integer crew;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCrew() {
return crew;
}
public void setCrew(Integer crew) {
this.crew = crew;
}
public void launch() {
System.out.println(name + " is launching with " + crew + " crew");
}
}
Groovy:
class Starship {
String name
Integer crew
def launch() {
println "$name is launching with $crew crew"
}
}
## Java Interoperability
Groovy code can seamlessly use Java libraries and vice versa. This is one of Groovy's greatest strengths.
// Using Java collections
import java.util.ArrayList
import java.util.HashMap
def list = new ArrayList<String>()
list.add("Sisko")
list.add("Kira")
// Using Java date/time API
import java.time.LocalDate
def today = LocalDate.now()
println "Stardate: $today"
// Calling Java static methods
def max = Math.max(10, 20)
// Using any Java library
import org.apache.commons.lang3.StringUtils
println StringUtils.capitalize("spaceport")
## When to Use Static Typing in Groovy
Even though Groovy supports dynamic typing with def, there are times when explicit Java-style types are better:
Use Static Types When: - Working with Java APIs that require specific types - You want compile-time type checking - Performance is critical (static typing can be faster) - You need better IDE autocomplete and refactoring support
// Dynamic typing - flexible but no compile-time checks
def processData(data) {
return data.size() * 2
}
// Static typing - safer and clearer
Integer processData(List<String> data) {
return data.size() * 2
}
## Migrating from Java to Groovy
If you're converting existing Java code to Groovy:
1. Start by just renaming .java to .groovy - It will work!
2. Remove semicolons - Clean up line endings
3. Replace getters/setters with properties - Let Groovy generate them
4. Use GStrings instead of concatenation - "Hello $name" instead of "Hello " + name
5. Replace loops with closures - .each{}, .collect{}, etc.
6. Add def for local variables - Unless you want static typing
## The Power of Groovy's Dynamic Nature
While Java requires everything to be known at compile time, Groovy can make runtime decisions:
// Groovy can handle unknown types gracefully
def process(input) {
if (input instanceof List) {
return input.size()
} else if (input instanceof String) {
return input.length()
} else if (input instanceof Number) {
return input * 2
}
return "Unknown type"
}
println process([1, 2, 3]) // 3
println process("Spaceport") // 9
println process(5) // 10
This flexibility is what makes Groovy perfect for rapid web development in Spaceport.
# Groovy for JavaScript Developers
If you're coming from JavaScript, you'll find many familiar concepts in Groovy. Both languages embrace dynamic typing, first-class functions, and developer productivity. However, Groovy runs on the JVM, giving you type safety when you need it and access to the entire Java ecosystem.
## Similarities to JavaScript
| Feature | JavaScript | Groovy |
|---|---|---|
| Dynamic Typing | let x = 5 |
def x = 5 |
| String Interpolation | Hello ${name} |
"Hello ${name}" |
| Closures/Lambdas | (x) => x * 2 |
{ x -> x * 2 } |
| Optional Parameters | Default parameters | Default parameters |
| Collections | Arrays, Objects | Lists, Maps |
| Truthiness | Falsy values concept | Groovy Truth |
| First-Class Functions | Functions as values | Closures as values |
## Side-by-Side Comparison
Array/List Operations
JavaScript:
const crew = ['Sisko', 'Kira', 'Dax'];
// Filter
const captains = crew.filter(m => m === 'Sisko');
// Map
const lengths = crew.map(m => m.length);
// ForEach
crew.forEach(m => console.log(m));
Groovy:
def crew = ['Sisko', 'Kira', 'Dax']
// Filter
def captains = crew.findAll { it == 'Sisko' }
// Map
def lengths = crew.collect { it.length() }
// Each
crew.each { println it }
Object/Map Literals
JavaScript:
const ship = {
name: 'Defiant',
registry: 'NX-74205',
captain: 'Sisko'
};
console.log(ship.name); // Dot notation
console.log(ship['registry']); // Bracket notation
Groovy:
def ship = [
name: 'Defiant',
registry: 'NX-74205',
captain: 'Sisko'
]
println ship.name // Dot notation
println ship['registry'] // Bracket notation
Functions/Closures
JavaScript:
// Function expression
const greet = function(name) {
return Hello, ${name}!;
};
// Arrow function
const double = x => x * 2;
// Callback
numbers.forEach(n => console.log(n));
Groovy:
// Closure
def greet = { name ->
"Hello, ${name}!"
}
// Single parameter (implicit 'it')
def double = { it * 2 }
// Callback
numbers.each { println it }
## Key Differences from JavaScript
### 1. Class-Based OOP
JavaScript uses prototype-based inheritance (even with ES6 classes). Groovy uses traditional class-based OOP like Java.
JavaScript:
class Starship {
constructor(name, crew) {
this.name = name;
this.crew = crew;
}
launch() {
console.log(${this.name} launching);
}
}
Groovy:
class Starship {
String name
Integer crew
// Constructor auto-generated
def launch() {
println "$name launching"
}
}
### 2. No Hoisting
Unlike JavaScript, Groovy doesn't hoist variable declarations. Variables must be declared before use.
JavaScript (works due to hoisting):
console.log(message); // undefined (hoisted)
var message = "Hello";
Groovy (error):
println message // Error! Variable not declared yet
def message = "Hello"
### 3. Type Coercion is More Explicit
JavaScript's type coercion is often surprising. Groovy's is more predictable.
JavaScript:
"5" - 2 // 3 (string coerced to number)
"5" + 2 // "52" (number coerced to string)
[] + [] // "" (weird)
[] + {} // "[object Object]"
Groovy:
"5" - 2 // Error! No automatic coercion
"5".toInteger() - 2 // 3 (explicit)
"5" + 2 // "52" (string concatenation)
### 4. Static Typing Option
JavaScript (without TypeScript) is always dynamically typed. Groovy lets you choose.
Groovy gives you both:
// Dynamic typing (like JavaScript)
def name = "Sisko"
// Static typing (compile-time checks)
String rank = "Captain"
Integer age = 45
### 5. No undefined - Only null
JavaScript has both null and undefined. Groovy only has null.
JavaScript:
let x; // undefined
let y = null; // null
Groovy:
def x // null
def y = null // null
## Promise/Async Patterns in Groovy
JavaScript developers are used to Promises and async/await. Groovy handles asynchrony differently:
JavaScript:
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
Groovy (using GPars for async):
import groovyx.gpars.GParsPool
// Parallel processing
GParsPool.withPool {
def results = data.collectParallel { item ->
processItem(item)
}
}
In Spaceport, most operations are synchronous since you're handling individual HTTP requests. Complex async patterns are less common.
## The Console Object
JavaScript developers use console.log() everywhere. In Groovy/Spaceport:
JavaScript:
console.log("Debug message");
console.error("Error occurred");
Groovy:
println "Debug message" // Standard output
System.err.println "Error" // Error output
// Or use Spaceport's logging
log.info("Info message")
log.error("Error message")
## Truthy/Falsy Comparison
Both languages have truthiness concepts, but with slight differences:
| Value | JavaScript | Groovy |
|---|---|---|
false |
❌ Falsy | ❌ False |
true |
✅ Truthy | ✅ True |
0 |
❌ Falsy | ❌ False |
1 |
✅ Truthy | ✅ True |
"" |
❌ Falsy | ❌ False |
"text" |
✅ Truthy | ✅ True |
null |
❌ Falsy | ❌ False |
undefined |
❌ Falsy | (doesn't exist) |
[] |
✅ Truthy | ❌ False |
{} |
✅ Truthy | ❌ False |
[1, 2] |
✅ Truthy | ✅ True |
Notice that empty collections are truthy in JavaScript but falsy in Groovy!
# Using Java Libraries from Groovy
One of Groovy's superpowers is seamless access to Java's vast ecosystem.
## Adding Dependencies
In Spaceport, you can use @Grab annotations to pull in Java libraries:
@Grab('org.apache.commons:commons-lang3:3.12.0')
import org.apache.commons.lang3.StringUtils
// Now use any Java library method
def result = StringUtils.reverse("Spaceport")
println result // "tropsecapS"
## Common Java Libraries in Groovy
// JSON processing with Jackson
@Grab('com.fasterxml.jackson.core:jackson-databind:2.15.0')
import com.fasterxml.jackson.databind.ObjectMapper
def mapper = new ObjectMapper()
def json = mapper.writeValueAsString([name: "Defiant", crew: 50])
// HTTP client
@Grab('org.apache.httpcomponents:httpclient:4.5.14')
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.HttpClients
def client = HttpClients.createDefault()
def request = new HttpGet("https://api.example.com/data")
def response = client.execute(request)
// Working with dates
import java.time.LocalDate
import java.time.format.DateTimeFormatter
def today = LocalDate.now()
def formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy")
println today.format(formatter)
# Mission Debrief
You now understand how Groovy relates to the languages you already know:
- ✅ Groovy's relationship to Java and full Java interoperability
- ✅ How Groovy compares to JavaScript's dynamic features
- ✅ Key differences in typing, truthiness, and object models
- ✅ When to use dynamic vs. static typing
- ✅ How to leverage Java libraries from Groovy code
Whether you came from Java or JavaScript, you can now leverage your existing knowledge while taking advantage of Groovy's unique strengths in the Spaceport framework.
- --
Mission 04: Engineering (Metaprogramming)(mission-04)
Mission Briefing: Cadet, you've inspected the "core systems" and understand how Spaceport operates. It's time for your advanced engineering certification. Welcome to the "warp core" itself.
This mission is about Metaprogramming: Groovy's ability to modify its own classes and objects at runtime. This is the "magic" that powers Groovy. It's like having the ability to add new buttons to your "command console" or rewrite a ship's blueprints while it's already in flight.
This is arguably the most powerful—and dangerous—feature in your toolkit. It requires precision, skill, and a deep understanding of the consequences. Master it, and you can solve problems that are "impossible" in other languages.
Objectives: - Learn to modify any class (even Java's) at runtime using ExpandoMetaClass - Understand how to create Builders and Domain-Specific Languages (DSLs) - Recognize AST Transformations and their power
# The "Warp Core": Runtime Metaprogramming
Groovy's ExpandoMetaClass (EMC) allows you to "open up" any class—yours, one from a library, or even java.lang.String—and add new methods, properties, and constructors to it.
This is exactly how Spaceport adds its own Class Enhancements like .kebab() and .money(). They aren't magic; they are just Groovy metaprogramming at work.
## Example: Augmenting a String
Let's say our ship's communication system needs a new "red alert" format (all caps, with an exclamation). We can't rewrite java.lang.String, but we can enhance it.
// This code is typically run once at startup (e.g., in an 'on initialize' Alert)
// 1. We access the 'metaClass' of the String class
String.metaClass.toRedAlert = {
// 2. 'delegate' refers to the String instance ("Priority One")
return delegate.toUpperCase() + "!"
}
// 3. Now, EVERY string in the entire application has this new method
def message = "Priority One"
println message.toRedAlert()
// Output: PRIORITY ONE!
## Adding Properties at Runtime
You can also add properties to classes dynamically:
// Add a property to Integer
Integer.metaClass.isEven = {
delegate % 2 == 0
}
println 4.isEven() // true
println 5.isEven() // false
## Intercepting Method Calls
You can intercept method calls using methodMissing:
class DynamicShip {
def methodMissing(String name, args) {
println "Called non-existent method: $name with args: $args"
return "Method $name executed"
}
}
def ship = new DynamicShip()
ship.launch("warp 9")
// Output: Called non-existent method: launch with args: [warp 9]
## Why is This Useful in Spaceport?
1. Clean Data Models
Add helper methods to your Document classes dynamically:
// Add a permission check to all User documents
UserDocument.metaClass.hasPermission = { perm ->
delegate.permissions?.contains(perm) ?: false
}
// Now you can use it anywhere
if (user.hasPermission('admin')) {
// Show admin panel
}
2. Fixing Libraries
Need a third-party Java library to behave differently? Patch it at runtime without forking:
// Make all Lists have a 'random' method
List.metaClass.random = {
delegate[new Random().nextInt(delegate.size())]
}
def crew = ['Sisko', 'Kira', 'Dax']
println crew.random() // Random crew member
3. Fluent APIs
Create incredibly readable code:
// Add a 'times' method to Integer for Ruby-like syntax
Integer.metaClass.times = { closure ->
for (int i = 0; i < delegate; i++) {
closure(i)
}
}
5.times {
println "Launching torpedo ${it + 1}"
}
// Output:
// Launching torpedo 1
// Launching torpedo 2
// ...
# Builders & DSLs: Your Own "Command Language"
A Domain-Specific Language (DSL) is a "mini-language" you create to solve one specific problem. Instead of writing complex, nested Groovy code, you create a simple, readable syntax.
Groovy's Builder pattern, combined with closures, makes this incredibly easy.
## Example: MarkupBuilder
Instead of mashing strings together, a builder gives you a clean DSL for "describing" the HTML you want.
def writer = new StringWriter()
// MarkupBuilder is a standard Groovy class
def html = new groovy.xml.MarkupBuilder(writer)
// This is a Groovy DSL.
// 'body', 'h1', and 'p' are not real methods...
// The 'builder' intercepts them and turns them into tags.
html.body {
h1("Mission Briefing")
p(class: 'intel', "Warning: Metaprogramming is powerful.")
}
def htmlOutput = writer.toString()
/* htmlOutput is:
<body>
<h1>Mission Briefing</h1>
<p class='intel'>Warning: Metaprogramming is powerful.</p>
</body>
*/
## How Builders Work
Builders use methodMissing to intercept method calls and convert them into structured data:
class SimpleBuilder {
def output = []
def methodMissing(String name, args) {
output << "Called: $name with ${args}"
return this // Return this for chaining
}
}
def builder = new SimpleBuilder()
builder.launch('Defiant').setSpeed('Warp 9').engage()
println builder.output
// [Called: launch with [Defiant], Called: setSpeed with [Warp 9], Called: engage with []]
## Creating a Validation DSL
Here's a practical example: creating a clean DSL for validating Documents in Spaceport.
The "Old" Way (Verbose Code):
def user = Document.get(userId, 'users')
def errors = []
if (user.fields.email == null || !user.fields.email.contains("@")) {
errors.add("Email is invalid")
}
if (user.fields.age < 18) {
errors.add("User must be 18 or older")
}
if (user.fields.password?.length() < 8) {
errors.add("Password must be at least 8 characters")
}
The "DSL" Way (Clean & Readable):
class Validator {
def errors = []
def doc
Validator(doc) {
this.doc = doc
}
def check(String field, Closure... rules) {
def value = doc.fields[field]
rules.each { rule ->
def result = rule(value)
if (result) errors << result
}
}
// Helper rule builders
static Closure isRequired() {
return { value ->
value ? null : "Field is required"
}
}
static Closure isEmail() {
return { value ->
value?.contains("@") ? null : "Must be a valid email"
}
}
static Closure min(int minValue) {
return { value ->
value >= minValue ? null : "Must be at least ${minValue}"
}
}
static Closure minLength(int length) {
return { value ->
value?.length() >= length ? null : "Must be at least ${length} characters"
}
}
static validate(doc, Closure definition) {
def validator = new Validator(doc)
definition.delegate = validator
definition()
return validator.errors
}
}
// Now use the DSL
def errors = Validator.validate(user) {
check 'email', isRequired(), isEmail()
check 'age', isRequired(), min(18)
check 'password', isRequired(), minLength(8)
}
if (errors) {
println "Validation failed:"
errors.each { println " - $it" }
}
This is the hallmark of a Groovy Luminary: Don't just write code; design the language for your problem, then write in that.
# AST Transformations: "Genetic Engineering"
If ExpandoMetaClass is "modifying the ship at runtime," then Abstract Syntax Tree (AST) Transformations are "rewriting the DNA in the blueprints before the ship is even built."
This is the most advanced form of metaprogramming. You write an annotation (like @...), and Groovy will physically rewrite your source code during the compilation phase.
## Common AST Transformations
You use these all the time, perhaps without knowing it:
@Singleton - Groovy rewrites your class to implement the full Singleton pattern:
@Singleton
class Database {
def connection = "db://localhost"
}
// Only one instance exists
def db1 = Database.instance
def db2 = Database.instance
println db1 == db2 // true
@ToString - Groovy writes a toString() method for you:
@ToString
class Officer {
String name
String rank
}
def odo = new Officer(name: "Odo", rank: "Constable")
println odo // Officer(Odo, Constable)
@Immutable - Groovy rewrites your class to be read-only and thread-safe:
@Immutable
class Coordinates {
int x
int y
}
def coord = new Coordinates(10, 20)
// coord.x = 30 // Compile error!
@Delegate - Groovy forwards method calls to another object:
class Engine {
def start() { println "Engine starting" }
def stop() { println "Engine stopping" }
}
class Starship {
@Delegate Engine engine = new Engine()
}
def ship = new Starship()
ship.start() // Calls engine.start()
ship.stop() // Calls engine.stop()
## The Power of AST Transformations
AST Transformations enable the language itself to evolve without changing its core syntax. They're the "Prime Directive" of Groovy.
While we won't be building our own ASTs in this course (that's a topic for post-graduates), it's your duty as an engineer to know they exist and recognize them when you see them.
# Mission Debrief
You have been inside the "warp core" and seen the raw power of Groovy's dynamic nature. You've learned:
- ✅ How to modify any class at runtime with ExpandoMetaClass
- ✅ How to intercept method calls and add properties dynamically
- ✅ How to create clean, readable DSLs using Builders
- ✅ The power of AST Transformations to modify code at compile time
You are not limited by the "rules" of the language—you can change the rules. This is the true power of Groovy, and what makes it perfect for a framework like Spaceport.
- --
Mission 05: The Bridge (Groovy in Launchpad)(mission-05)
Mission Briefing: You've mastered the engine room. Now it's time to take command of the bridge—where Groovy directly controls your user interface through Launchpad.
Groovy isn't just for the backend in Spaceport; it's the command language for your frontend. Through Launchpad templates, you can embed Groovy code directly in your HTML, creating dynamic, reactive user interfaces with the full power of the language at your fingertips.
Objectives: - Understand how Groovy integrates into Launchpad templates - Master server-side rendering with embedded Groovy - Use closures for Server Actions and Transmissions - Build reactive UI components with Cargo
# Groovy in Templates
Launchpad templates (.ghtml files) allow you to embed Groovy code directly alongside your HTML. This is similar to JSP, ERB, or PHP, but with Groovy's elegant syntax.
## Basic Template Syntax
<%
// Scriptlet - Any Groovy code
def crew = ["Sisko", "Kira", "Dax", "Bashir"]
def shipName = "Defiant"
%>
<div>
<h1>Welcome aboard the <%= shipName %></h1>
<ul>
<% crew.each { member -> %>
<li>Commander <%= member %></li>
<% } %>
</ul>
</div>
## Expression Output
Use ${ ... } to output expressions directly into HTML:
<%
def user = [name: "Sisko", rank: "Captain"]
%>
<div>
<h2>${ user.name }</h2>
<p>Rank: ${ user.rank }</p>
<!-- Groovy expressions can include logic -->
<p class="${ user.rank == 'Captain' ? 'command' : 'crew' }">
Access Level: ${ user.rank }
</p>
</div>
## Using Class Enhancements in Templates
This is where Spaceport's Class Enhancements really shine:
<%
def user = context.data.user
def isAdmin = user?.role == 'admin'
%>
<nav>
<a href="/">Home</a>
<!-- Only show admin link if user is admin -->
${ "<a href='/admin'>Admin Panel</a>".if { isAdmin }}
<!-- Only show login if no user -->
${ "<a href='/login'>Login</a>".if { !user }}
</nav>
# Server Actions with Closures
One of Launchpad's most powerful features is Server Actions—the ability to execute Groovy code on the server in response to client events.
## Basic Server Action
<%
def counter = 0
def increment = {
counter++
return "Count: $counter"
}
%>
<div>
<button on-click=${ _{ increment() }} target="#display">
Click Me
</button>
<div id="display">Count: 0</div>
</div>
## Using the Transmission Object
The t parameter (or whichever name you choose) gives you access to client data:
<%
def greet = { t ->
def name = t.getString('username')
return "Hello, ${name.clean()}!"
}
%>
<form on-submit=${ _{ t -> greet(t) }} target="#greeting">
<input name="username" placeholder="Enter your name">
<button type="submit">Greet</button>
</form>
<div id="greeting"></div>
## Returning Transmissions
Server Actions can return Maps to perform multiple operations:
<%
def processForm = { t ->
def name = t.getString('name')
def email = t.getString('email')
// Validate
if (!email.contains('@')) {
return [
'#error': 'Invalid email address',
'+error': 'it',
'disabled': false
]
}
// Success
return [
'#message': "Welcome, ${name.clean()}!",
'+success': 'it',
'disabled': true,
'@hide': '#error'
]
}
%>
<form on-submit=${ _{ t -> processForm(t) }} target="self">
<input name="name" required>
<input name="email" type="email" required>
<button type="submit">Submit</button>
<div id="error" style="display:none"></div>
<div id="message"></div>
</form>
# Working with Cargo
Cargo objects are Groovy Maps with superpowers—they provide reactive state management in Spaceport.
## Basic Cargo Usage in Templates
<%
import spaceport.computer.memory.virtual.Cargo
// Get or create a Cargo object from the store
def cart = Cargo.fromStore('shopping-cart')
def addItem = { t ->
def itemName = t.getString('item')
cart.inc('count')
cart.append('items', itemName.clean())
return [ '@reload' : null ]
}
%>
<div>
<h2>Shopping Cart (${ cart.getInteger('count') ?: 0 } items)</h2>
<form on-submit=${ _{ t -> addItem(t) }}>
<input name="item" placeholder="Item name">
<button>Add Item</button>
</form>
<ul>
<% (cart.getList('items') ?: []).each { item -> %>
<li>${ item }</li>
<% } %>
</ul>
</div>
## Reactive Updates with Cargo
Use the ${{ ... }} syntax for reactive subscriptions:
<%
import spaceport.computer.memory.virtual.Cargo
def counter = Cargo.fromStore('live-counter')
counter.counter.getDefaulted('value', 0)
def increment = {
counter.inc('value')
// No need to return anything - reactive update happens automatically!
}
%>
<div>
<h2>Live Counter</h2>
<!-- This updates automatically when counter changes -->
<div class="display">
Count: ${{ counter.getInteger('value') }}
</div>
<button on-click=${ _{ increment() }}>+1</button>
</div>
When counter changes on the server, Launchpad automatically pushes just the new value to the client and updates only that part of the DOM. No full page reload needed!
# Advanced Template Patterns
## Combining Static and Dynamic Content
<%
import spaceport.computer.memory.virtual.Cargo
def posts = Cargo.fromDocument(blogDoc)
def publishPost = { t ->
def title = t.getString('title')
def content = t.getString('content')
posts.setNext([
title: title.clean(),
content: content.clean(),
timestamp: System.currentTimeMillis()
])
return ['@reload': null]
}
%>
<div class="blog">
<!-- Static header -->
<h1>Deep Space Nine Blog</h1>
<!-- Dynamic post list -->
<div class="posts">
<% posts.values().sort { a, b ->
b.timestamp <=> a.timestamp
}.each { post -> %>
<article>
<h2>${ post.title }</h2>
<p>${ post.content }</p>
<time>${ post.timestamp.date() }</time>
</article>
<% } %>
</div>
<!-- Interactive form -->
<form on-submit=${ _{ t -> publishPost(t) }}>
<input name="title" placeholder="Post title">
<textarea name="content" placeholder="Write your post..."></textarea>
<button>Publish</button>
</form>
</div>
## Using Groovy Helper Methods
Define reusable methods at the top of your template:
<%
// Helper method to format rank with insignia
def formatRank = { rank ->
def insignia = [
'Captain': '✦✦✦✦',
'Commander': '✦✦✦',
'Lieutenant': '✦✦',
'Ensign': '✦'
]
return "${insignia[rank] ?: ''} $rank"
}
def crew = [
[name: 'Sisko', rank: 'Captain'],
[name: 'Kira', rank: 'Commander'],
[name: 'Dax', rank: 'Lieutenant']
]
%>
<div class="crew-roster">
<% crew.each { member -> %>
<div class="officer">
<strong>${ member.name }</strong>
<span class="rank">${ formatRank(member.rank) }</span>
</div>
<% } %>
</div>
# Mission Debrief
You've learned how to command the bridge—using Groovy to create dynamic, interactive user interfaces through Launchpad. You've mastered:
- ✅ Embedding Groovy code in HTML templates
- ✅ Using expressions and scriptlets effectively
- ✅ Creating Server Actions with closures
- ✅ Working with the Transmission object
- ✅ Managing reactive state with Cargo
- ✅ Building complex UI patterns with Groovy
With Groovy powering both your backend logic and frontend templates, you have complete control over your Spaceport application from engine room to bridge.
- --
Final Examination(final-exam)
To earn your Groovy Luminary Certification, you must complete this practical examination. These challenges will test your mastery of Groovy in real-world Spaceport scenarios.
# Challenge 1: Data Transformation
Given this list of starship data:
def fleet = [
[name: "Defiant", registry: "NX-74205", crew: 50],
[name: "Rio Grande", registry: "NCC-72452", crew: 4],
[name: "Rubicon", registry: "NCC-72936", crew: 4]
]
Write Groovy code to: 1. Find all ships with crew larger than 5 2. Create a list of just the ship names 3. Calculate the total crew across all ships 4. Sort ships by crew size (descending) 5. Create a Map with registry numbers as keys and ship names as values
# Challenge 2: Closure Mastery
Create a closure called processReports that:
1. Takes a list of report maps (each with keys status and details)
2. Filters out any reports with status "success"
3. Returns a formatted string of all remaining reports: "Status: [STATUS] - [DETAILS]"
4. Each report should be on its own line
Test it with this data:
def reports = [
[status: "success", details: "Mission complete"],
[status: "warning", details: "Low fuel"],
[status: "error", details: "Engine failure"],
[status: "warning", details: "Hull breach on deck 4"]
]
# Challenge 3: Class Design
Create a CrewMember class with:
- Properties: name, rank, station
- A method report() that returns: "[RANK] [NAME] reporting from [STATION]"
- A static method getAllByRank(rank) that filters a list of crew members
- Use the @ToString annotation
Create at least 3 crew members and demonstrate all functionality.
# Challenge 4: Metaprogramming
Use metaprogramming to:
1. Add a isOfficer() method to the CrewMember class that returns true if rank is "Captain", "Commander", or "Lieutenant"
2. Add a reverse() method to String that returns the string backwards
3. Demonstrate both additions work correctly
# Challenge 5: Spaceport Integration
Create a simple Source Module that:
1. Handles requests to /crew/list
2. Maintains a static list of crew members (use Cargo)
3. Renders the crew list using a Launchpad template
4. Provides a Server Action to add a new crew member
5. Uses Class Enhancements to format the output
Show the complete Source Module code and template code.
- --
Certification Achievement(certification)
Congratulations, Cadet!
Upon completing the course and final examination, you have demonstrated exceptional proficiency in:
- ✅ Groovy syntax fundamentals and advanced features
- ✅ Dynamic and static typing strategies
- ✅ Collections, closures, and functional programming
- ✅ Object-oriented programming in Groovy
- ✅ Understanding of Groovy's relationship to Java and JavaScript
- ✅ Advanced metaprogramming and runtime enhancement
- ✅ Building DSLs and using the Builder pattern
- ✅ Spaceport-specific architecture patterns
- ✅ Source Modules, Alerts, and Class Enhancements
- ✅ Launchpad template integration
- ✅ Reactive UI development with Cargo
- --
You are now certified as a Groovy Luminary.
Your certification code: GL-${new Date().format('yyyyMMdd')}-${(1000..9999).random()}
You have earned the knowledge and skills necessary to build powerful, elegant, and dynamic applications using Groovy in the Spaceport framework. Your training has prepared you to:
- Write concise, expressive code with minimal boilerplate
- Leverage Groovy's dynamic features for rapid development
- Integrate seamlessly with Java libraries and frameworks
- Build sophisticated applications using closures and functional patterns
- Apply metaprogramming to solve complex problems
- Create reactive, server-driven user interfaces
- Design Domain-Specific Languages for your problem domain
- Command both the engineering deck and the bridge of your application
Whether you came from Java, JavaScript, or jumped straight in by building the Tic-Tac-Toe game, you now understand the full power and elegance of Groovy as Spaceport's command language.
Welcome to the fleet, Groovy Luminary. Your mission to explore the possibilities of dynamic JVM programming has just begun.
# Additional Resources
Continue your journey with these recommended resources:
Official Groovy Resources
- Groovy Documentation: groovy-lang.org/documentation.html
- Groovy API Documentation: groovy-lang.org/api.html
- Groovy Style Guide: groovy-lang.org/style-guide.html
Spaceport Documentation
- Developer Onboarding: Developer Onboarding Guide
- Tic-Tac-Toe Tutorial: Your First Application
- Source Modules: Source Modules Documentation
- Launchpad: Launchpad Documentation
- Cargo: Cargo Documentation
- Class Enhancements: Class Enhancements Documentation
- Alerts: Alerts Documentation
- Documents: Documents Documentation
Books
- Groovy in Action (2nd Edition) by Dierk König
- Programming Groovy 2 by Venkat Subramaniam
- Making Java Groovy by Ken Kousen
May your code be elegant, your closures powerful, and your applications ever-dynamic.
Safe travels, Luminary. 🚀
SPACEPORT DOCS