Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions java/Glacier2/callback/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Gradle generated build files
bin
build
.gradle
57 changes: 57 additions & 0 deletions java/Glacier2/callback/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Glacier2 Callback

This demo shows how to write a client that establishes a session with a Glacier2 router. It also shows how to implement
callbacks in this client.

This demo is similar to the [Ice Callback][1] demo, except all communications go through the Glacier router.

The connection between the client and the Glacier2 router is a "bidir" connection, like in the [Ice Bidir][2] demo:

```mermaid
flowchart LR
c[Client<br>hosts AlarmClock] --bidir connection--> g[Glacier2 router:4063]
g --connection1--> s[Server:4061<br>hosts WakeUpService] --connection2--> g
```

## Ice prerequisites

- Install Glacier2. See [Ice service installation].

## Building the demo

The demo has two Gradle projects, **client** and **server**, both using the [application plugin].

To build the demo, run:

```shell
./gradlew build
```

## Running the demo

Start the Server program in its own terminal:

```shell
./gradlew :server:run --quiet
```

Next, start the Glacier2 router in its own terminal:

```shell
glacier2router --Ice.Config=config.glacier2
```

> [!TIP]
> You can also start the Glacier2 router before the server. The order does not matter: the server is identical to the
> server provided in the [Ice Callback][1] demo and does not depend on Glacier2.

Finally, in a separate terminal, start the client application:

```shell
./gradlew :client:run --quiet
```

[Application plugin]: https://docs.gradle.org/current/userguide/application_plugin.html

[1]: ../callback
[2]: ../bidir
Comment on lines +56 to +57
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link references are inconsistent with their usage in the document. Reference [1] is used for both 'Ice Callback' and 'Ice Greeter', but they point to different demos. The reference on line 46 should use [2] for 'Ice Greeter' or the text should be corrected to reference 'Ice Callback'.

Copilot uses AI. Check for mistakes.
33 changes: 33 additions & 0 deletions java/Glacier2/callback/client/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) ZeroC, Inc.

plugins {
// Apply the application plugin to tell gradle this is a runnable Java application.
id("application")

// Apply the Slice-tools plugin to enable Slice compilation.
id("com.zeroc.slice-tools") version "3.8.+"

// Pull in our local 'convention plugin' to enable linting.
id("zeroc-linting")
}

dependencies {
// Add Ice and Glacier2 as implementation dependencies.
implementation("com.zeroc:ice:3.8.+")
implementation("com.zeroc:glacier2:3.8.+")
implementation(project(":util"))
}

sourceSets {
main {
// Add the AlarmClock.ice file from the parent slice directory to the main source set.
slice {
srcDirs("../slice")
}
}
}

application {
// Specify the main entry point for the application.
mainClass.set("com.example.glacier2.callback.client.Client")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) ZeroC, Inc.

package com.example.glacier2.callback.client;

import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.concurrent.ExecutionException;

import com.example.earlyriser.AlarmClockPrx;
import com.example.earlyriser.WakeUpServicePrx;
import com.example.util.Time;
import com.zeroc.Glacier2.CannotCreateSessionException;
import com.zeroc.Glacier2.PermissionDeniedException;
import com.zeroc.Glacier2.RouterPrx;
import com.zeroc.Glacier2.SessionPrx;
import com.zeroc.Ice.Communicator;
import com.zeroc.Ice.Identity;
import com.zeroc.Ice.ObjectAdapter;
import com.zeroc.Ice.Util;

