«Top»

The Map-based reference implementation
of our example application does not implement any housekeeping, which
will eventually lead to OutOfMemory errors as the
Map
grows.

This part shows how to replace the ConcurrentMap with a Cache and utilize the
Cache’s eviction to limit the amount of memory used.

The following describes the Infinispan example.

Initialization

In the implementation below, Infinispan’s
DefaultCacheManager
is used to obtain a Cache
instance. The Infinispan cache extends ConcurrentMap.

This corresponds one-to-one to the architecture described in the
JSR 107 overview.

@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
    try {
        DefaultCacheManager cacheManager = new DefaultCacheManager("infinispan.xml");
        ConcurrentMap<String, UserEventList> map = cacheManager.getCache();
        ServletContext context = servletContextEvent.getServletContext();
        context.setAttribute(CACHE, map);
        context.setAttribute(CACHE_MANAGER, cacheManager);
    } catch (IOException e) {
        throw new WebApplicationException(e);
    }
}

The initialization code above reads Infinispan’s configuration from
an XML file called inifnispan.xml, which is loaded via the classpath.
The contents of infinispan.xml is as follows:

<infinispan xsi:schemaLocation="urn:infinispan:config:5.2 infinispan-config-5.2.xsd"
            xmlns="urn:infinispan:config:5.2"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <default>
        <eviction maxEntries="1000" strategy="LRU"/>
    </default>
</infinispan>

The REST Interface loads the map from the
ServletContext:

// ...
@Context
private ServletContext context;
private ConcurrentMap<String, UserEventList> map;

@PostConstruct
@SuppressWarnings("unchecked")
public void init() {
    map = (ConcurrentMap) context.getAttribute(CACHE);
}

Shutdown

As with Ehcache and
Hazelcast, the cache
is shut down using the cache manager.

@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
    ServletContext context = servletContextEvent.getServletContext();
    DefaultCacheManager cacheManager = (DefaultCacheManager) context.getAttribute(CACHE_MANAGER);
    cacheManager.stop();
}

Write

As Infinispan’s Cache
implements ConcurrentMap, the implementation of the REST interface can be
taken one-to-one from the Map-based reference implementation:

@POST
@Path("{user}")
@Consumes(MediaType.APPLICATION_JSON)
public void appendEvent(@PathParam("user") String user, String msg) {
    boolean success;
    map.putIfAbsent(user, UserEventList.emptyList());
    do {
        UserEventList oldMsgList = map.get(user);
        UserEventList newMsgList = UserEventList.append(oldMsgList, msg);
        success = map.replace(user, oldMsgList, newMsgList);
    }
    while ( ! success );
}

Read

Like the write method, the read method is one-to-one the same as in the
Map-based implementation:

@GET
@Path("{user}")
@Produces(MediaType.APPLICATION_JSON)
public List<String> searchByUser(@PathParam("user") String user) {
    UserEventList result = map.get(user);
    if ( result == null ) {
        return new ArrayList<>();
    }
    return result.getMessages();
}

Dependencies

<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-core</artifactId>
    <version>5.2.5.Final</version>
</dependency>

How To Run

Our example code is hosted on GitHub.
The example can be run with maven:

mvn tomcat7:run-war -pl part02.infinispan -am verify

The Web interface is then accessible via
http://localhost:8080.

Next

This was the last page of part 02 of our series, which showed how the
ConcurrentMap in our example can be replaced with a local cache.

Part 03 and part 04 will show how the cache can be clustered and shared among
multiple application servers:

Monitoring-Workshop 2017 12./13.9. Düsseldorf