Using Settings.xml to handle Multiple Mirrors in Maven

The goal of having an internal artifactory is to host library for maven, so everytime we do java build, we dont need to pull the whole libraries from internet. Usally we have something like Nexus or JFrog for this.

But the thing is, sometimes we already have a working application that is running well when pulling libraries from online before, and now we need to change it into pointing into our artifact repository without have to change the maven’s pom.xml configuration.

for example, we have some pom.xml file which is pointing to a specific online repository like the sample below,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.4</version>
		<relativePath /> 
	</parent>
	<groupId>com.something</groupId>
	<artifactId>some-java-app</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>some-java-app</name>
	<description>some java app</description>
	<properties>
		<java.version>17</java.version>
		<tomcat.version>9.0.68</tomcat.version>
	</properties>
	<repositories>
		<repository>
			<id>splunk-releases</id>
			<name>Splunk Releases</name>
			<url>https://splunk.jfrog.io/splunk/ext-releases-local</url>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.spring.io/libs-release</url>
		</repository>
	</repositories>
	...
</project>

We can see that application is connecting to multiple maven repositories, such as Splunk JFrog and Spring Repository, other than the default Maven Central.

For this approach, we can create a custom settings.xml and implement a multiple mirror approach for handling to this problem. The result is looks like below xml,

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <servers>
        <server>
            <id>default</id>
            <username>username</username>
            <password>password</password>
        </server>
        <server>
            <id>splunk-releases</id>
            <username>username</username>
            <password>password</password>
        </server>
		<server>
            <id>spring-releases</id>
            <username>username</username>
            <password>password</password>
        </server>
    </servers>

    <mirrors>
        <mirror>
            <id>default</id>
            <name>Default Repository</name>
            <url>https://my-artifact-repository/default/maven2/</url>
            <mirrorOf>*, !splunk-releases, !spring-releases</mirrorOf>
        </mirror>
        <mirror>
            <id>splunk-releases</id>
            <name>Splunk Local Repository</name>
            <url>https://my-artifact-repository/splunk/maven2/</url>
            <mirrorOf>splunk-releases</mirrorOf>
        </mirror>
		<mirror>
            <id>spring-releases</id>
            <name>Spring Local Repository</name>
            <url>https://my-artifact-repository/spring/maven2/</url>
            <mirrorOf>spring-releases</mirrorOf>
        </mirror>
    </mirrors>
</settings>

As we can see on above, we have multiple mirror of repositories with each pointing to different local artifact repository endpoints. We can run maven build with having this configuration as parameter.

$ mvn clean package -s settings.xml

How to Clear User Cache in Keycloak

Keycloak provides a very convenient method of reducing workload to either database or active directory, and that is by using cache mechanism. But sometimes we want to trigger clearing cache manually, for example when there is some changes into the user data which coming from external applications.

We can do that by going to Keycloak menu, click on Realm Settings, and go to Cache tabs. We can clear cache by clicking on the User Cache Clear button.

But becareful since it will clear the whole user-cache, and not a specific user only.

Where is the Location of Podman’s auth.json

There are times when we want to get the list of credentials that Podman is using, but sometimes it is hard to locate them.
The exact location would be vary depending on our userid, but it is pretty much straight forward.

For this sample im using 1005 as my userid. So the location of my credentials would be like this,

$ vi /run/user/1005/containers/auth.json

the result would be something like this

{
   "auths": {
		"docker.io": {
			 "auth": "xxxx="
		},
		"quay.io": {
			 "auth": "xxxx="
		}
   }
}

Connecting Spring Boot to an Infinispan Cluster

Infinispan, or its supported product which is Red Hat DataGrid, is a very strong in-memory data grid product which offers flexible deployment options and robust capabilities for storing, managing, and processing data. And to maintain high availability and fault tolerance, Infinispan provides a clustering mechanism which have a multiple members.

For this article, im trying to create a cluster which consist of 3 Infinispan instances and all instances are being installed by using docker images. First we need to pull a specific Infinispan image,

$ docker pull infinispan/server:14.0.2.Final

And run 3 different instances of Infinispan,

$ docker run -p 11222:11222 -e USER=admin -e PASS=password \
        --add-host=HOST:192.168.56.1 \ 
        infinispan/server:14.0.2.Final
		
$ docker run -p 11223:11222 -e USER=admin -e PASS=password \
        --add-host=HOST:192.168.56.1 \ 
        infinispan/server:14.0.2.Final		

$ docker run -p 11224:11222 -e USER=admin -e PASS=password \
        --add-host=HOST:192.168.56.1 \ 
        infinispan/server:14.0.2.Final

Next is login to one of Infinispan instances which is located in localhost:11222, and login with credential of “admin” and “password”. A successfully cluster will give this display,

