blob: 59f3317435d6c43d1d69a582aebe8d0def6fe27a [file] [log] [blame]
/*
* Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.tests.junit.sessionsxml;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import org.eclipse.persistence.exceptions.ServerPlatformException;
import org.eclipse.persistence.logging.DefaultSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.platform.server.ServerPlatformBase;
import org.eclipse.persistence.platform.server.ServerPlatformDetector;
import org.eclipse.persistence.platform.server.ServerPlatformUtils;
import org.eclipse.persistence.sessions.DatabaseSession;
import org.eclipse.persistence.sessions.ExternalTransactionController;
import org.eclipse.persistence.sessions.factories.SessionManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Tests the API from SessionManager.
*/
public class SessionManagerTest {
private List<ServerPlatformDetector> detectors;
private SessionLog originalLogger;
@Before
public void setUp() {
originalLogger = (SessionLog) getField(SessionManager.class, "LOG", null);
SessionLog log = new LogWrapper();
log.setLevel(originalLogger.getLevel());
setStaticField(SessionManager.class, "LOG", log);
}
@After
public void tearDown() {
if (detectors != null) {
for (Iterator<ServerPlatformDetector> i = detectors.iterator(); i.hasNext();) {
ServerPlatformDetector detector = i.next();
if (Detector.class.getName().equals(detector.getClass().getName())) {
i.remove();
break;
}
}
setStaticField(ServerPlatformUtils.class, "SERVER_PLATFORM_CLS", null);
}
setStaticField(SessionManager.class, "LOG", originalLogger);
reinitManager(false, true);
}
@Test
public void testConcurrency() {
reinitManager(true, true);
SessionManager sm = SessionManager.getManager();
Assert.assertEquals("test", getField(SessionManager.class, "context", sm));
final Platform platform = (Platform) getField(SessionManager.class, "detectedPlatform", sm);
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
// create and start testing threads
new Thread(new Worker(startSignal, doneSignal, platform)).start();
}
//create and start context switching thread
ContextSwitcher cs = new ContextSwitcher(platform);
Thread contextSwitcher = new Thread(cs);
contextSwitcher.start();
// let all threads proceed
startSignal.countDown();
try {
// wait for all to finish
doneSignal.await();
} catch (InterruptedException e) {
//ignore
}
//finish the context switching thread
cs.stop = true;
try {
contextSwitcher.join();
} catch (InterruptedException e) {
//ignore
}
//check what we can
Set<String> contexts = cs.values;
ConcurrentMap<String, SessionManager> registeredManagers = (ConcurrentMap<String, SessionManager>) getField(SessionManager.class, "managers", null);
//cannot do this as there may not be any thread catching some particular value
//Assert.assertEquals(contexts.size() + 1, registeredManagers.size());
for (String context: contexts) {
if ("test".equals(context)) {
continue;
}
platform.partitionId = context;
SessionManager.getManager().destroy();
}
Assert.assertEquals(1, registeredManagers.size());
}
@Test
public void testAllManagers() {
Collection<SessionManager> allManagers = SessionManager.getAllManagers();
Assert.assertEquals(1, allManagers.size());
Assert.assertNull(getField(SessionManager.class, "context", allManagers.iterator().next()));
reinitManager(true, true);
allManagers = SessionManager.getAllManagers();
Assert.assertEquals(1, allManagers.size());
Assert.assertEquals("test", getField(SessionManager.class, "context", allManagers.iterator().next()));
}
@Test
public void testCustomManager() {
SessionManager sm = new SM();
SessionManager.setManager(sm);
SessionManager m1 = SessionManager.getManager();
Assert.assertNotNull(m1);
Assert.assertNull(getField(SessionManager.class, "context", m1));
Collection<SessionManager> allManagers = SessionManager.getAllManagers();
Assert.assertEquals(1, allManagers.size());
Assert.assertTrue(sm == allManagers.iterator().next());
reinitManager(true, true);
SessionManager.setManager(sm);
m1 = SessionManager.getManager();
Assert.assertNotNull(m1);
Assert.assertEquals("test", getField(SessionManager.class, "context", m1));
allManagers = SessionManager.getAllManagers();
Assert.assertEquals(1, allManagers.size());
Assert.assertTrue(sm == allManagers.iterator().next());
sm.destroy();
}
@Test
public void testInvalidPlatform() {
// platform class name can be invalid/not found
reinitManager(true, false);
SessionManager sm = SessionManager.getManager();
LogWrapper logger = (LogWrapper) getField(SessionManager.class, "LOG", null);
Assert.assertEquals(SessionLog.WARNING, logger.getLastLevel());
Assert.assertEquals(SessionLog.CONNECTION, logger.getLastCategory());
Throwable t = logger.getLastThrowable();
Assert.assertTrue("invalid excpetion type: " + t, t instanceof ServerPlatformException);
Assert.assertTrue("invalid excpetion type: " + t.getCause(), t.getCause() instanceof ClassNotFoundException);
}
@Test
public void testNPEFromPlatform() {
// platform impl can throw NPE which is being wrapped by other exceptions
Platform.forceNPE = true;
try {
reinitManager(true, true);
SessionManager sm = SessionManager.getManager();
LogWrapper logger = (LogWrapper) getField(SessionManager.class, "LOG", null);
Assert.assertEquals(SessionLog.WARNING, logger.getLastLevel());
Assert.assertEquals(SessionLog.CONNECTION, logger.getLastCategory());
Throwable t = logger.getLastThrowable();
Assert.assertTrue("invalid excpetion type: " + t, t instanceof ServerPlatformException);
} finally {
Platform.forceNPE = false;
}
}
private void reinitManager(boolean addDetector, boolean validDetector) {
if (addDetector) {
detectors = (List<ServerPlatformDetector>) getField(ServerPlatformUtils.class, "PLATFORMS", null);
detectors.add(0, new Detector(validDetector));
setStaticField(ServerPlatformUtils.class, "SERVER_PLATFORM_CLS", null);
}
Method m = null;
try {
m = SessionManager.class.getDeclaredMethod("init");
m.setAccessible(true);
m.invoke(null);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
} finally {
if (m != null) {
try {
m.setAccessible(false);
} catch (Exception e) {
//ignore
}
}
}
}
private Object getField(Class<?> c, String field, Object o) {
Field f = null;
try {
f = c.getDeclaredField(field);
f.setAccessible(true);
return f.get(o);
} catch (IllegalAccessException | IllegalArgumentException | SecurityException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
} finally {
if (f != null) {
f.setAccessible(false);
}
}
}
private void setStaticField(Class<?> c, String field, Object value) {
Field f = null;
try {
f = c.getDeclaredField(field);
setPrivateStaticFinalField(f, value);
} catch (IllegalArgumentException | SecurityException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
} finally {
if (f != null) {
f.setAccessible(false);
}
}
}
private void setPrivateStaticFinalField(Field f, Object value) {
Field modifiersField = null;
int orig = 0;
try {
f.setAccessible(true);
orig = f.getModifiers();
if (Modifier.isFinal(orig)) {
// remove final modifier from field
modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, orig & ~Modifier.FINAL);
}
f.set(null, value);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
throw new RuntimeException(ex);
} finally {
if (modifiersField != null) {
if (f != null) {
try {
modifiersField.setInt(f, orig);
} catch (IllegalArgumentException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
modifiersField.setAccessible(false);
}
if (f != null) {
f.setAccessible(false);
}
}
}
private static final class SM extends SessionManager {
public SM() {
//empty by intention to test the 'context' field initialization
//through the setManager API
}
}
private static final class LogWrapper extends DefaultSessionLog {
private volatile Throwable lastThrowable;
private volatile String lastCategory;
private volatile int lastLevel;
@Override
public void logThrowable(int level, String category, Throwable throwable) {
this.lastLevel = level;
this.lastCategory = category;
this.lastThrowable = throwable;
super.logThrowable(level, category, throwable);
}
/**
* @return the throwable
*/
public Throwable getLastThrowable() {
return lastThrowable;
}
/**
* @return the category
*/
public String getLastCategory() {
return lastCategory;
}
/**
* @return the level
*/
public int getLastLevel() {
return lastLevel;
}
}
public static final class Platform extends ServerPlatformBase {
private volatile String partitionId = "test";
private static boolean forceNPE = false;
public Platform(DatabaseSession newDatabaseSession) {
super(newDatabaseSession);
if (forceNPE) {
throw new NullPointerException();
}
}
@Override
public Class<? extends ExternalTransactionController> getExternalTransactionControllerClass() {
return null;
}
@Override
public boolean usesPartitions() {
return true;
}
@Override
public String getPartitionID() {
return partitionId;
}
}
private static class Detector implements ServerPlatformDetector {
private final boolean valid;
Detector(boolean valid) {
this.valid = valid;
}
@Override
public String checkPlatform() {
return valid ? Platform.class.getName() : "non-existing-class-name";
}
}
/**
* Randomly pick a number which is used as a context and changes it in random interval
*/
private static final class ContextSwitcher implements Runnable {
private final Random r = new Random(System.currentTimeMillis());
private volatile boolean stop = false;
private final Platform p;
//contains names which were assigned to partitionId during test run
private final Set<String> values = new HashSet<>();
public ContextSwitcher(Platform p) {
this.p = p;
}
@Override
public void run() {
while (!stop) {
String id = String.valueOf(r.nextInt(10));
p.partitionId = id;
values.add(id);
try {
Thread.sleep(r.nextInt(3));
} catch (InterruptedException ie) {
}
}
}
};
private static final class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final Platform p;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal, Platform p) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.p = p;
}
@Override
public void run() {
try {
startSignal.await();
//actual test
SessionManager sm = SessionManager.getManager();
Assert.assertNotNull(sm);
} catch (InterruptedException ex) {
//ignore
} finally {
doneSignal.countDown();
}
}
}
}