| /* |
| * Copyright (c) 1998, 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.distributedservers.rcm.broadcast; |
| |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.Properties; |
| import java.util.Vector; |
| |
| import javax.naming.Context; |
| import javax.naming.InitialContext; |
| import javax.naming.NamingException; |
| |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.sessions.coordination.RemoteCommandManager; |
| import org.eclipse.persistence.testing.framework.TestCase; |
| import org.eclipse.persistence.testing.framework.TestCollection; |
| import org.eclipse.persistence.testing.framework.TestErrorException; |
| import org.eclipse.persistence.testing.framework.TestProblemException; |
| import org.eclipse.persistence.testing.framework.TestWrapper; |
| |
| public abstract class BroadcastSetupHelper { |
| public static final String TEST_CONTEXT_FACTORY = "org.eclipse.persistence.testing.framework.naming.InitialContextFactoryImpl"; |
| public static Properties CONTEXT_PROPERTIES = new Properties(); |
| static { |
| CONTEXT_PROPERTIES.setProperty(Context.INITIAL_CONTEXT_FACTORY, TEST_CONTEXT_FACTORY); |
| } |
| |
| public static Context getContext() throws NamingException { |
| // should use the testing JNDI factory. It doesn't require authorization. |
| return new InitialContext(CONTEXT_PROPERTIES); |
| } |
| |
| // connection type to be passed to methods: |
| // removeConnectionForAllSessions, removeConnectionForAllSessionsExcept, |
| // createConnectionForAllSessions, createConnectionForAllSessionsExcept, |
| public static final String LOCAL = "local"; |
| public static final String EXTERNAL = "external"; |
| public static final String ALL = "all"; |
| |
| |
| protected static BroadcastEventLock eventLock = new BroadcastEventLock(); |
| |
| public static BroadcastEventLock getEventLock() { |
| return eventLock; |
| } |
| |
| // Should return true in case a message may be received by the target, |
| // even though the source threw an exception on attempt to send it. |
| // This strange condition happens with Oc4jJGroups. |
| |
| public boolean shouldIgnoreTargetListenerInReconnectionTest() { |
| return false; |
| } |
| |
| // If removeConnectionOnError is set, |
| // local (listening) connection is removed in JMS case if subscriber.receive() throws exception; |
| // however in Oc4jJGroups case the only (local and external) connection is not removed |
| // unless message sending fails. |
| |
| public abstract boolean isLocalConnectionRemovedOnListeningError(); |
| |
| // Returns errorCode of RemoteCommandManagerException thrown in case |
| // creation of localConnection has failed. |
| |
| public abstract int getRcmExceptionErrorCodeOnFailureToCreateLocalConnection(); |
| |
| // JMSTopicTransportManager has separate connection for sending (external) and receiving (local) messages. |
| // Oc4jJGroups uses a single connection for both sending and receiving messages. |
| |
| public abstract boolean isLocalConnectionAlsoExternalConnection(); |
| |
| // After internal TestCase's method test() is executed |
| // waits until either the event or timeToWaitBeforeVerify is up |
| |
| public static class TestWrapperWithEventLock extends TestWrapper { |
| long timeToWaitBeforeVerify; |
| BroadcastEventLock eventLock; |
| |
| TestWrapperWithEventLock(TestCase test, long timeToWaitBeforeVerify, BroadcastEventLock eventLock) { |
| super(test); |
| this.timeToWaitBeforeVerify = timeToWaitBeforeVerify; |
| this.eventLock = eventLock; |
| } |
| |
| public long getTimeToWaitBeforeVerify() { |
| return this.timeToWaitBeforeVerify; |
| } |
| |
| public void setTimeToWaitBeforeVerify(long timeToWaitBeforeVerify) { |
| this.timeToWaitBeforeVerify = timeToWaitBeforeVerify; |
| } |
| |
| public BroadcastEventLock getEventLock() { |
| return eventLock; |
| } |
| |
| @Override |
| protected void test() throws Throwable { |
| this.eventLock.initialize(); |
| super.test(); |
| this.eventLock.waitUntilUnlocked(this.timeToWaitBeforeVerify); |
| } |
| |
| @Override |
| protected void verify() throws Throwable { |
| try { |
| super.verify(); |
| } catch (Exception verifyException) { |
| if (this.eventLock.getState() == BroadcastEventLock.UNLOCKED_BY_TIMER) { |
| throw new TestErrorException("Target hasn't processed remote command. Consider insreasing timeToWaitBeforeVerify.", verifyException); |
| } else if (this.eventLock.getState() == BroadcastEventLock.UNLOCKED_BY_SOURCE_EXCEPTION_HANDLER) { |
| throw new TestErrorException("Source has thrown an exception on attempt to propagate command. Note that the internal exception is NOT the one thrown by source.", verifyException); |
| } else if (this.eventLock.getState() == BroadcastEventLock.UNLOCKED_BY_SOURCE_SESSION) { |
| throw new TestErrorException("Source has removed the remote connection. Note that the internal exception is NOT the one thrown by source.", verifyException); |
| } else { |
| throw verifyException; |
| } |
| } |
| } |
| } |
| |
| // state |
| protected boolean isCreated = false; |
| |
| public boolean isCreated() { |
| return isCreated; |
| } |
| protected boolean isStarted = false; |
| |
| public boolean isStarted() { |
| return isStarted; |
| } |
| |
| // jndi names |
| protected String factoryJndiName; |
| |
| String getFactoryJndiName() { |
| return factoryJndiName; |
| } |
| protected String topicJndiName; |
| |
| String getTopicJndiName() { |
| return topicJndiName; |
| } |
| |
| // sessions used |
| protected IdentityHashMap sessions = new IdentityHashMap(2); |
| |
| public Iterator getSessionsIterator() { |
| return sessions.keySet().iterator(); |
| } |
| |
| // there's no public constructor - the subclasses are singletons. |
| |
| protected BroadcastSetupHelper() { |
| super(); |
| } |
| |
| public void createFactory() throws Exception { |
| if (!isCreated) { |
| Object[] factoryAndTopic = internalCreateFactory(); |
| Context context = getContext(); |
| if (factoryAndTopic[0] != null) { |
| context.bind(factoryJndiName, factoryAndTopic[0]); |
| } |
| if (factoryAndTopic[1] != null) { |
| context.bind(topicJndiName, factoryAndTopic[1]); |
| } |
| isCreated = true; |
| isStarted = false; |
| } |
| } |
| |
| public void startFactory() throws Exception { |
| if (isCreated && !isStarted) { |
| internalStartFactory(); |
| isStarted = true; |
| } |
| } |
| |
| public void stopFactory() throws Exception { |
| if (isCreated && isStarted) { |
| internalStopFactory(); |
| isStarted = false; |
| } |
| } |
| |
| public void destroyFactory() throws Exception { |
| if (isCreated) { |
| try { |
| Context context = getContext(); |
| if (factoryJndiName != null) { |
| Object factory = context.lookup(factoryJndiName); |
| if (factory != null) { |
| context.unbind(factoryJndiName); |
| } |
| } |
| if (topicJndiName != null) { |
| Object topic = context.lookup(topicJndiName); |
| if (topic != null) { |
| context.unbind(topicJndiName); |
| } |
| } |
| internalDestroyFactory(); |
| } finally { |
| isCreated = false; |
| isStarted = false; |
| } |
| } |
| } |
| |
| // returns array of two objects: the first is factory, the second is topic |
| |
| protected abstract Object[] internalCreateFactory() throws Exception; |
| |
| protected abstract void internalStartFactory() throws Exception; |
| |
| protected abstract void internalStopFactory() throws Exception; |
| |
| protected abstract void internalDestroyFactory() throws Exception; |
| |
| protected abstract void createTransportManager(RemoteCommandManager rcm); |
| |
| public void startCacheSynchronization(AbstractSession session, boolean isSource) { |
| try { |
| sessions.put(session, isSource); |
| if (sessions.size() == 1) { |
| createFactory(); |
| startFactory(); |
| } |
| |
| RemoteCommandManager rcm = new RemoteCommandManager(session); |
| createTransportManager(rcm); |
| session.setShouldPropagateChanges(true); |
| rcm.initialize(); |
| |
| getEventLock().attach(session, isSource); |
| |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| throw new TestProblemException("exception in startCacheSynchronization ", ex); |
| } |
| } |
| |
| public static Object wrapAllTestCases(Object test, long timeToWait) { |
| if (test instanceof TestCase) { |
| return new TestWrapperWithEventLock((TestCase)test, timeToWait, eventLock); |
| } else if (test instanceof TestCollection) { |
| Vector tests = ((TestCollection)test).getTests(); |
| Vector wrappedTests = new Vector(tests.size()); |
| for (int i = 0; i < tests.size(); i++) { |
| Object wrappedTest = wrapAllTestCases(tests.elementAt(i), timeToWait); |
| if (wrappedTest != null) { |
| wrappedTests.add(wrappedTest); |
| } else { |
| // must be collection - keep it |
| wrappedTests.add(tests.elementAt(i)); |
| } |
| } |
| // remove the original tests |
| tests.clear(); |
| // add the wrapped ones |
| ((TestCollection)test).addTests(wrappedTests); |
| return null; |
| } else { |
| // Neither TestCase nor TestCollection - can't handle it |
| return null; |
| } |
| } |
| |
| // Looking for the test case with the specified short class name. |
| // If shouldLookUnderWrapper==true then will be checks the class name of the wrapped test - |
| // but returns the wrapper test. |
| |
| public static TestCase getTestCase(Object test, String testShortClassName, boolean shouldLookUnderWrapper) { |
| if (test instanceof TestCase) { |
| TestCase testToLookAt = (TestCase)test; |
| if (shouldLookUnderWrapper) { |
| while (testToLookAt instanceof TestWrapper) { |
| testToLookAt = ((TestWrapper)testToLookAt).getWrappedTest(); |
| } |
| } |
| if (Helper.getShortClassName(testToLookAt).equals(testShortClassName)) { |
| // return the wrapped test |
| return (TestCase)test; |
| } |
| } else if (test instanceof TestCollection) { |
| Iterator it = ((TestCollection)test).getTests().iterator(); |
| while (it.hasNext()) { |
| TestCase currentTest = getTestCase(it.next(), testShortClassName, shouldLookUnderWrapper); |
| if (currentTest != null) { |
| return currentTest; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public void stopCacheSynchronization(AbstractSession session) { |
| try { |
| sessions.remove(session); |
| getEventLock().detach(session); |
| session.setShouldPropagateChanges(false); |
| session.getCommandManager().shutdown(); |
| session.setCommandManager(null); |
| |
| if (sessions.size() == 0) { |
| // destroy the factory |
| destroyFactory(); |
| } |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| throw new TestProblemException("exception in stopCacheSynchronization ", ex); |
| } |
| } |
| |
| public void removeConnectionsForAllSessions(String connectionType) throws Exception { |
| removeConnectionsForAllSessionsExcept(null, connectionType); |
| } |
| |
| public void removeConnectionsForAllSessionsExcept(AbstractSession sessionToIgnore, String connectionType) throws Exception { |
| Iterator it = sessions.keySet().iterator(); |
| while (it.hasNext()) { |
| AbstractSession session = (AbstractSession)it.next(); |
| if (session != sessionToIgnore) { |
| removeConnections(session, connectionType); |
| } |
| } |
| } |
| |
| protected void removeConnections(AbstractSession session, String connectionType) { |
| if (connectionType.equalsIgnoreCase(LOCAL)) { |
| session.getCommandManager().getTransportManager().removeLocalConnection(); |
| } else if (connectionType.equals(EXTERNAL)) { |
| session.getCommandManager().getTransportManager().removeAllConnectionsToExternalServices(); |
| } else if (connectionType.equals(ALL)) { |
| session.getCommandManager().getTransportManager().discardConnections(); |
| } else { |
| throw new TestProblemException("invalid connection type: " + connectionType + ". Valid types are: " + LOCAL + "; " + EXTERNAL + "; " + ALL); |
| } |
| } |
| |
| // by default does nothing. in JMS case sends an arbitrary message to speed up shut down of listening threads. |
| |
| protected void sendMessageToStopListenerThreads() throws Exception { |
| } |
| |
| public void createConnectionsForAllSessions(String connectionType) { |
| createConnectionsForAllSessionsExcept(null, connectionType); |
| } |
| |
| public void createConnectionsForAllSessionsExcept(AbstractSession sessionToIgnore, String connectionType) { |
| Iterator it = sessions.keySet().iterator(); |
| while (it.hasNext()) { |
| AbstractSession session = (AbstractSession)it.next(); |
| if (session != sessionToIgnore) { |
| createConnections(session, connectionType); |
| } |
| } |
| } |
| |
| protected void createConnections(AbstractSession session, String connectionType) { |
| if (connectionType.equalsIgnoreCase(LOCAL)) { |
| session.getCommandManager().getTransportManager().createLocalConnection(); |
| } else if (connectionType.equals(EXTERNAL)) { |
| createExternalConnection(session); |
| } else if (connectionType.equals(ALL)) { |
| session.getCommandManager().getTransportManager().createConnections(); |
| } else { |
| throw new TestProblemException("invalid connection type: " + connectionType + ". Valid types are: " + LOCAL + "; " + EXTERNAL + "; " + ALL); |
| } |
| } |
| |
| protected abstract void createExternalConnection(AbstractSession session); |
| } |