Once every Infinispan instances are started, we can now focus on our Java project. Lets start with pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>spring-boot-and-clustered-infinispan</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <version.infinispan>14.0.7.Final</version.infinispan>
        <version.protostream>4.6.2.Final</version.protostream>
        <version.spring.boot3>3.0.4</version.spring.boot3>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${version.infinispan}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${version.spring.boot3}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- infinispan -->
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot3-starter-remote</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-query</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-remote-query-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan.protostream</groupId>
            <artifactId>protostream-processor</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

And an application.properties, in here we can define how many instances of Infinispan that we are connecting to. For this sample, im putting 3 instances which are each having their own ip.

### server port
server.port=8080
spring.application.name=Spring Boot and Clustered Infinispan

## logging
logging.level.root=INFO
logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss} %magenta([%thread]) %highlight(%-5level) %logger.%M - %msg%n

# infinispan
infinispan.remote.server-list=172.17.0.2:11222;172.17.0.3:11222;172.17.0.4:11222
infinispan.remote.auth-username=admin
infinispan.remote.auth-password=password
infinispan.remote.marshaller=org.infinispan.commons.marshall.ProtoStreamMarshaller

And create several Spring Boot’s Java classes, such as main class, controllers, beans, and configs.

@EnableCaching
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
@RestController
public class IndexController {

    @Autowired
    private RemoteCacheManager cacheManager;

    @GetMapping(path = "/")
    public HashMap index() {
        return new HashMap(){{
            put("hello", "world");
        }};
    }

    @GetMapping(path = "/get-user")
    public User getUsers(@RequestParam String name) {
        return (User) cacheManager.getCache("user-cache").getOrDefault(name, new User());
    }

    @GetMapping(path = "/add-user")
    public User addUsers(@RequestParam String name, @RequestParam Integer age, @RequestParam String address) {
        cacheManager.getCache("user-cache").put(name, new User(name, age, address));
        return (User) cacheManager.getCache("user-cache").getOrDefault(name, new User());
    }

}
public class User implements Serializable {
    private String name;

    private Integer age;

    private String address;

    public User() {
    }

    public User(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @ProtoField(number = 1, required = true)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ProtoField(number = 2)
    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @ProtoField(number = 3)
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
@Configuration
public class InfinispanConfiguration {
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public InfinispanRemoteCacheCustomizer remoteCacheCustomizer() {
        return b -> {
            b.remoteCache("user-cache").marshaller(ProtoStreamMarshaller.class);
        };
    }
}
@Component
public class InfinispanInitializer implements CommandLineRunner {

    @Autowired
    private RemoteCacheManager cacheManager;

    @Override
    public void run(String...args) throws Exception {
        SerializationContext ctx = MarshallerUtil.getSerializationContext(cacheManager);
        RemoteCache<String, String> protoMetadataCache = cacheManager.getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME);

        String msgSchemaFile = null;
        try {
            ProtoSchemaBuilder protoSchemaBuilder = new ProtoSchemaBuilder();
            msgSchemaFile = protoSchemaBuilder.fileName("user.proto").packageName("user").addClass(User.class).build(ctx);
            protoMetadataCache.put("user.proto", msgSchemaFile);
        } catch (Exception e) {
            throw new RuntimeException("Failed to build protobuf definition from 'User class'", e);
        }

        String errors = protoMetadataCache.get(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX);
        if (errors != null) {
            throw new IllegalStateException("Some Protobuf schema files contain errors: " + errors + "\nSchema :\n" + msgSchemaFile);
        }
    }
}

Run the code and try do some curl to add and retrieve data from cache,

$ curl -kv http://localhost:8080/add-user?name=lele&age=14&address=Jogja
{"name":"lele","age":14,"address":"Jogja"} 

$ curl -kv http://localhost:8080/get-user?name=lele
{"name":"lele","age":14,"address":"Jogja"} 

And we can check the content of our cache from our dashboard,

Have fun with Infinispan.

https://github.com/edwin/spring-boot-and-clustered-infinispan

Importing a Custom SPI into Keycloak Operator in Openshift

Keycloak Operator provide a convenient method for uploading a custom SPI into Keycloak instances, and that is by using an extensions inside Keycloak YAML operator.

apiVersion: keycloak.org/v1alpha1
kind: Keycloak
metadata: 
  namespace: my-redhat-sso
  labels:
    app: sso
spec:
  extensions:
    - >-
      https://url/custom-sso-spi-1.0.0.jar
  externalAccess:
    enabled: true
  externalDatabase:
    enabled: true
  instances: 1

Rollout your Keycloak pod, and you can see that Keycloak instance is now having a custom SPI embedded within it.