Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
###############################################################################
# Copyright (c) 2018, 2023 IBM Corporation and others.
# Copyright (c) 2018, 2026 IBM Corporation and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License 2.0
# which accompanies this distribution, and is available at
Expand Down Expand Up @@ -30,7 +30,10 @@ cacheSeparator=Cache name separator
cacheSeparator.desc=The single character used to separate the session meta cache name. The default value should usually be used.

appInCacheName=Use the application name in the cache names
appInCacheName.desc=By default, per-application JCache session cache names are generated by using the context root. When the JCache session caches are distributed across multiple servers, multiple applications with the same context root might exist that must not share a session cache. When this option is enabled, application names are included in JCache cache names to help avoid conflicting JCache cache names. The default value is false.
appInCacheName.desc=By default, JCache session cache names are generated by using the application context root. In distributed environments with multiple servers, conflicts can occur when different applications share the context root but require separate session caches. Enabling this option appends the application name to the JCache cache name, preventing such conflicts. The default value is false.

cacheNamePrefix=Cache name prefix
cacheNamePrefix.desc=An optional prefix for Liberty-generated cache names, useful for preventing conflicts when multiple Liberty instances share a cache store. If not specified, the prefix defaults to the standard com.ibm.ws.session value.

properties=JCache Configuration Properties
properties.desc=List of vendor-specific JCache configuration properties, which are passed to the JCache provider when the CacheManager is obtained. This setting is ignored when cacheManagerRef is used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
</AD>
<AD id="writeInterval" type="String" ibm:type="duration(s)" default="2m" ibmui:group="performance" name="%writeInterval" description="%writeInterval.desc"/>
<AD id="cacheSeparator" type="String" required="false" default="%" ibmui:group="performance" name="%cacheSeparator" description="%cacheSeparator.desc"/>
<AD id="cacheNamePrefix" type="String" required="false" default="" ibmui:group="performance" name="%cacheNamePrefix" description="%cacheNamePrefix.desc"/>
<AD id="appInCacheName" type="Boolean" required="false" default="false" ibmui:group="performance" name="%appInCacheName" description="%appInCacheName.desc"/>