class Client {
public static void main(String[] args) {
// Create an Ice communicator. We'll use this communicator to create proxies and manage outgoing connections.
try (Communicator communicator = Util.initialize(args)) {

// Create a proxy to the Glacier2 router. The addressing information (transport, host and port number) is
// derived from the value of Glacier2.Client.Endpoints in the glacier2 router configuration file.
RouterPrx router = RouterPrx.createProxy(communicator, "Glacier2/router:tcp -h localhost -p 4063");

// Create a session with the Glacier2 router. In this demo, the Glacier2 router is configured to accept any
// username/password combination. This call establishes a network connection to the Glacier2 router; the
// lifetime of the session is the same as the lifetime of the connection.
SessionPrx session;
try {
session = router.createSession(System.getProperty("user.name"), "password");
} catch (PermissionDeniedException | CannotCreateSessionException e) {
System.out.println("Could not create session: " + e.getMessage());
return;
}

// The proxy returned by createSession is null because we did not configure a SessionManager on the Glacier2
// router.
assert session == null;

// Obtain a category string from the router. We need to use this category for the identity of
// server->client callbacks invoked through the Glacier2 router.
String clientCategory = router.getCategoryForClient();

// Create an object adapter with no name and no configuration, but with our router proxy. This object
// adapter is a "bidirectional" object adapter, like the one created by the Ice/Bidir client application.
// It does not listen on any port and it does not need to be activated.
ObjectAdapter adapter = communicator.createObjectAdapterWithRouter("", router);

// Register the MockAlarmClock servant with the adapter. It uses the category retrieved from the router.
// You can verify the Ring callback is never delivered if you provide a different category.
var mockAlarmClock = new MockAlarmClock();
AlarmClockPrx alarmClock = AlarmClockPrx.uncheckedCast(
adapter.add(mockAlarmClock, new Identity("alarmClock", clientCategory)));

// Create a proxy to the wake-up service behind the Glacier2 router. Typically, the client cannot connect
// directly to this server because it's on an unreachable network.
WakeUpServicePrx wakeUpService = WakeUpServicePrx.createProxy(
communicator,
"wakeUpService:tcp -h localhost -p 4061");

// Configure the proxy to route requests using the Glacier2 router.
wakeUpService = WakeUpServicePrx.uncheckedCast(wakeUpService.ice_router(router));

// Schedule a wake-up call in 5 seconds. This call establishes the connection to the server; incoming
// requests over this connection are handled by the communicator's default object adapter.
wakeUpService.wakeMeUp(alarmClock, Time.toTimeStamp(ZonedDateTime.now(ZoneOffset.UTC).plusSeconds(5)));
System.out.println("Wake-up call scheduled, falling asleep...");
try {
// Wait until the user presses the stop button on the alarm clock.
mockAlarmClock.stopPressed().get();
System.out.println("Stop button pressed, exiting...");
} catch (InterruptedException | ExecutionException ex) {
System.err.println(ex);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra blank line

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) ZeroC, Inc.

package com.example.glacier2.callback.client;

import java.util.concurrent.CompletableFuture;

import com.example.earlyriser.AlarmClock;
import com.example.earlyriser.ButtonPressed;
import com.zeroc.Ice.Current;

/**
* MockAlarmClock is an Ice servant that implements Slice interface AlarmClock.
*/
class MockAlarmClock implements AlarmClock {
private boolean _needMoreTime = true;

private final CompletableFuture<Void> _stopPressed;

public MockAlarmClock() {
_stopPressed = new CompletableFuture<>();
}

public CompletableFuture<Void> stopPressed() {
return _stopPressed;
}

// Implements the method ring from the AlarmClock interface generated by the Slice compiler.
@Override
public ButtonPressed ring(String message, Current current) {
System.out.println("Dispatching ring request { message = '" + message + "' }");
if (_needMoreTime) {
System.out.println("Returning " + ButtonPressed.Snooze + " to request more time.");
_needMoreTime = false; // we only snooze one time
return ButtonPressed.Snooze;
} else {
_stopPressed.complete(null);
System.out.println("Returning " + ButtonPressed.Stop + " to stop the alarm.");
return ButtonPressed.Stop;
}
}
}
18 changes: 18 additions & 0 deletions java/Glacier2/callback/config.glacier2
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Config file for glacier2router

# The client-visible endpoint of the Glacier2 router.
Glacier2.Client.Endpoints=tcp -p 4063

# The server-visible endpoint of the Glacier2 router.
# You need to configure this endpoint only when your servers make callbacks (call objects implemented by the clients).
# If you don't use callbacks, don't set this property.
Glacier2.Server.Endpoints=tcp -h 127.0.0.1

# This Glacier router accepts any username/password combination.
Glacier2.PermissionsVerifier=Glacier2/NullPermissionsVerifier

# Turn on tracing.
Glacier2.Client.Trace.Request=1
Glacier2.Server.Trace.Request=1
Glacier2.Trace.Session=1
Glacier2.Trace.RoutingTable=1
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading