Introduction to Csar

It was not long ago that the programming landscape was dominated with languages such as C, with its public structures and global variables. Before we fully understood separation of concerns, the idea that an application is more maintainable when divided into independent parts with exclusive purposes, it was not uncommon to see settings in the sky that could be accessed by all and modified on a whim.

Object-oriented programming techniques, with its encapsulation and information hiding, were supposed to change all that. The Java language went so far as to prevent bare variables and methods, requiring every piece of data to be part of some class, thinking that this is all it took to prevent a bad practice. But it wasn't enough. Csar (pronounced /zɑːr/), the Concern Separation Aspect Registrar, is meant to help truly separate concerns in a program by providing a concern registration that compartmentalizes global variables by thread group while still providing transparent lookup. Csar can be found at https://csar.io/.

The Static Problem

We supposedly learned long ago the drawbacks of publicly mutable global data. Why then in Java do we have the static methods Locale.getDefault() and Locale.setDefault(Locale newLocale), with which any piece of code can change the locale for the entire JVM? (What if one wanted to simultaneously serve two users, one in France and the other in China?) If one wants to install a network authentication strategy, why must one call Authenticator.setDefault(Authenticator a) which specifies how authentication will be handled for all Internet connections—even those created from other threads or inside third party libraries? (Why should a thread getting the latest currency rates use the same authenticator as a thread transferring money between bank accounts?)

Why when creating a logger in SLF4J must one use the global LoggerFactory.getLogger(String name) mechanism? (What if each bank account access should be logged in a file specific to the logged-in user, while the currency update code should be logged to a general application log file?) The difficulty is described in Should Logger members of a class be declared as static? and When Static References to Log objects can be used, the latter of which describes it as the static problem:

The real root cause of this problem is that SHARED data (static members on a class) is being shared across supposedly independent applications. If no classes are shared between applications then the problem does not exist. However it appears that (to my personal frustration) container vendors continue to encourage the use of shared classes, and application developers continue to use it.

The static problem is no other than the global variable problem resurfacing in an object-oriented age. It seems like every new framework or subsystem has its global registry in which the developer performs some configuration that affects the entire JVM. If there is only one application running on the JVM, all is fine; but in today's world in many applications or context could be running on the same JVM, it become difficult to compartmentalize these global registries for contexts that need independent configurations.

Containers

The current vogue for addressing this problem is a container, an API that provides another layer of indirection when accessing system resources. Because each application accesses its configuration exclusively via the container API, a container thus isolates the various embedded applications, thereby allowing potentially a different configuration for each. There are IoC containers, servlet containers, and EJB containers, just to name a few. Containers play an important role, but their use in solving the static problem is diminished for two reasons:

Cross-Cutting Concerns

The most visible examples of general-purpose libraries needing access to compartmentalized settings are those addressing cross-cutting concerns or aspects of a program, such as internationalization and logging. Logback for example, an SLF4J logging implementation, attempts to address the relatively difficult problem of providing a separate logging environment for multiple applications running on the same web or EJB container by using what it calls a context selector. The problem is that a Logback context selector depends on JNDI environments provided through specially configured web containers. No mention is made of how one might easily have multiple logging configurations compartmentalized in an environment independent of a web server.

Csar

Csar is named Concern Separation Aspect Registrar because it provides access to some concern (usually cross-cutting) that may configured either globally or locally to some section of the program. Csar acts like a global service locator that provides flexible, transparent local configuration. Like a czar in American politics, Csar governs configuration and access to program concerns.

Csar takes advantage of the once-maligned but now almost forgotten ThreadGroup class. Every thread runs inside some thread group. Csar can turn a thread group into a mini-container, an ever-present registry providing access to a program concern potentially with an independent configuration of others on the JVM. At any time a program, module, or library can access its configured concern by calling Csar.getConcern(MyConcern.class). The returned instance of MyConcern to the caller seems like a global variable, but other callers may receive other instances configured specifically for their separate thread group.

Rincl and Clogr

An unlimited number of configuration types can be coupled with Csar. Two libraries already exist to tackle the problem of compartmentalizing logging and internationalization.

Rincl
Facilitates internationalization by providing access to localization resources via Csar.
Clogr
Simplifies SLF4J logging while providing compartmentalized logging configurations.

As an example the methods Rincl.getLocale(Locale.Category category) and Rincl.setLocale(Locale.Category category, Locale locale) provide access to what appears to be a global locale setting. In reality the locale is restricted to the ResourceI81nConcern for that thread group (which may or may not be shared with other threads and thread groups). This ResourceI81nConcern also allows lookup of a hierarchy of resource definitions, potentially using independent locales for different callers. Without special Csar configuration, these methods will fall back to using the JVM default locale transparently.

You can configure Csar concerns manually, as explained below. Csar also provides a ConcernProvider mechanism whereby any library that uses Csar can provide a list of concerns to be automatically available by default without any special configuration required by the program. Simply by including the io.rincl:rincle-resourcebundle dependency, for example, will cause any call to Csar.getConcern(ResourceI18nConcern.class) to return an implementation that looks up resources stored in resource bundles.

Adding Csar to an Application

To provide a concrete example of how you can add local globals to your own application using Csar, consider that you want to provide some sort of environment class to consumers. If you were to follow the lead of the frameworks mentioned above, you might create an Environment class with static access methods, or even provide a singleton static Environment.INSTANCE which any class on the JVM could access. Csar takes a less invasive approach, orchestrated by the io.csar.Csar class.

1. Include Csar Dependency

pom.xml
<project>
  …
  <dependencies>
    …
    <dependency>
      <groupId>io.csar</groupId>
      <artifactId>csar</artifactId>
      <version>x.x.x</version>
    </dependency>
  </dependencies>
</project>

The first step is to include the appropriate Csar dependency. To support resource bundles, simply add io.csar:csar:x.x.x to your Maven POM. Check the Maven Central Repository for the latest io.csar version.

2. Make a Concern

Creating such an environment in Csar requires simply that your class implement io.csar.Concern; your Environment class is acting as one of your program's concerns, which Csar is helping to keep separate from the others—and from other instances of the same concern. In this example we'll leverage the existing java.util.Properties class to hold our environment properties.

Environment.java
public class Environment extends Properties implements Concern {
  …

3. Set the Default Concern

Setting one or more default concerns via Csar.setDefaultConcerns(Concern... concerns) is a fallback mechanism; it is equivalent to setting a globally accessible instance of a concern. If your application does not use local concerns, any thread asking for a concern will receive the default registered concern of the requested type. Setting the default concern is optional if you configured local concerns for all threads. On the other hand, you application may only use a single default concern if that type of concern does not need to be configured locally for any threads.

Setting a default concern.
final Environment defaultEnvironment = new Environment();
defaultEnvironment.setProperty("test", "default");
Csar.setDefaultConcerns(defaultEnvironment);

4. Request a Concern

Any code at any time may ask Csar for a concern using Csar.getConcern(Class<C> concernType), indicating the desired type of concern. Csar will automatically determine if a local concern has been provided to that thread group. If not, the default registered concern will be retrieved.

Retrieving a concern.
final Environment defaultEnvironment = new Environment();
defaultEnvironment.setProperty("test", "default");
Csar.setDefaultConcerns(defaultEnvironment);
…
final Environment env = Csar.getConcern(Environment.class);
System.out.println(env.getProperty("test")); //prints "default"

5. Use a Local Concern

If you wish to provide a specially configured concern locally to a thread, you can simply use Csar.run(Concern concern, Runnable runnable) and provide the local concern along with a java.lang.Runnable to execute. When any code in the returned java.lang.Thread instance calls Csar.getConcern(Class<C> concernType), it will be given the local concern instance, not the global default concern.

Using a local concern.
final Environment defaultEnvironment = new Environment();
defaultEnvironment.setProperty("test", "default");
Csar.setDefaultConcerns(defaultEnvironment);
…
final Environment localEnvironment = new Environment();
localEnvironment.setProperty("test", "local");
Csar.run(localEnvironment, () -> {
  final Environment env = Csar.getConcern(Environment.class);
  System.out.println(env.getProperty("test")); //prints "local"
});

Csar is a small library with no other dependencies. Include it as a dependency and use Csar.getConcern(Class<C> concernType) wherever you need configuration information, even without libraries that can be used within any container. You won't need to create a global singleton concern for the JVM. Rather your application can simply register a default concern instance or provide default concerns via Csar's concern provider mechanism. Either way your entire application will have access to the default concern you specified.

The moment your application needs various compartmentalized concern instances, ask Csar to run the relevant code in a different thread group, providing the concern instance you have specially configured for that local context. Code in that thread group, that before retrieved a global concern will now have access to the locally configured instance. The requesting code will be none the wiser—after all, when concerns are properly separated, exactly how concerns are configured should be of no concern to the calling code.

By Garret Wilson. Copyright © 2016 GlobalMentor, Inc. All Rights Reserved. Content may not be published or reproduced by any means for any purpose besides Csar education without permission.