Table of Contents Previous Next
Logo
The Ice Run Time in Detail : 28.11 The Ice::Context Parameter
Copyright © 2003-2008 ZeroC, Inc.

28.11 The Ice::Context Parameter

Methods on a proxy are overloaded with a trailing parameter of type const Ice::Context & (C++), java.util.Map (Java), or Dictionary<string, string> (C#). The Slice definition of this parameter is as follows:
module Ice {
    local dictionary<string, string> Context;
};
As you can see, a context is a dictionary that maps strings to strings or, conceptually, a context is a collection of name–value pairs. The contents of this dictionary (if any) are implicitly marshaled with every request to the server, that is, if the client populates a context with a number of name–value pairs and uses that context for an invocation, the name–value pairs that are sent by the client are available to the server.
On the server side, the operation implementation can access the received Context via the ctx member of the Ice::Current parameter (see Section 28.6) and extract the name–value pairs that were sent by the client.

28.11.1 Passing a Context Explicitly

Contexts provide a means of sending an unlimited number of parameters from client to server without having to mention these parameters in the signature of an operation. For example, consider the following definition:
struct Address {
    // ...
};

interface Person {
    string setAddress(Address a);
    // ...
};
Assuming that the client has a proxy to a Person object, it could do something along the following lines:
PersonPrx p = ...;
Address a = ...;

Ice::Context ctx;
ctx["write policy"] = "immediate";

p>setAddress(a, ctx);
In Java, the same code would looks as follows:
PersonPrx p = ...;
Address a = ...;

java.util.Map ctx = new java.util.HashMap();
ctx.put("write policy", "immediate");

p.setAddress(a, ctx);
In C#, the code is almost identical, except that the context dictionary is type safe:
using System.Collections.Generic;

PersonPrx p = ...;
Address a = ...;

Dictionary<string, string> ctx = new Dictionary<string, string>();
ctx["write policy"] = "immediate";

p.setAddress(a, ctx);
On the server side, we can extract the policy value set by the client to influence how the implementation of setAddress works. A C++ implementation might look like:
void
PersonI::setAddress(const Address& a, const Ice::Current& c)
{
    Ice::Context::const_iterator i = c.ctx.find("write policy");
    if (i != c.ctx.end() && i>second == "immediate") {

        // Update the address details and write through to the 
        // data base immediately...

    } else {
    
        // Write policy was not set (or had a bad value), use
        // some other database write strategy.
    }
}
For this example, the server examines the value of the context with the key "write policy" and, if that value is "immediate", writes the update sent by the client straight away; if the write policy is not set or contains a value that is not recognized, the server presumably applies a more lenient write policy (such as caching the update in memory and writing it later). The Java version of the operation implementation is essentially identical, so we do not show it here.

28.11.2 Passing a Per-Proxy Context

Instead of passing a context explicitly with an invocation, you can also use a per-proxy context. Per-proxy contexts allow you to set a context on a particular proxy once and, thereafter, whenever you use that proxy to invoke an operation, the previously-set context is sent with each invocation. The proxy base class provides a member function, ice_context, to do this:
namespace IceProxy {
    namespace Ice {
        class Object : /* ... */ {
        public:
            Ice::ObjectPrx
                ice_context(const Ice::Context&) const;
            // ...
        };
    }
}
For Java, the corresponding function is:
package Ice;

public interface ObjectPrx {
    ObjectPrx ice_context(java.util.Map newContext);
    // ...
}
For C#, the corresponding method is:
namespace Ice
{
    public interface ObjectPrx
    {
        ObjectPrx ice_context(Dictionary<string, string>
                                  newContext);
        // ...
    }
}
ice_context creates a new proxy that stores the passed context. Note that the return type of ice_context is ObjectPrx. Therefore, before you can use the newly-created proxy, you must down-cast it to the correct type. For example, in C++:
Ice::Context ctx;
ctx["write policy"] = "immediate";

PersonPrx p1 = ...;
PersonPrx p2 = PersonPrx::uncheckedCast(p1>ice_context(ctx));

Address a = ...;

p1>setAddress(a);       // Sends no context

p2>setAddress(a);       // Sends ctx implicitly

Ice::Context ctx2;
ctx2["write policy"] = "delayed";

p2>setAddress(a, ctx2); // Sends ctx2
As the example illustrates, once we have created the p2 proxy, any invocation via p2 implicitly sends the previously-set context. The final line of the example illustrates that it is possible to explicitly send a context for an invocation even if the proxy has an implicit context—an explicit context always overrides any implicit context.
Note that, once you have set a per-proxy context, that context becomes immutable: if you change the context you have passed to ice_context later, that does not affect the per-proxy context of any proxies you previously created with that context because each proxy on which you set a per-proxy context makes a copy of the dictionary and stores that copy.

28.11.3 Retrieving the Per-Proxy Context

You can retrieve the per-proxy context by calling ice_getContext on the proxy. If a proxy has no implicit context, the returned dictionary is empty. For C++, the signature is:
namespace IceProxy {
    namespace Ice {
        class Object : /* ... */ {
        public:
            Ice::Context ice_getContext() const;
            // ...
        };
    }
}
For Java, the signature is:
package Ice;

public interface ObjectPrx {
    java.util.Map ice_getContext();
    // ...
}
For C#, the signature is:
namespace Ice
{
    public interface ObjectPrx
    {
        Dictionary<string, string> ice_getContext();
    }
}

28.11.4 Implicit Contexts

In addition to the explicit and the implicit per-proxy contexts we described in the preceding sections, you can also establish an implicit context on a communicator. This implicit context is sent with all invocations made via proxies created by that communicator, provided that you do not supply an explicit context with the call.
Access to this implicit context is provided by the Communicator interface:
module Ice {
    local interface Communicator
    {
        ImplicitContext getImplicitContext();

        // ...
    };
};
getImplicitContext returns the implicit context. If a communicator has no implicit context, the operation returns a null proxy.
You can manipulate the contents of the implicit context via the ImplicitContext interface:
local interface ImplicitContext
{
    Context getContext();
    void    setContext(Context newContext);

    string get(string key);
    string put(string key, string value);
    string remove(string key);
    bool   containsKey(string key);
};
The getContext operation returns the currently-set context dictionary. The setContext operation replaces the currently-set context in its entirety.
The remaining operations allow you to manipulate specific entries:
• get
This operation returns the value associated with key. If key was not previously set, the operation returns the empty string.
• put
This operation adds the key–value pair specified by key and value. It returns the previous value associated with key; if no value was previously associated with key, it returns the empty string. It is legal to add the empty string as a value.
• remove
This operation removes the key–value pair specified by key. It returns the previously-set value (or the empty string if key was not previously set).
• containsKey
This operation returns true if key is currently set and false, otherwise. You can use this operation to distinguish between a key–value pair that was explicitly added with an empty string as a value, and a key–value pair that was never added at all.

Scope of the Implicit Context

You establish the implicit context on a communicator by setting a property, Ice.ImplicitContext. This property controls whether a communicator has an implicit context and, if so, at what scope the context applies. The property can be set to the following values:
• None
With this setting (or if Ice.ImplicitContext is not set at all), the communicator has no implicit context, and getImplicitContext returns a null proxy.
• Shared
The communicator has a single implicit context that is shared by all threads. Access to the context via its ImplicitContext interface is interlocked, so different threads can concurrently manipulate the context without risking data corruption or reading stale values.
• PerThread
The communicator maintains a separate implicit context for each thread. This allows you to propagate contexts that depend on the sending thread (for example, to send per-thread transaction IDs).

28.11.5 Interactions of Explicit, Per-Proxy, and Implicit Contexts

If you use explicit, per-proxy, and implicit contexts, it is important to be aware of their interactions:
• If you send an explicit context with an invocation, only that context is sent with the call, regardless of whether the proxy has a per-proxy context and whether the communicator has an implicit context.
• If you send an invocation via a proxy that has a per-proxy context, and the communicator also has an implicit context, the contents of the per-proxy and implicit context dictionaries are combined, so the combination of context entries of both contexts is transmitted to the server. If the per-proxy context and the implicit context contain the same key, but with different values, the per-proxy value takes precedence.

28.11.6 Context Use Cases

The purpose of Ice::Context is to permit services to be added to Ice that require some contextual information with every request. Contextual information can be used by services such as a transaction service (to provide the context of a currently established transaction) or a security service (to provide an authorization token to the server). IceStorm (see Chapter 41) uses the context to provide an optional cost parameter to the service that influences how the service propagates messages to down-stream subscribers.
In general, services that require such contextual information can be implemented much more elegantly using contexts because this hides explicit Slice parameters that would otherwise have to be supplied by the application programmer with every call.
In addition, contexts, because they are optional, permit a single Slice definition to apply to implementations that use the context, as well as to implementations that do not use it. In this way, to add transactional semantics to an existing service, you do not need to modify the Slice definitions to add an extra parameter to each operation. (Adding an extra parameter would not only be inconvenient for clients, but would also split the type system into two halves: without contexts, we would need different Slice definitions for transactional and non-transactional implementations of (conceptually) a single service.)
Finally, per-proxy contexts permit context information to be passed by through intermediate parts of your program without cooperation of those intermediate parts. For example, suppose you set a per-proxy context on a proxy and then pass that proxy to another function. When that function uses the proxy to invoke an operation, the per-proxy context will still be sent. In other words, per-proxy contexts allow you to transparently propagate information via intermediaries that are ignorant of the presence of any context.
Keep in mind though that this works only within a single process. If you stringify a proxy or transmit it as a parameter over the wire, the per-proxy context is not preserved. (Ice does not write the per-proxy context into stringified proxies and does not marshal the per-proxy context when a proxy is marshaled.)

28.11.7 A Word of Warning

Contexts are a powerful mechanism for transparent propagation of context information, if used correctly. In particular, you may be tempted to use contexts as a means of versioning an application as it evolves over time. For example, version 2 of your application may accept two parameters on an operation that, in version 1, used to accept only a single parameter. Using contexts, you could supply the second parameter as a name–value pair to the server and avoid changing the Slice definition of the operation in order to maintain backward compatibility.
We strongly urge you to resist any temptation to use contexts in this manner. The strategy is fraught with problems:
• Missing context
There is nothing that would compel a client to actually send a context when the server expects to receive a context: if a client forgets to send a context, the server, somehow, has to make do without it (or throw an exception).
• Missing or incorrect keys
Even if the client does send a context, there is no guarantee that it has set the correct key. (For example, a simple spelling error can cause the client to send a value with the wrong key.)
• Incorrect values
The value of a context is a string, but the application data that is to be sent might be a number, or it might be something more complex, such as a structure with several members. This forces you to encode the value into a string and decode the value again on the server side. Such parsing is tedious and error prone, and far less efficient than sending strongly-typed parameters. In addition, the server has to deal with string values that fail to decode correctly (for example, because of an encoding error made by the client).
None of the preceding problems can arise if you use proper Slice parameters: parameters cannot be accidentally omitted and they are strongly typed, making it much less likely for the client to accidentally send a meaningless value.
If you are concerned about how to evolve an application over time without breaking backward compatibility, Ice facets are better suited to this task (see Chapter 30). Contexts are meant to be used to transmit simple tokens (such as a transaction identifier) for services that cannot be reasonably implemented without them; you should restrict your use of contexts to that purpose and resist any temptation to use contexts for any other purpose.
Finally, be aware that, if a request is routed via one or more Ice routers, contexts may be dropped by intermediate routers if they consider them illegal. This means that, in general, you cannot rely on an arbitrary context value that is created by an application to actually still be present when a request arrives at the server—only those context values that are known to routers and that are considered legitimate are passed on. It follows that you should not abuse contexts to pass things that really should be passed as parameters.
Table of Contents Previous Next
Logo