<!--
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018, 2024 IBM Corporation and others.
* Copyright (c) 2018, 2026 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
Expand Down Expand Up @@ -78,6 +78,12 @@ public class CacheHashMap extends BackedHashMap {
*/
private static final Pattern COLON = Pattern.compile(":"), PERCENT = Pattern.compile("%"), SLASH = Pattern.compile("/");

/**
* Cache name prefixes for session metadata and attributes
*/
private static final String SESSION_META_CACHE_PREFIX = "com.ibm.ws.session.meta.";
private static final String SESSION_ATTR_CACHE_PREFIX = "com.ibm.ws.session.attr.";

/**
* The end-of-line marker.
*/
Expand Down Expand Up @@ -169,7 +175,9 @@ private void cacheInit() {

// Session Meta Information Cache

String metaCacheName = new StringBuilder(24 + a.length()).append("com.ibm.ws.session.meta.").append(a).toString();
String prefix = cacheStoreService.cacheNamePrefix;
String baseMetaPrefix = prefix.isEmpty() ? SESSION_META_CACHE_PREFIX : prefix + SESSION_META_CACHE_PREFIX;
String metaCacheName = new StringBuilder(baseMetaPrefix.length() + a.length()).append(baseMetaPrefix).append(a).toString();

if (trace && tc.isDebugEnabled())
tcInvoke(cacheStoreService.tcCacheManager, "getCache", metaCacheName, "String", "ArrayList");
Expand Down Expand Up @@ -209,7 +217,8 @@ private void cacheInit() {

// Session Attributes Cache

String attrCacheName = new StringBuilder(24 + a.length()).append("com.ibm.ws.session.attr.").append(a).toString();
String baseAttrPrefix = prefix.isEmpty() ? SESSION_ATTR_CACHE_PREFIX : prefix + SESSION_ATTR_CACHE_PREFIX;
String attrCacheName = new StringBuilder(baseAttrPrefix.length() + a.length()).append(baseAttrPrefix).append(a).toString();

if (trace && tc.isDebugEnabled())
tcInvoke(cacheStoreService.tcCacheManager, "getCache", attrCacheName, "String", "byte[]");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018, 2022 IBM Corporation and others.
* Copyright (c) 2018, 2026 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
Expand Down Expand Up @@ -82,6 +82,7 @@ public class CacheStoreService implements Introspector, SessionStoreService {
private static final String CONFIG_KEY_PROPERTIES = "properties";
private static final Set<String> sessionCacheAttributes = new HashSet<>(Arrays.asList("appInCacheName",
"cacheSeparator",
"cacheNamePrefix",
"scheduleInvalidationFirstHour",
"scheduleInvalidationSecondHour",
"writeContents",
Expand All @@ -103,6 +104,12 @@ public class CacheStoreService implements Introspector, SessionStoreService {

final AtomicReference<ServiceReference<?>> monitorRef = new AtomicReference<ServiceReference<?>>();


/**
* Optional prefix to prepend to Liberty-generated cache names.
* Empty string means no prefix (default behavior for backward compatibility).
*/
volatile String cacheNamePrefix = "";
SerializationService serializationService;
private CacheManagerService cacheManagerService;

Expand Down Expand Up @@ -145,6 +152,8 @@ private void processConfiguration(Map<String, Object> props) {
Object scheduleInvalidationFirstHour = configurationProperties.get("scheduleInvalidationFirstHour");
Object scheduleInvalidationSecondHour = configurationProperties.get("scheduleInvalidationSecondHour");
Object writeContents = configurationProperties.get("writeContents");
Object prefix = configurationProperties.get("cacheNamePrefix");
cacheNamePrefix = (prefix instanceof String) ? (String) prefix : "";
Object writeFrequency = configurationProperties.get("writeFrequency");

// httpSessionCache writeContents accepts ONLY_SET_ATTRIBUTES in place of ONLY_UPDATED_ATTRIBUTES to better reflect the behavior provided
Expand Down Expand Up @@ -482,8 +491,8 @@ public void introspect(PrintWriter out) throws Exception {
// detailed information per cache
for (String cacheName : cacheNames) {
out.println();
boolean isMetaCache = cacheName.startsWith("com.ibm.ws.session.meta.");
boolean isAttrCache = cacheName.startsWith("com.ibm.ws.session.attr.");
boolean isMetaCache = cacheName.contains("com.ibm.ws.session.meta.");
boolean isAttrCache = cacheName.contains("com.ibm.ws.session.attr.");
out.println("Cache " + cacheName + ":");
Cache<?, ?> cache = isMetaCache ? cacheManager.getCache(cacheName, String.class, ArrayList.class)
: isAttrCache ? cacheManager.getCache(cacheName, String.class, byte[].class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
SessionCacheTwoServerTest.class,
SessionCacheTimeoutTest.class,
SessionCacheTwoServerTimeoutTest.class,
SessionCachePrefixTest.class,
HazelcastClientTest.class
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*******************************************************************************
* Copyright (c) 2026 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.ws.session.cache.fat;

import static org.junit.Assert.assertNotNull;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.ibm.websphere.simplicity.log.Log;

import componenttest.annotation.Server;
import componenttest.custom.junit.runner.FATRunner;
import componenttest.custom.junit.runner.RepeatTestFilter;
import componenttest.rules.repeater.RepeatTests;
import componenttest.topology.impl.LibertyServer;
import componenttest.topology.utils.FATServletClient;

/**
* Tests for cacheNamePrefix configuration with Hazelcast.
* This feature allows customers to customize session cache names when multiple
* Liberty instances share the same Hazelcast cluster.
*
* Uses a single server instance with setServerConfigurationFile() to test
* different prefix configurations sequentially.
*/
@RunWith(FATRunner.class)
public class SessionCachePrefixTest extends FATServletClient {

@Server("sessionCachePrefixServer")
public static LibertyServer server;

public static SessionCacheApp app = null;

@ClassRule
public static RepeatTests repeatRule = RepeatTests.withoutModification().andWith(new CacheManagerRepeatAction());

@BeforeClass
public static void setUp() throws Exception {
app = new SessionCacheApp(server, true, "session.cache.web");

String hazelcastConfigFile = "hazelcast-localhost-only-multicastDisabled.xml";
String configLocation = new File(server.getUserDir() + "/shared/resources/hazelcast/" + hazelcastConfigFile).getAbsolutePath();

List<String> jvmOptions = Arrays.asList("-Dhazelcast.group.name=" + UUID.randomUUID(),
"-Dhazelcast.config=" + configLocation);

server.setJvmOptions(jvmOptions);
}

@AfterClass
public static void tearDown() throws Exception {
if (server != null && server.isStarted()) {
server.stopServer("CWWKG0058E", "CWWKO0221E");
}
}

/**
* Test Scenario 1: Custom prefix configured
* Verify that cache names use the custom prefix "testPrefix_" when configured.
*/
@Test
public void testCustomPrefix() throws Exception {
server.setServerConfigurationFile("server_customPrefix.xml");
server.startServer();

try {
List<String> session = new ArrayList<>();
String sessionId = app.sessionPut("testKey", "testValue", session, true);
assertNotNull("Session ID should not be null", sessionId);

// Verify session data can be retrieved
app.sessionGet("testKey", "testValue", session);

// Check logs for custom prefix "testPrefix_"
assertNotNull("Should find custom prefix in cache names",
server.waitForStringInTrace("testPrefix_com\\.ibm\\.ws\\.session\\.(attr|meta)", 30000));

Log.info(SessionCachePrefixTest.class, "testCustomPrefix",
"Successfully verified custom prefix 'testPrefix_' in cache names");
} finally {
server.stopServer("CWWKG0058E", "CWWKO0221E");
}
}

/**
* Test Scenario 2: Empty string prefix
* Verify that an empty string prefix behaves like the default (no prefix).
*/
@Test
public void testEmptyStringPrefix() throws Exception {
server.setServerConfigurationFile("server_emptyPrefix.xml");
server.startServer();

try {
List<String> session = new ArrayList<>();
String sessionId = app.sessionPut("emptyKey", "emptyValue", session, true);
assertNotNull("Session ID should not be null", sessionId);

// Verify session data can be retrieved
app.sessionGet("emptyKey", "emptyValue", session);

// Check logs for standard cache name pattern (empty prefix should behave like default)
assertNotNull("Should find standard cache name pattern in logs",
server.waitForStringInTrace("com\\.ibm\\.ws\\.session\\.(attr|meta)\\.default_host", 30000));

Log.info(SessionCachePrefixTest.class, "testEmptyStringPrefix",
"Successfully verified empty string prefix behaves like default");
} finally {
server.stopServer("CWWKG0058E", "CWWKO0221E");
}
}

/**
* Test Scenario 3: Special characters in prefix
* Verify that special characters (dash, underscore, dot, colon) work in prefix.
*/
@Test
public void testSpecialCharactersInPrefix() throws Exception {
server.setServerConfigurationFile("server_specialPrefix.xml");
server.startServer();

try {
List<String> session = new ArrayList<>();
String sessionId = app.sessionPut("specialKey", "specialValue", session, true);
assertNotNull("Session ID should not be null", sessionId);

// Verify session data can be retrieved
app.sessionGet("specialKey", "specialValue", session);

// Check logs for prefix with special characters: "app-v1.2_prod:"
assertNotNull("Should find prefix with special characters in cache names",
server.waitForStringInTrace("app-v1\\.2_prod:com\\.ibm\\.ws\\.session\\.(attr|meta)", 30000));

Log.info(SessionCachePrefixTest.class, "testSpecialCharactersInPrefix",
"Successfully verified special characters in prefix");
} finally {
server.stopServer("CWWKG0058E", "CWWKO0221E");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!--
Copyright (c) 2026 IBM Corporation and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License 2.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-2.0/

SPDX-License-Identifier: EPL-2.0

Contributors:
IBM Corporation - initial API and implementation
-->
<server>
<featureManager>
<feature>servlet-3.1</feature>
<feature>componenttest-1.0</feature>
<feature>bells-1.0</feature>
<feature>sessionCache-1.0</feature>
</featureManager>

<include location="../fatTestPorts.xml"/>

<httpSession maxInMemorySessionCount="1" allowOverflow="false" hideSessionValues="false"/>

<library id="HazelcastLib">
<file name="${shared.resource.dir}/hazelcast/hazelcast.jar"/>
</library>

<bell libraryRef="HazelcastLib" service="javax.cache.spi.CachingProvider"/>

<!-- Grant Hazelcast library permission to read its own system properties -->
<javaPermission codebase="${shared.resource.dir}/hazelcast/*" className="java.util.PropertyPermission" actions="read" name="hazelcast.*"/>

<httpSessionCache cacheNamePrefix="testPrefix_"/>

<!-- Perms needed because the application uses Hazelcast directly -->
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.io.FilePermission" actions="read" name="${shared.resource.dir}/hazelcast/*"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="accessClassInPackage.sun.management"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="accessClassInPackage.com.sun.management.internal"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="accessClassInPackage.sun.misc"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="accessClassInPackage.sun.nio.ch"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="accessDeclaredMembers"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="enableContextClassLoaderOverride"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="getClassLoader"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="getenv.HZ_PHONE_HOME_ENABLED"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="setContextClassLoader"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.RuntimePermission" name="shutdownHooks"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.lang.reflect.ReflectPermission" name="suppressAccessChecks"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.net.NetPermission" name="getNetworkInformation"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.net.SocketPermission" actions="connect,listen,resolve" name="*"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="java.util.PropertyPermission" actions="read" name="*"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="javax.management.MBeanPermission" actions="queryNames,registerMBean" name="*"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="javax.management.MBeanServerPermission" name="createMBeanServer"/>
<javaPermission codebase="${server.config.dir}/dropins/sessionCacheApp.war" className="javax.management.MBeanTrustPermission" name="register"/>
</server>
Loading