/******************************************************************************* * Copyright (c) 2015, 2016 Oracle and/or its affiliates. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * 05/11/2015-2.7 Tomas Kraus * - Initial API and implementation. ******************************************************************************/ package org.eclipse.persistence.testing.tests.jpa; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.persistence.spi.PersistenceUnitInfo; import junit.framework.Test; import junit.framework.TestSuite; import org.eclipse.persistence.internal.databaseaccess.Accessor; import org.eclipse.persistence.internal.helper.JPAClassLoaderHolder; import org.eclipse.persistence.internal.jpa.IsolatedHashMap; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.platform.server.ServerPlatform; import org.eclipse.persistence.platform.server.ServerPlatformUtils; import org.eclipse.persistence.sessions.DatabaseSession; import org.eclipse.persistence.sessions.factories.SessionManager; import org.eclipse.persistence.testing.framework.ReflectionHelper; import org.eclipse.persistence.testing.framework.junit.JUnitTestCase; /** * Tests partition isolated {@link HashMap}. */ public class IsolatedHashMapTest extends JUnitTestCase { /** * {@link ServerPlatform} mocking class. Allows changing current partition ID * during the test. */ private static class TestServerPlatform implements ServerPlatform { /** Current partition ID. */ private String partitionId; /** * Created an instance of {@code ServerPlatform} mocking class. * Current partition ID is set to default value. */ private TestServerPlatform() { partitionId = DEFAULT_PARTITION_ID; } /** * Check whether this platform uses partitions. * @return Always returns {@code true}. */ @Override public boolean usesPartitions() { return true; } /** * Get current partition ID. * @return Current partition ID. */ @Override public String getPartitionID() { return partitionId; } /** * Set partition ID to be used as current partition context. * @param id New partition ID. */ private void setPartitionID(final String id) { partitionId = id; } @Override public DatabaseSession getDatabaseSession() { throw new UnsupportedOperationException("Not implemented"); } @Override public String getServerNameAndVersion() { throw new UnsupportedOperationException("Not implemented"); } @Override public String getModuleName() { throw new UnsupportedOperationException("Not implemented"); } @Override public Class getExternalTransactionControllerClass() { throw new UnsupportedOperationException("Not implemented"); } @Override public void setExternalTransactionControllerClass(final Class newClass) { throw new UnsupportedOperationException("Not implemented"); } @Override public void initializeExternalTransactionController() { throw new UnsupportedOperationException("Not implemented"); } @Override public boolean isJTAEnabled() { throw new UnsupportedOperationException("Not implemented"); } @Override public boolean isRuntimeServicesEnabledDefault() { throw new UnsupportedOperationException("Not implemented"); } @Override public void disableJTA() { throw new UnsupportedOperationException("Not implemented"); } @Override public boolean isRuntimeServicesEnabled() { throw new UnsupportedOperationException("Not implemented"); } @Override public void disableRuntimeServices() { throw new UnsupportedOperationException("Not implemented"); } @Override public void registerMBean() { throw new UnsupportedOperationException("Not implemented"); } @Override public void unregisterMBean() { throw new UnsupportedOperationException("Not implemented"); } @Override public void shutdown() { throw new UnsupportedOperationException("Not implemented"); } @Override public int getThreadPoolSize() { throw new UnsupportedOperationException("Not implemented"); } @Override public void setThreadPoolSize(final int threadPoolSize) { throw new UnsupportedOperationException("Not implemented"); } @Override public Connection unwrapConnection(final Connection connection) { throw new UnsupportedOperationException("Not implemented"); } @Override public void launchContainerRunnable(final Runnable runnable) { throw new UnsupportedOperationException("Not implemented"); } @Override public SessionLog getServerLog() { throw new UnsupportedOperationException("Not implemented"); } @Override public boolean shouldUseDriverManager() { throw new UnsupportedOperationException("Not implemented"); } @Override public boolean wasFailureCommunicationBased( final SQLException exception, final Accessor connection, final AbstractSession sessionForProfile) { throw new UnsupportedOperationException("Not implemented"); } @Override public JPAClassLoaderHolder getNewTempClassLoader(final PersistenceUnitInfo puInfo) { throw new UnsupportedOperationException("Not implemented"); } @Override public void clearStatementCache(Connection connection) { throw new UnsupportedOperationException("Not implemented"); } @Override public int getJNDIConnectorLookupType() { throw new UnsupportedOperationException("Not implemented"); } } /** Session name and partition ID separator. */ private static final char SEPARATOR = '$'; /** Default short enough partition ID when server does not support partitions.*/ private static final String DEFAULT_PARTITION_ID = "0"; /** Tested map shall be stared trough whole JVM to run test against the same map for all configured partitions. */ private static final Map<String, String> map = IsolatedHashMap.newMap(); /** Detected server platform. */ private static final ServerPlatform serverPlatform; /** Does platform support partitions? */ private static final boolean supportPartitions; /** Session name templates used to generate session names in the tests. */ private static final String[] sessionNameTemplates = { "", "0", "mySession", "EclipseLink" }; /** Class initialization code. */ static { serverPlatform = ServerPlatformUtils.createServerPlatform( null, ServerPlatformUtils.detectServerPlatform(null), SessionManager.class.getClassLoader()); // False value also handles case when serverPlatform is null to avoid NPE. supportPartitions = serverPlatform != null ? serverPlatform.usesPartitions() : false; } /** * Creates jUnit test suite for partition isolated {@link HashMap} tests. * @return new jUnit test suite containing all the tests. */ public static Test suite() { TestSuite suite = new TestSuite(); suite.setName("IsolatedHashMapTest"); suite.addTest(new IsolatedHashMapTest("testIsolationInRealContext")); suite.addTest(new IsolatedHashMapTest("testIsolationInMockedContext")); return suite; } /** * Initialize session names in current partition context. * Test initialization helper. * @param partitionId Current partition ID. Shall not be {@code null}. */ private static String[] initSessionNames(final String partitionId) { final int count = sessionNameTemplates.length; final String[] sessionNames = new String[count]; int maxLength = 0; // Calculate StringBuilder internal storage size to avoid resizing. for (int i = 0; i < count; i++) { final int sessionNameTemplateLength = sessionNameTemplates[i].length(); if (sessionNameTemplateLength > maxLength) { maxLength = sessionNameTemplateLength; } } maxLength += 1 + partitionId.length(); final StringBuilder sb = new StringBuilder(maxLength); for (int i = 0; i < count; i++) { if (i > 0) { sb.delete(0, sb.length() - 1); } sb.append(sessionNameTemplates[i]); sb.append(SEPARATOR); sb.append(partitionId); sessionNames[i] = sb.toString(); } return sessionNames; } /** * Creates an instance of partition isolated {@link HashMap} test. */ public IsolatedHashMapTest() { super(); } /** * Creates an instance of partition isolated {@link HashMap} test. * @param name jUnit test name. */ public IsolatedHashMapTest(final String name) { super(name); } /** * Do partition isolation check in current partition context. * @param partitionId Current partition ID that should be visible in {@code map}. */ private static void doIsolationCheck(final Map<String, String> map, final String partitionId, final SessionLog log) { final String[] sessionNames = initSessionNames(partitionId); final Set<String> namesSet = new HashSet<>(sessionNames.length); log.log(SessionLog.INFO, " Partition ID: " + partitionId); for (String sessionName : sessionNames) { log.log(SessionLog.INFO, " Adding session name: " + sessionName); map.put(sessionName, sessionName); namesSet.add(sessionName); } // Map shall contain session names in current partition context only. final int mapSize = map.size(); log.log(SessionLog.INFO, " Checking map size: session names count = " + sessionNames.length + ", map size = " + mapSize ); assertEquals("Map size does not match session names count.", sessionNames.length, mapSize); // Verify individual session names. for (String sessionName : map.keySet()) { log.log(SessionLog.INFO, " Getting and checking session name: " + sessionName); assertTrue("Session name " + sessionName + "was not stored in current partition context.", namesSet.contains(sessionName)); } } /** * Test partition isolation in real context with real {@link ServerPlatform}. */ public void testIsolationInRealContext() { final SessionLog log = getServerSession().getSessionLog(); log.log(SessionLog.INFO, "IsolatedHashMapTest.testIsolationInRealContext()"); doIsolationCheck(map, supportPartitions ? serverPlatform.getPartitionID() : DEFAULT_PARTITION_ID, log); } /** * Test partition isolation in mocked partition context. */ public void testIsolationInMockedContext() { final SessionLog log = getServerSession().getSessionLog(); log.log(SessionLog.INFO, "IsolatedHashMapTest.testIsolationInMockedContext()"); // Replace serverPlatform with mocked instance in IsolatedHashMap. final TestServerPlatform testPlatform = new TestServerPlatform(); Field serverPlatformField; Field supportPartitions; try { final ServerPlatform originalPlatform = (ServerPlatform)ReflectionHelper.getPrivateStatic(IsolatedHashMap.class, "serverPlatform"); final boolean originalsupport = ReflectionHelper.getPrivateStatic(IsolatedHashMap.class, "supportPartitions"); log.log(SessionLog.INFO, "Original platform field instance: " + originalPlatform.getClass().getName()); log.log(SessionLog.INFO, "Original partitions support flag: " + Boolean.toString(originalsupport)); ReflectionHelper.setPrivateStaticFinal(IsolatedHashMap.class, "serverPlatform", testPlatform); ReflectionHelper.setPrivateStaticFinal( IsolatedHashMap.class, "supportPartitions", testPlatform.usesPartitions()); log.log(SessionLog.INFO, "Mocked platform field instance: " + testPlatform.getClass().getName()); log.log(SessionLog.INFO, "Mocked partitions support flag: " + Boolean.toString(testPlatform.usesPartitions())); // Run the test in mocked partition context. final Map<String, String> map = IsolatedHashMap.newMap(); log.log(SessionLog.INFO, "Created " + map.getClass().getName() + "instance"); final String[] partitionIds = {"first", "second", "third"}; for (String partitionId : partitionIds) { testPlatform.setPartitionID(partitionId); doIsolationCheck(map, partitionId, log); } // Return original serverPlatform instance into IsolatedHashMap. ReflectionHelper.setPrivateStaticFinal(IsolatedHashMap.class, "serverPlatform", originalPlatform); ReflectionHelper.setPrivateStaticFinal(IsolatedHashMap.class, "supportPartitions", originalsupport); } catch (ReflectiveOperationException | SecurityException e) { log.logThrowable(SessionLog.WARNING, e); e.printStackTrace(); } } }