Common-scope
common-scope is a client library that helps managing the scope of calls to gCube services. Most noticeably, it offers mechanisms to transparently propagate scope informations across client components, thereby minimising their explicit dependencies on scope.
common-scope
is available in our Maven repositories with the following coordinates:
<groupId>org.gcube.core</groupId> <artifactId>common-scope</artifactId> <version>...</version>
Introduction
Endpoints of gCube services may execute on behalf of different groups of users. Since they may leave different traces for different groups, clients must mark their requests with an indication of the scope in which the endpoints should process them. In turn, the endpoints may call endpoints of other services, and must mark the requests with the scope in which they have been invoked. Overall, there is a general requirement for propagating scope information across chains of service calls, starting from an original client request:
For example, a user logs in the portal to operate in the scope of a given group. He then triggers through the GUI a request for a front-end service (portlet) endpoint, which discovers and calls a back-end service endpoint that operates in the same scope. This service may discover and call in turn another back-end service that operates in that scope, and so on until the original request is satisfied.
The requirement of scope propagation risks to intrude heavily in the design of all clients involved in a call chain. In the worst case, it may require clients to pass scope information across all the local components that are involved in the chain, making them all scope-aware:
common-scope
offers facilities to limit the number of scope-aware components. The idea is that the first client component that becomes aware of the scope of the next outgoing requests places it in a shared context provided by common-scope
, as the current scope. Components that execute thereafter can ignore the existence of a current scope, unless they need to retrieve it from the shared context in order to mark outgoing requests with it. '
Thus, common-scope
requires explicit scope handling only at the boundaries of the client's runtime. Components that operates at such boundaries are scope-aware, and need not know each other: those that set the current scope do so in the assumption that others will later retrieve it; those that retrieve it assume that others will have set it earlier. Components that operate instead inside the runtime's boundaries are instead scope-free, i.e. can ignore the existence of a scope and its requirements.
Scope-aware components can be provided by general-purpose client libraries, and usually are. For example, common-gcore-stubs provides components that set the current scope on outgoing requests made through the Featherweight Stack. Similarly, the gCube Application Framework
(gCF) provides components that take the scope that marks incoming request to gCore services and sets is as the current scope.
Thus many clients remain by and large scope-free. In what follows, we address the smaller subset of scope-aware components, and discuss how common-scope
helps managing scope.
Scope
A scope can be:
- an entire e-Infrastructure. Syntactically, we represent it with a name, e.g.
d4science.research-infrastructures.eu
;
- a Virtual Organisation (VO) within an e-Infrastructure. Syntactically, we represent it with the name of the e-Infrastructure and the name of the VO, separating the two with a forward slash, e.g.
d4science.research-infrastructures.eu/FARM
;
- a Virtual Research Environment (VRE) within a VO. Syntactically, we represent it with the name of the e-Infrastructure of the VO, the name of the VO, and the name of the VRE, e.g.
d4science.research-infrastructures.eu/FARM/AquaMaps
.
Names are agreed upon and, clearly, cannot contain forward slashes.
'Note: the conditions under which clients and services operate in given scopes are independent from scope management requirements, and we will not discuss them here.
In code, common-scope
models scopes as plain String
s, e.g.:
String scope = “a/b/c”;
Scope-aware components that need to analyse scopes can wrap String
s in ScopeBean
s and use their APIs:
import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean.Type; ScopeBean scope = new ScopeBean(“a/b/c”); assert(scope.is(Type.VRE)); assert(scope.type()==Type.VRE); assert(scope.name().equals(“c”)); assert(scope.enclosingScope().name().equals(“b”)); assert(scope.toString().equals(“a/b/c”));
Scope Provider
common-scope
offers a ScopeProvider
as a repository for the current scope. Scope-aware components can set the current scope as follows:
import org.gcube.common.scope.api.ScopeProvider ScopeProvider.instance.set(“a/b/c”);
Similarly, scope-aware components can retrieve the current scopes as follows:
String scope =ScopeProvider.instance.get();
In rare cases, scope-aware components may want to delete the current scope:
ScopeProvider.instance.reset();
Default Scope Provider
ScopeProvider
is an interface. When the constant ScopeProvider.instance
is first accessed, common-scope
looks for an implementation of the interface on the classpath (using Java's ServiceLoder
mechanism). If it finds none, it sets the constant to an instance of a default implementation, DefaultScopeProvider
.
DefaultScopeProvider
binds the current scope to threads, i.e. treats it as thread-local information. In particular, it:
- stores the current scope in a
InheritableThreadLocal
. This means that the current scope propagates transparently to child threads. E..g:
final Scope scope = … ScopeProvider.instance.set(scope); new Thread() { public void run() { assert(scope.equals(ScopeProvider.instance.get()); } }
- retrieves the current scope from the system property
gcube.scope
if none is associated with the current thread or its ancestors. This means that clients that operate in a statically known scope can configure the runtime for it, with no need to designate a scope-aware component to set it.
Scoped Tasks
The DefaultScopeProvider
allows clients to remain scope-free even when they spawn new threads. Many clients, however, do not engage in explicit thread management, but submit Callable
or Runnable
tasks to Java's ExecutorService
s. These services preallocate thread pools for the executions of tasks, where the pooled threads are not necessarily in a parent-child relationship with the threads from which clients submits tasks.
In these cases, the DefaultScopeProvider
cannot transparently propagate the scope across threads, and clients must become scope-aware. common-scope
minimise the awareness with facilities that take care of the lower-level details of scope propagation. In particular, ScopedTasks
offers static bind()
methods to wrap Callable
and Runnable
tasks in scope-aware versions, e.g.:
import org.gcube.common.scope.impl.ScopedTasks; … final Scope scope = … ExecutorService service = ... Runnable task = new Runnable() {…}; task = ScopedTasks.bind(task); ...service.submit(task)...;
Here bind()
returns a Runnable
that:
- memorises the current scope;
- sets it on the pooled thread in which the
ExecutorService
will assign to its execution; - delegates execution to the original
Runnable
; - reset the current scope on the pooled thread.