Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions cpp/Glacier2/session/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Default cmake build directory
build
44 changes: 44 additions & 0 deletions cpp/Glacier2/session/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.21)

project(glacier2_session CXX)

include(../../cmake/common.cmake)

add_executable(client PokeBox.ice Client.cpp)

slice2cpp_generate(client)
target_link_libraries(client PRIVATE Ice::Ice Ice::Glacier2)
if(WIN32)
add_custom_command(TARGET client POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy -t $<TARGET_FILE_DIR:client>
$<TARGET_RUNTIME_DLLS:client>
$<GENEX_EVAL:$<TARGET_PROPERTY:Ice::Ice,ICE_RUNTIME_DLLS>>
COMMAND_EXPAND_LISTS
)
endif()

add_executable(
server
DefaultPokeSession.cpp
DefaultPokeSession.h
InMemoryPokeStore.cpp
InMemoryPokeStore.h
PokeBox.ice
PokeStore.h
Server.cpp
SessionManager.cpp
SessionManager.h
SharedPokeBox.cpp
SharedPokeBox.h
UserIdResolver.h)

slice2cpp_generate(server)
target_link_libraries(server PRIVATE Ice::Ice Ice::Glacier2)
if(WIN32)
add_custom_command(TARGET server POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy -t $<TARGET_FILE_DIR:server>
$<TARGET_RUNTIME_DLLS:server>
$<GENEX_EVAL:$<TARGET_PROPERTY:Ice::Ice,ICE_RUNTIME_DLLS>>
COMMAND_EXPAND_LISTS
)
endif()
133 changes: 133 additions & 0 deletions cpp/Glacier2/session/Client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) ZeroC, Inc.

#include "../../common/Env.h"
#include "PokeBox.h"

#include <Glacier2/Glacier2.h>
#include <Ice/Ice.h>
#include <array>
#include <cassert>
#include <iostream>
#include <random>

using namespace std;
using namespace CatchThemAll;

// All the Pokemon we know about.
std::array<const char*, 57> allPokemon = {
"Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard", "Squirtle", "Wartortle",
"Blastoise", "Caterpie", "Metapod", "Butterfree", "Weedle", "Kakuna", "Beedrill", "Pidgey",
"Pidgeotto", "Pidgeot", "Rattata", "Raticate", "Spearow", "Fearow", "Ekans", "Arbok",
"Pikachu", "Raichu", "Sandshrew", "Sandslash", "Nidoran♀", "Nidorina", "Nidoqueen", "Nidoran♂",
"Nidorino", "Nidoking", "Clefairy", "Clefable", "Vulpix", "Ninetales", "Jigglypuff", "Wigglytuff",
"Zubat", "Golbat", "Oddish", "Gloom", "Vileplume", "Paras", "Parasect", "Venonat",
"Venomoth", "Diglett", "Dugtrio", "Meowth", "Persian", "Psyduck", "Golduck", "Mankey",
"Primeape"};

