Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
IPokeStore.h
IUserIdResolver.h
PokeBox.ice
Server.cpp
SessionManager.cpp
SessionManager.h
SharedPokeBox.cpp
SharedPokeBox.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,
IUserIdResolverPtr userIdResolver)
: _adapter(std::move(adapter)),
_sessionControl(std::move(sessionControl)),
_userIdResolver(std::move(userIdResolver))
{
}

void
DefaultPokeSession::destroy(const Ice::Current& current)
{
std::cout << "Destroying session #" << current.id.name << std::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 "IUserIdResolver.h"
#include "PokeBox.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,
IUserIdResolverPtr 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;

IUserIdResolverPtr _userIdResolver;
};
}

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

#ifndef IPOKE_STORE_H
#define IPOKE_STORE_H

#include "PokeBox.h"
#include <list>
#include <memory>
#include <string>

namespace Server
{
/// Represents a Pokémon storage system.
class IPokeStore
{
public:
virtual ~IPokeStore() = 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.
virtual CatchThemAll::PokemonList retrieveCollection(const std::string& userId) const = 0;
};

using IPokeStorePtr = std::shared_ptr<IPokeStore>;
}
#endif
28 changes: 28 additions & 0 deletions cpp/Glacier2/session/IUserIdResolver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) ZeroC, Inc.

#ifndef IUSER_ID_RESOLVER_H
#define IUSER_ID_RESOLVER_H

#include <memory>
#include <optional>
#include <string>

namespace Server
{
/// Resolves a user ID from a session token.
class IUserIdResolver
{
public:
/// Gets the user ID associated with the specified session token.
/// @param token The session token.
/// @returns The user ID associated with the specified session token, or null if not found.
virtual std::optional<std::string> getUserId(const std::string& token) = 0;

/// Removes the specified session token.
/// @param token The session token.
virtual void removeToken(const std::string& token) = 0;
};

using IUserIdResolverPtr = std::shared_ptr<IUserIdResolver>;
}
#endif
18 changes: 18 additions & 0 deletions cpp/Glacier2/session/InMemoryPokeStore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

#include "InMemoryPokeStore.h"

using namespace Server;
using namespace std;

void
InMemoryPokeStore::saveCollection(const string& userId, CatchThemAll::PokemonList pokemon)
{
_store[userId] = std::move(pokemon);

Choose a reason for hiding this comment

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

This implementation seems different than the other languages.
In the others passing an empty list causes us to remove the entry, whereas here we never remove entries.

I don't think this is 'wrong', just different from the others.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated to match others.

}

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

#include "IPokeStore.h"
#include <list>
#include <map>
#include <string>

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

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