# Script Body Guide (Groovy Java)

The script acts as a **boolean filter**:

* Return `true` → the event is significant and an alert should be generated
* Return `false` → suppress the alert for this event

***

### 1) Script Body rules (must-follow) <a href="#id-1-script-body-rules-must-follow" id="id-1-script-body-rules-must-follow"></a>

* **Groovy only**
* The Script Body is the **inside of a method** \
  ✅ Write statements directly \
  ❌ Do not add a class or method signature
* **Always return a boolean** on every path
* Prefer **early `return false`** for readability and safety

***

### 2) Runtime variables (available without declaration) <a href="#id-2-runtime-variables-available-without-declaration" id="id-2-runtime-variables-available-without-declaration"></a>

These are injected into the script context automatically:

#### **Core context**

* **`dlpObject` (Object)** Current state of the object that triggered the event.
* **`oldDlpObject` (Object)** Previous state of the object **only for UPDATE** events. Often `null` on INSERT, and may not match expected type.
* **`wsUser` (User)** The user/system account that performed the action. Useful for role-based suppression.

#### **Message output**

* **`mapResultsForMessage` (Map\<String, String>)** Placeholder map for template-based message text. Typical keys are `"?1"`, `"?2"` etc.
* **`mapMessageFromServer` (Map\<String, String>)** Full message override. Use key `"MSG"` to provide the message text.

***

### 3) Recommended structure (copy-paste mental model) <a href="#id-3-recommended-structure-copy-paste-mental-model" id="id-3-recommended-structure-copy-paste-mental-model"></a>

A clean script usually follows this flow:

1. **Imports** (only what you use)
2. **Type check + cast** `dlpObject` (and optionally `oldDlpObject`)
3. **Guard clauses** for nulls / wrong event shape
4. **Business logic** (status checks, threshold checks, role checks, etc.)
5. **Populate message values** (template placeholders or full message)
6. `return true`

***

### 4) Type check + casting (avoid ClassCastException) <a href="#id-4-type-check--casting-avoid-classcastexception" id="id-4-type-check--casting-avoid-classcastexception"></a>

`dlpObject` and `oldDlpObject` are generic `Object`, so type-check first.

```groovy
import com.dataloy.ds.Voyage
if (!(dlpObject instanceof Voyage)) 
    return false Voyage current = (Voyage) dlpObject
```

For old object (updates only):

```groovy
Voyage old = 
    (oldDlpObject instanceof Voyage)
     ? (Voyage) oldDlpObject
     : null
```

***

### 5) Null-Safety: Patterns and Best Practices <a href="#id-5-null-safety-patterns-dont-catch-npes-with-your-face" id="id-5-null-safety-patterns-dont-catch-npes-with-your-face"></a>

Assume linked objects can be null (headers, vessel, statuses, etc.). Use:

* **Guard clauses**
* Groovy **safe navigation** `?.`
* Null coalescing `?:`

```groovy
def vesselName = vessel?.vesselName
// vessel is null, so "Unknown vessel" is returned
def displayName = vesselName ?: "Unknown vessel"

def refNo = current?.voyageHeader?.referenceNo 
if (!refNo) 
    return false
```

***

### 6) Field access and status comparisons

#### Linked object navigation

```groovy
def statusCode = current?.voyageHeader?.voyageStatus?.statusTypeCode
```

#### Status fields

Compare **status codes**, not the status object:&#x20;

```groovy
if (!"OPR".equals(statusCode)) 
    return false
```

{% hint style="info" %}
Tip: `"LITERAL".equals(x)` is null-safe and avoids surprises.
{% endhint %}

***

### 7) Update/change detection (current vs old) <a href="#id-7-update-change-detection-current-vs-old" id="id-7-update-change-detection-current-vs-old"></a>

If your trigger is UPDATE and you only want alerts on change:

#### **Step-by-step pattern**

```groovy
if (old == null) return false // not an update or no previous state
```

#### **Null-safe comparisons**

Avoid calling `.equals()` on potentially null values.

```groovy
def newVal = current?.someField 
def oldVal = old?.someField
if (newVal == null || oldVal == null) return false 
if (newVal == oldVal) return false 
```

#### **Numeric thresholds (example)**

