- Infinispan Remote Weather App Tutorial
- Objectives
- Prerequisites
- Weather System Architecture
- Starting Infinispan Server
- Accessing the Infinispan Console
- Getting the Weather Application
- Bootstrap the project
- Use the complete solution
- Establishing Remote Connections
- Implementing Temperature Loader
- Implementing Temperature Monitor
- Implementing Weather Loader
- Implementing Weather Finder
- Testing Infinispan Server
- What’s Next?
Learn how to use Infinispan from the Console and remote Hot Rod clients. This tutorial includes Java applications that use Infinispan capabilities to provide services for a searchable weather monitoring system.
By completing this tutorial, you will learn how to:
-
Run Infinispan Server.
-
Access and use the Infinispan Console.
-
Create Infinispan caches.
-
Read and write data as primitive types and Java objects.
-
Add lifespans to entries so data expires.
-
Deploy client listeners to get event notifications.
-
Search the data store for specific values.
-
Use out-of-the-box testing with Junit 5 for verification.
To complete this tutorial, you need:
Tip
|
Run |
This tutorial builds a Weather System with the following Java applications:
-
TemperatureLoaderApp
-
TemperatureMonitorApp
-
WeatherLoaderApp
-
WeatherFinderApp
The Temperature Loader and Temperature Monitor applications comprise the temperature subsystem, as shown below:
This application loads temperatures for geographic locations and runs every five seconds. Infinispan stores that data in the temperature
cache as follows:
-
Location: Key
String
-
Temperature: Value
Float
The Weather Loader and Weather Finder applications comprise the weather subsystem, as shown below:
This application loads weather information for geographic locations and runs every five seconds. Infinispan stores that data in the weather
cache as follows:
-
Location: Key
String
-
Weather: Value
LocationWeather
(temperature, condition, city, country)
Before you start coding fun stuff, you need to start Infinispan Server. For this tutorial, you need a locally running server instance.
You can do one of the following:
By default, Infinispan Server requires user authentication. This tutorial uses
admin
and secret
credentials but you can use any username and password.
The easiest way to run Infinispan Server locally is to pull the container image.
-
Podman
podman run --net=host -p 11222:11222 -e USER="admin" -e PASS="secret" quay.io/infinispan/server:latest
-
Docker
docker run -it -p 11222:11222 -e USER="admin" -e PASS="password" infinispan/server:latest
Infinispan Server comes as a bare metal distribution that you can run locally.
-
Download the server distribution from Infinispan Downloads and extract it.
-
Open a terminal window in the resulting directory. This is
$ISPN_HOME
. -
Add credentials.
$ ./bin/cli.sh user create admin -p secret
-
Run Infinispan Server.
$ ./bin/server.sh
Open http:https://localhost:11222/ in any browser.
You’ll see the Welcome to Infinispan Server page.
To start using the Infinispan Console, do the following:
-
Select Go to the console.
-
Enter your credentials (
admin
/secret
).
You can create the Weather Application yourself going step by step or you can skip ahead and use the complete solution.
You’ll find the code for each application and placeholder comments for each step in this tutorial on the main
branch.
git clone -b main https://github.com/infinispan/infinispan-server-tutorial.git
If you just want to see the Weather System in action, use the completed example on the solution
branch.
git clone -b solution https://github.com/infinispan/infinispan-server-tutorial.git
Warning
|
Infinispan uses Protostream, a Protobuf serialization Java library; Protobuf schemas are generated in build-time.
You must build the project before running the main classes (even from your editor).
If you experience issues with the tests (Docker, running on Mac…), skip the test suite with the
# Build
mvn clean install -DskipTests=true
# Run the loader
mvn exec:java -Dexec.mainClass=org.infinispan.tutorial.client.temperature.TemperatureLoaderApp
# Run the monitor
mvn exec:java -Dexec.mainClass=org.infinispan.tutorial.client.temperature.TemperatureMonitorApp |
Connect to your locally running Infinispan Server from a Hot Rod Java client.
Open the pom.xml
file for this project and confirm that the following dependencies are available:
-
infinispan-client-hotrod
adds the Java Hot Rod Client. -
infinispan-api
adds the Infinispan new API with the annotations. -
infinispan-query-dsl
adds the Infinispan Search API. -
infinispan-remote-query-client
adds a remote search client.
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-client-hotrod</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-api</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-query-dsl</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-remote-query-client</artifactId>
</dependency>
Update the connect()
method in the DataSourceConnector
class as follows:
ConfigurationBuilder builder = new ConfigurationBuilder(); //(1)
builder.uri("hotrod:https://admin:secret@localhost:11222"); //(2)
builder.clientIntelligence(ClientIntelligence.BASIC); //(3)
remoteCacheManager = new RemoteCacheManager(builder.build()); //(4)
-
Creates a
ConfigurationBuilder
-
HotRod URI connection (server, port and credentials)
-
Uses
BASIC
Hot Rod client intelligence. This is required to use Docker with a Mac. -
Creates a
RemoteCacheManager
with the configuration.
In this section of the tutorial, you implement the Temperature Loader application and learn how to:
-
Create caches from the Console.
-
Read data from the cache.
-
Write data to the cache.
-
Expire entries in the cache.
Update the connect()
method in the DataSourceConnector
class by adding a remoteCache("temperature")
as follows:
builder.remoteCache("temperature").configurationURI(temperatureCacheConfig); (1)
-
Adds a cache named
temperature
that uses the content of the 'temperatureCacheConfig.xml' file.This configuration uses Protobuf encoding for keys and values so that you can operate on data from different clients.
Tip
|
View the configuration in JSON for the cache from the Console once it’s created. |
Implement the getForLocation()
method in the TemperatureLoader
service as follows:
@Override
public Float getForLocation(String location) {
Float temperature = cache.get(location); //(1)
if (temperature == null) {
temperature = fetchTemperature(); //(2)
cache.put(location, temperature); //(3)
}
return temperature;
}
-
Get the value for the
location
key. -
Fetches the value if it does not exist in the cache.
The private
fetchTemperature()
method emulates an external service call that takes 200ms to retrieve the temperature for a geographic location. -
Adds the value to the
temperature
cache.
Run TemperatureLoaderApp
to check that it adds temperature data.
The first time the application runs, it takes about two seconds to load data. Subsequent calls retrieve the temperature from the cache, which increases performance.
You should see messages such as the following:
---- Connect to Infinispan ----
<timestamp> org.infinispan.client.hotrod.RemoteCacheManager actualStart
INFO: ISPN004021: Infinispan version: Infinispan 'Corona Extra' 11.0.1.Final
---- Get or create the 'temperature' cache ----
---- Press any key to quit ----
---- Loading information ----
Rome, Italy - 22.000622
Como, Italy - 21.044369
...
---- Loaded in 1762ms ----
---- Loading information ----
Rome, Italy - 22.000622
Como, Italy - 21.044369
...
---- Loaded in 44ms ----
q
---- Shutdown the client ----
At this point, data in the cache remains the same, even if temperatures at the locations change. You can use expiration to remove data after a period of time so that the Temperature Loader fetches new data for the temperature
cache.
Update the put()
method in the TemperatureLoader
class so data expires after 20 seconds as follows:
cache.put(location, temperature, 20, TimeUnit.SECONDS);
Run the TemperatureLoaderApp
class again. After 20 seconds you should notice that temperature loading performance decreases because the service needs to fetch data again.
In this section of the tutorial, you implement the Temperature Monitor application and learn how to use Infinispan Client Listeners.
These client listeners enable the Temperator Monitor application to display notifications about temperature changes that happen for each location.
At present, client listeners do not include values of keys in receiving events. For this reason, you use the Async API to get the value and display the temperature that corresponds to the key.
Update the TemperatureMonitor
service as follows:
@ClientListener //(1)
public class TemperatureChangesListener {
private String location;
TemperatureChangesListener(String location) {
this.location = location;
}
@ClientCacheEntryCreated //(2)
public void created(ClientCacheEntryCreatedEvent event) {
if(event.getKey().equals(location)) {
cache.getAsync(location) //(3)
.whenComplete((temperature, ex) ->
System.out.printf(">> Location %s Temperature %s", location, temperature));
}
}
}
...
public void monitorLocation(String location) {
System.out.println("---- Start monitoring temperature changes for " + location + " ----\n");
TemperatureChangesListener temperatureChangesListener = new TemperatureChangesListener(location);
cache.addClientListener(temperatureChangesListener); //(4)
}
-
Annotates
TemperatureChangesListener
with@ClientListener
to make it an Infinispan Client Listener. -
Uses the
@ClientCacheEntryCreated
annotation to get notifications every time data is added to thetemperature
cache. -
Filters locations by key and gets values using the async call and then prints the new values.
-
Adds the client listener to the cache.
Tip
|
The preceding example filters events in the listener. However, these events can also be filtered server-side with an event filter. However, you must create the filter and deploy it to Infinispan Server, which is beyond the scope of this tutorial. |
Important
|
Always remove client listeners from caches when you no longer need them. |
Make sure that TemperatureLoaderApp
is running and then run TemperatureMonitorApp
.
You should see a message that displays the current temperature of a location and then get notifications for new temperatures every 20 seconds.
---- Connect to Infinispan ----
<timestamp> org.infinispan.client.hotrod.RemoteCacheManager actualStart
INFO: ISPN004021: Infinispan version: Infinispan 'Corona Extra' 11.0.1.Final
---- Get or create the 'temperature' cache ----
Temperature 14.185611 for Bilbao, Spain
---- Start monitoring temperature changes for Bilbao, Spain ----
---- Press any key to quit ----
>> Location Bilbao, Spain Temperature 7.374308
>> Location Bilbao, Spain Temperature 24.784744
Tip
|
Change the expiration values to get more notifications. Use |
In this section of the tutorial, you implement the Weather Loader application and learn how to:
-
Add complex key/value entries to a cache.
-
Serialize Java objects so they can be transmitted to Infinispan Server.
-
Use Protobuf encoding for searchable data so you perform remote queries from Hot Rod Java clients as well as REST clients and other Hot Rod clients such as C# and Node.js.
Infinispan uses Protostream to serialize data to byte.
-
Add the
@Proto
annotation toLocationWeather
. -
Add indexing annotations.
@Indexed
@Proto
public record LocationWeather(@Basic
float temperature,
@Basic
String condition,
@Keyword(projectable = true, sortable = true, normalizer = "lowercase", indexNullAs = "unnamed", norms = false)
String city,
@Keyword(projectable = true, sortable = true, normalizer = "lowercase", indexNullAs = "unnamed", norms = false)
String country) {
}
To marshall the annotated LocationWeather
class, Infinispan requires a Protobuf schema. You can either provide a Protobuf descriptor file or create a descriptor file from the annotations you added to the POJO.
In LocationWeatherMarshallingContext
, you add the schema to the Protobuf cache in Infinispan and then build a Protobuf using the @AutoProtoSchemaBuilder
method.
@ProtoSchema(
includeClasses = {
LocationWeather.class
},
schemaFileName = "weather.proto",
schemaFilePath = "proto/",
schemaPackageName = "org.infinispan.tutorial.data")
public interface LocationWeatherSchema extends GeneratedSchema {
}
Important
|
Run |
// Retrieve metadata cache
RemoteCache<String, String> metadataCache =
cacheManager.getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); // (1)
GeneratedSchema schema = new LocationWeatherSchemaImpl(); // (2)
// Define the new schema on the server too
metadataCache.put(schema.getProtoFileName(), schema.getProtoFile()); //(3)
-
Retrieves the metadata cache that stores all Protobuf schemas.
-
Use the class generated from the
LocationWeatherSchema
interface to retrieve the schema. -
Adds the schema to the cache.
In this step, you create a weather
cache that can store LocationWeather
objects. First you must initialize the marshalling context in the application and then create the cache, as follows:
As before, configure the weather
cache.
builder.remoteCache("weather").configurationURI(weatherCacheConfig); (1)
-
Adds a cache named
weather
that uses the content of the 'weatherCacheConfig.xml' file.
Unlike the temperature
cache, the weather
cache stores complex Java objects and you will query
the values. For this reason the serialization context needs to be registered on the client
and on Infinispan Server.
= public RemoteCache<String, LocationWeather> getWeatherCache() {
System.out.println("--- Get or Create a queryable weather cache ---");
Objects.requireNonNull(remoteCacheManager);
LocationWeatherMarshallingContext.initSerializationContext(remoteCacheManager); // (1)
return remoteCacheManager.getCache("weather"); // (2)
}
-
Initializes the serialization context.
-
Gets the
weather
cache.
The code that loads data into the weather
cache is located in the org.infinispan.tutorial.services.weather.FullWeatherLoader
. Because this service is similar to the code you implemented for the TemperatureLoader
service, you don’t need to do anything else.
Run WeatherLoaderApp
to check that it loads weather data.
You should see messages that indicate the weather
cache is created and weather information is added for different locations:
---- Connect to Infinispan ----
<timestamp> org.infinispan.client.hotrod.RemoteCacheManager actualStart
INFO: ISPN004021: Infinispan version: Infinispan 'Corona Extra' 11.0.1.Final
LocationWeatherMarshallingContext - initialize the serialization context for LocationWeather class
---- Get or create the 'weather' cache ----
---- Press any key to quit ----
---- Loading information ----
Rome, Italy - LocationWeather{temperature=17.252243, condition='SUNNY', city='Rome', country='Italy'}
Como, Italy - LocationWeather{temperature=24.495003, condition='WINDLESS', city='Como', country='Italy'}
Basel, Switzerland - LocationWeather{temperature=19.795946, condition='WINDLESS', city='Basel', country='Switzerland'}
Bern, Switzerland - LocationWeather{temperature=20.455978, condition='WINDLESS', city='Bern', country='Switzerland'}
...
---- Loaded in 3386ms ----
---- Loading information ----
Rome, Italy - LocationWeather{temperature=17.252243, condition='CLOUDY', city='Rome', country='Italy'}
Como, Italy - LocationWeather{temperature=24.495003, condition='PARTIALLY_COVERED', city='Como', country='Italy'}
...
---- Loaded in 70ms ----
In this section of the tutorial, you learn how to:
-
Create and run FROM queries.
-
Create and run SELECT queries.
-
Perform continuous queries.
Create a FROM query on values in the weather
cache as follows:
public List<LocationWeather> findByCountry(String country) {
// Use Ickle to run the query
Query<LocationWeather> query = weather.query("FROM org.infinispan.tutorial.data.LocationWeather WHERE country = :country"); //(1)
// Set the parameter value
query.setParameter("country", country); //(2)
return query.execute().list(); // (3)
}
-
Creates a FROM query using the Ickle query language. This query finds each
LocationWeather
in a country. -
Sets the
country
parameter. -
Executes the query and returns the list.
Make sure WeatherLoaderApp
is running and then run WeatherFinderApp
.
You should see output such as the following:
---- Get or create the 'weather' cache ----
Spain: [LocationWeather{temperature=6.2846804, condition='CLOUDY',city='Bilbao', country='Spain'},
LocationWeather{temperature=18.044653, condition='SUNNY', city='Madrid', country='Spain'}]
For some queries, you don’t want every field for an object. In this example, you create and run a query that returns only the city
that matches a given weather condition.
public List<String> findByCondition(WeatherCondition condition) {
Query<Object[]> query = createFindLocationWeatherByConditionQuery(condition);
return query.execute().list().stream().map(data -> (String) data[0]).collect(Collectors.toList()); //(3)
}
private Query<Object[]> createFindLocationWeatherByConditionQuery(WeatherCondition condition) {
// Use Ickle to run the query
Query<Object[]> query = weather.query("SELECT city FROM org.infinispan.tutorial.data.LocationWeather WHERE condition = :condition"); // (1)
// Set the parameter value
query.setParameter("condition", condition.name()); //(2)
return query;
}
-
Creates a SELECT query using the Ickle query language. This query finds every
LocationWeather
with a weather condition and returns only the city. -
Sets the
condition
parameter. -
Executes the query, returns the list, and filters the
Object[]
to get theString
results.
Make sure WeatherLoaderApp
is running and then run WeatherFinderApp
.
You should see output such as the following:
SUNNY: [Madrid]
CLOUDY: [Lisbon, Bilbao, Newcastle, Como]
RAINY: [Cluj-Napoca]
PARTIALLY_COVERED: [Toronto, Bern]
HUMID: []
WINDY: []
FOGGY: [Washington, Porto, Rome]
WINDLESS: [London, Raleigh]
DRY: [Ottawa]
WET: [Basel, Bucarest]
Continuous Queries allow applications to register listeners that receive the entries matching a query filter. In this way, applications are continuously notified of changes to the queried data set.
public void findWeatherByConditionContinuously(WeatherCondition condition) {
Query<Object[]> query = createFindLocationWeatherByConditionQuery(condition); //(1)
ContinuousQuery<String, LocationWeather> continuousQuery = weather.continuousQuery(); //(2)
// Create the continuous query listener.
ContinuousQueryListener<String, Object[]> listener = //(3)
new ContinuousQueryListener<>() {
// This method will be executed every time new items that correspond with the query arrive
@Override
public void resultJoining(String key, Object[] data) {
System.out.printf("%s is now %s%n", data[0], condition);
}
};
// And the listener corresponding the query to the continuous query
continuousQuery.addContinuousQueryListener(query, listener); //(4)
}
-
Creates a query that finds all locations with a certain weather condition; for example, 'Sunny'.
-
Creates a continuous query on the
weather
cache. -
Creates a continuous query listener and prints the condition.
-
Matches the query and the listener in the
ContinuousQuery
object
Important
|
Always remove continuous queries when you no longer need them. |
Make sure WeatherLoaderApp
is running and then run WeatherFinderApp
.
You should see output such as the following:
---- Press any key to quit ----
Madrid is now SUNNY
Bilbao is now SUNNY
Toronto is now SUNNY
Newcastle is now SUNNY
Cluj-Napoca is now SUNNY
Porto is now SUNNY
...
Test containers are a great way to run an Infinispan Server and test with a Junit 5 extension.
This section of the tutorial provides an example test that verifies the temperatures loaded in Infinispan Server are correct.
You need Docker for this part of the tutorial.
Open the pom.xml
file for this project and add the infinispan-server-testdriver-junit5
dependency as follows:
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-server-testdriver-junit5</artifactId>
<version>${version.infinispan}</version>
<scope>test</scope>
</dependency>
Note
|
JUnit 4 rules are also available for out-of-the-box testing with Infinispan Server. Check the |
Create a Junit 5 Test and use the InfinispanServerExtension
.
@RegisterExtension
static InfinispanServerExtension infinispanServerExtension = InfinispanServerExtensionBuilder.server(); // (1)
@Test
public void loadLocationTemperature() {
DataSourceConnector dataSourceConnector = new DataSourceConnector(createRemoteCacheManager());
TemperatureLoader temperatureLoader = new TemperatureLoader(dataSourceConnector);
Float temperatureLoaderForLocation = temperatureLoader.getForLocation(WeatherLoader.LOCATIONS[0]);
assertNotNull(temperatureLoaderForLocation);
}
// (2)
private RemoteCacheManager createRemoteCacheManager() {
RemoteCacheManager remoteCacheManager = infinispanServerExtension.hotrod().createRemoteCacheManager();
SerializationContext serCtx = MarshallerUtil.getSerializationContext(remoteCacheManager);
LocationWeatherSchema schema = new LocationWeatherSchemaImpl();
schema.registerSchema(serCtx);
schema.registerMarshallers(serCtx);
return remoteCacheManager;
}
-
Registers the Junit 5 Infinispan Server Extension.
-
Adds a serialization context for the tests.
Congratulations on completing this tutorial!
You should now be well on your way with using the Infinispan Server. Here are some more things to help you keep learning:
Quarkus, Spring Boot, Vert.x and other frameworks are featured in the Infinispan demos.
Visit the Infinispan Operator Guide and learn how to deploy and scale Infinispan on Kubernetes or OpenShift.
Try the Infinispan REST API and check out different Hot Rod clients to use Infinispan with other programming languages.