Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 Greeter][1] demo and does not depend on Glacier2.
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 reference to 'Ice Greeter' demo is incorrect. Based on the context, this should reference the 'Ice Callback' demo since this Glacier2 demo is similar to the Ice Callback demo mentioned at the beginning.

Suggested change
> server provided in the [Ice Greeter][1] demo and does not depend on Glacier2.
> server provided in the [Ice Callback][1] demo and does not depend on Glacier2.

Copilot uses AI. Check for mistakes.

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