int
main(int argc, char* argv[])
{
// Retrieve the user ID for this run.
string userId = argc > 1 ? argv[1] : Env::getUsername();

// Create an Ice communicator. We'll use this communicator to create proxies and manage outgoing connections.
Ice::CommunicatorPtr communicator = Ice::initialize(argc, argv);

// Make sure the communicator is destroyed at the end of this scope.
Ice::CommunicatorHolder communicatorHolder{communicator};

// 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.
Glacier2::RouterPrx router{communicator, "Glacier2/router:tcp -h localhost -p 4063"};

// Set this proxy as the default router for all future proxies created from this communicator.
communicator->setDefaultRouter(router);

// 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.
optional<Glacier2::SessionPrx> session = router->createSession(userId, "password");

// We configured a SessionManager on the Glacier2 router, so session is a non-null PokeSession.
assert(session);
auto pokeSession = Ice::uncheckedCast<PokeSessionPrx>(*session);

// Retrieve the PokeBox proxy from the session.
optional<PokeBoxPrx> pokeBox = pokeSession->getPokeBox();
assert(pokeBox);

size_t currentCount = pokeBox->getInventory().size();
cout << userId << "'s PokeBox contains " << currentCount << " Pokémon." << endl;

// Catch a few Pokémon.
// Initialize random number generators.
std::mt19937 gen{std::random_device{}()};
std::uniform_int_distribution<size_t> addDist{1, 6};
std::uniform_int_distribution<size_t> pokeDist{0, allPokemon.size() - 1};
size_t addCount = addDist(gen);
cout << "Catching " << addCount << " Pokémon... " << endl;
vector<string> newPokemon;
newPokemon.reserve(addCount);
for (size_t i = 0; i < addCount; ++i)
{
newPokemon.emplace_back(allPokemon[pokeDist(gen)]);
}
pokeBox->caught(newPokemon);

// Display the contents of the PokeBox.
PokemonList inventory = pokeBox->getInventory();
cout << userId << "'s PokeBox now holds " << inventory.size() << " Pokémon:" << endl;
for (const string& pokemon : inventory)
{
cout << "\t" << pokemon << endl;
}

if (inventory.size() > 10)
{
cout << "Oh no! All the Pokémon escaped!" << endl;
pokeBox->releaseAll();
}

// Exiting, closing the connection, or calling `destroy(Async)` on the session terminates both PokeSession and the
// internal session state maintained by the Glacier2 router.
cout << "Destroying the session..." << endl;
pokeSession->destroy();

// Verify the proxy no longer works.
try
{
pokeBox->getInventory();
cout << "Error: the PokeBox proxy should not work without a session!" << endl;
}
catch (const Ice::ConnectionLostException&)
{
// We get a ConnectionLostException because the Glacier2 router aborts the request on the (new) connection
// without an associated session.
cout << "The PokeBox proxy is no longer valid, as expected." << endl;
}

// Create a new session. This allows us to reach the PokeBox object again.
cout << "Creating a new session..." << endl;
session = router->createSession(userId, "password");

try
{
// The pokeBox proxy no longer works as it was created with the token of an old session.
pokeBox->getInventory();
cout << "Error: the PokeBox proxy should not work with a new session!" << endl;
}
catch (const Ice::DispatchException& dispatchException)
{
if (dispatchException.replyStatus() == Ice::ReplyStatus::Unauthorized)
{
// See code in SharedPokeBox::getUserId.
cout << "The PokeBox proxy remains unusable, as expected." << endl;
}
else
{
throw;
}
}

return 0;
}
42 changes: 42 additions & 0 deletions cpp/Glacier2/session/DefaultPokeSession.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) ZeroC, Inc.

#include "DefaultPokeSession.h"

#include <iostream>

using namespace Server;
using namespace std;

DefaultPokeSession::DefaultPokeSession(
Ice::ObjectAdapterPtr adapter,
Glacier2::SessionControlPrx sessionControl,
UserIdResolverPtr userIdResolver)
: _adapter(std::move(adapter)),
_sessionControl(std::move(sessionControl)),
_userIdResolver(std::move(userIdResolver))
{
}

void
DefaultPokeSession::destroy(const Ice::Current& current)
{
cout << "Destroying session #" << current.id.name << endl;

// Remove the token from the user ID resolver, since the token is no longer valid.
_userIdResolver->removeToken(current.id.name);

// Remove this servant from the object adapter. A new call to this session object will fail with
// ObjectNotExistException.
_adapter->remove(current.id);

// Destroy the session in the Glacier2 router.
_sessionControl->destroy();
}

optional<CatchThemAll::PokeBoxPrx>
DefaultPokeSession::getPokeBox(const Ice::Current& current)
{
// The session token is the name component of the session identity; we use it for the identity of the PokeBox
// object as well.
return _adapter->createProxy<CatchThemAll::PokeBoxPrx>(Ice::Identity{current.id.name, "PokeBox"});
}
44 changes: 44 additions & 0 deletions cpp/Glacier2/session/DefaultPokeSession.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) ZeroC, Inc.

#ifndef DEFAULT_POKE_SESSION_H
#define DEFAULT_POKE_SESSION_H

#include "PokeBox.h"
#include "UserIdResolver.h"