```groovy
import java.math.BigDecimal
BigDecimal newPnl = current?.voyageResult as BigDecimal 
BigDecimal oldPnl = old?.voyageResult as BigDecimal 
if (newPnl == null || oldPnl == null) return false
if ((newPnl - oldPnl).abs() <= 20000G) return false
```

***

### 8) Message building modes (CRITICAL – prevents misconfiguration)

There are **two valid combinations** between the Script Body and Message Configuration.

#### **Mode A — Build message in script = true**

Use this when the Script Body produces the full message text.**Requirements**

* `Build message in script = true`
* Script must set: `mapMessageFromServer["MSG"]`

{% code overflow="wrap" %}

```groovy
mapMessageFromServer.put("MSG", "Voyage PNL changed: " + current?.voyageHeader?.referenceNo) 
return true
```

{% endcode %}

#### **Mode B — Build message in script = false**

Use this when the server builds the message using a template string (`messageTxt`) and placeholder map.

**Requirements**

* `Build message in script = false`
* `messageTxt` must be provided (example: `"Hello ?1"`)
* Script sets: `mapResultsForMessage["?1"] = "World"`

Example configuration:

* `messageTxt = "Hello ?1"`

**Script:**

```groovy
mapResultsForMessage.put("?1", "World")
return true
```

#### **The broken combo**

**Don’t do this:**

* `mapResultsForMessage` + `Build message in script = true`

Reason: `Build message in script = true` expects `"MSG"` in `mapMessageFromServer`.

***

### 9) Guidance for generators / validation logic (recommended) <a href="#id-9-guidance-for-generators-validation-logic-recommended" id="id-9-guidance-for-generators-validation-logic-recommended"></a>

When auto-generating alert script configs, set `Build message in script` using this rule:

1. If Script Body contains `mapMessageFromServer` → set **true**
2. Else if Script Body contains `mapResultsForMessage` AND `messageTxt` is set → set **false**
3. Else → fail validation or require the missing pieces (recommended)

This prevents “alerts firing with empty messages” situations.

***

### 10) Optional: fetching related data (advanced) <a href="#id-10-optional-fetching-related-data-advanced" id="id-10-optional-fetching-related-data-advanced"></a>

Sometimes the needed data is not reachable from `dlpObject` (link not loaded / not present). You can query the database using:

```groovy
com.dataloy.platform.query.DlpObjectSelect
```

{% hint style="warning" %}
Method details can vary by version and are not fully documented. Use known examples as reference and test carefully.
{% endhint %}

***

### 11) Troubleshooting <a href="#id-11-troubleshooting" id="id-11-troubleshooting"></a>

#### **Logging**

Print debug information to application logs:

```groovy
java.lang.System.out.println("---- newPnl: " + newPnl)
```

#### **Return-path sanity**

If alerts don’t fire:

* confirm type-check isn’t rejecting
* confirm `oldDlpObject` isn’t null when you expect update logic
* confirm [message building](https://app.gitbook.com/o/-LhoT2vqihl0pYiCeolt/s/7R7IFboJW2JabvnivFfc/~/edit/~/changes/32/voyage-management-system/step-by-step-guides/alerts/alert-scripts/script-body-groovy-detailed-guide#id-8-message-building-modes-critical-prevents-misconfiguration) is valid

***

### 12) Full example (safe, readable, and template-based) <a href="#id-12-full-example-safe-readable-and-template-based" id="id-12-full-example-safe-readable-and-template-based"></a>

Config:

* `Build message in script = false`
* `messageTxt = "Voyage ?1 PNL changed to ?2"`

Script:

```groovy
import com.dataloy.ds.Voyage 
import java.math.BigDecimal

if (!(dlpObject instanceof Voyage)) return false 
Voyage current = (Voyage) dlpObject

Voyage old = (oldDlpObject instanceof Voyage) ? (Voyage) oldDlpObject : null 
if (old == null) return false

BigDecimal newPnl = current?.voyageResult as BigDecimal 
BigDecimal oldPnl = old?.voyageResult as BigDecimal 
if (newPnl == null || oldPnl == null) return false

if ((newPnl - oldPnl).abs() <= 20000G) return false

mapResultsForMessage.put("?1", current?.voyageHeader?.referenceNo ?: "") 
mapResultsForMessage.put("?2", String.valueOf(newPnl))
return true
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://api.dataloy.com/api-release-8.23/user-guides/enterprise-functionality/alert-scripts/script-body-guide-groovy-java.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