namespace Server
{
/// DefaultPokeSession is an Ice servant that implements Slice interface PokeSession. We create a
/// DefaultPokeSession for each PokeSession object.
class DefaultPokeSession : public CatchThemAll::PokeSession
{
public:
/// Constructs a DefaultPokeSession servant.
/// @param adapter The object adapter that hosts this servant and the PokeBox objects.
/// @param sessionControl The session control proxy.
/// @param userIdResolver The user ID resolver.
DefaultPokeSession(
Ice::ObjectAdapterPtr adapter,
Glacier2::SessionControlPrx sessionControl,
UserIdResolverPtr userIdResolver);

void destroy(const Ice::Current& current) final;

// Implements the pure virtual function in the base class (CatchThemAll::PokeSession) generated by the Slice
// compiler.
std::optional<CatchThemAll::PokeBoxPrx> getPokeBox(const Ice::Current&) final;

private:
// The object adapter that hosts this servant and the PokeBox objects.
Ice::ObjectAdapterPtr _adapter;

// A proxy to the SessionControl object hosted by the Glacier2 router; this proxy allows us to control the
// Glacier2 session, in particular to destroy it. In this demo, that's the only per-session state maintained by
// DefaultPokeSession.
Glacier2::SessionControlPrx _sessionControl;

UserIdResolverPtr _userIdResolver;
};
}

#endif
25 changes: 25 additions & 0 deletions cpp/Glacier2/session/InMemoryPokeStore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

#include "InMemoryPokeStore.h"

using namespace Server;
using namespace std;

void
InMemoryPokeStore::saveCollection(const string& userId, CatchThemAll::PokemonList pokemon)
{
if (pokemon.empty())
{
_store.erase(userId);
}
else
{
_store[userId] = std::move(pokemon);
}
}

CatchThemAll::PokemonList
InMemoryPokeStore::retrieveCollection(const string& userId) const
{
auto it = _store.find(userId);
return it == _store.end() ? CatchThemAll::PokemonList{} : it->second;
}
25 changes: 25 additions & 0 deletions cpp/Glacier2/session/InMemoryPokeStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) ZeroC, Inc.

#ifndef IN_MEMORY_POKE_STORE_H
#define IN_MEMORY_POKE_STORE_H

#include "PokeStore.h"
#include <map>
#include <string>

namespace Server
{
/// An in-memory implementation of PokeStore.
/// @remark This mock implementation is not thread-safe. A real implementation should support concurrent calls.
class InMemoryPokeStore : public PokeStore
{
public:
void saveCollection(const std::string& userId, CatchThemAll::PokemonList pokemon) final;
[[nodiscard]] CatchThemAll::PokemonList retrieveCollection(const std::string& userId) const final;

private:
std::map<std::string, CatchThemAll::PokemonList> _store;
};
}

#endif
33 changes: 33 additions & 0 deletions cpp/Glacier2/session/PokeBox.ice
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) ZeroC, Inc.

// Include the Glacier2/Session.ice file included in the Glacier2 NuGet package.
#include <Glacier2/Session.ice>

module CatchThemAll
{
/// Represents a list of Pokémon.
sequence<string> PokemonList;

/// Represents the Pokémon collected by a user.
interface PokeBox
{
/// Lists all the Pokémon in this box.
/// @return The list of Pokémon.
PokemonList getInventory();

/// Adds one or more Pokémon to this box.
/// @param pokemon The Pokémon to add.
void caught(PokemonList pokemon);

/// Releases all the Pokémon.
void releaseAll();
}

/// Represents a specialized session for our Pokémon application.
interface PokeSession : Glacier2::Session
{
/// Retrieves the PokeBox proxy associated with this session.
/// @return The PokeBox proxy.
PokeBox* getPokeBox();
}
}
30 changes: 30 additions & 0 deletions cpp/Glacier2/session/PokeStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) ZeroC, Inc.

#ifndef POKE_STORE_H
#define POKE_STORE_H

#include "PokeBox.h"

namespace Server
{
/// Represents a Pokémon storage system.
class PokeStore
{
public:
virtual ~PokeStore() = default;

/// Saves the Pokémon collection for a specific user.
/// @param userId The user ID.
/// @param pokemon The Pokémon collection to save.
virtual void saveCollection(const std::string& userId, CatchThemAll::PokemonList pokemon) = 0;

/// Retrieves the Pokémon collection for a specific user.
/// @param userId The user ID.
/// @returns The saved Pokémon collection, or an empty list if no collection was saved for @p userId.
[[nodiscard]] virtual CatchThemAll::PokemonList retrieveCollection(const std::string& userId) const = 0;
};

using PokeStorePtr = std::shared_ptr<PokeStore>;
}

#endif
Loading