/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ambari.annotations;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ambari.annotations.TransactionalLock.LockArea;
import org.apache.ambari.annotations.TransactionalLock.LockType;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.H2DatabaseCleaner;
import org.apache.ambari.server.orm.GuiceJpaInitializer;
import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
import org.apache.ambari.server.orm.TransactionalLocks;
import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.persist.Transactional;
import com.google.inject.util.Modules;
/**
* Tests {@link TransactionalLock} and associated classes.
*/
public class TransactionalLockInterceptorTest {
private Injector m_injector;
@Before
public void setup() throws Exception {
m_injector = Guice.createInjector(
Modules.override(new InMemoryDefaultTestModule()).with(new MockModule()));
m_injector.getInstance(GuiceJpaInitializer.class);
}
@After
public void teardown() throws AmbariException, SQLException {
H2DatabaseCleaner.clearDatabaseAndStopPersistenceService(m_injector);
}
/**
* Tests that the {@link Transactional} and {@link TransactionalLock}
* annotations cause the interceptors to lock the right area.
*
* @throws Throwable
*/
@Test
public void testTransactionalLockInvocation() throws Throwable {
// create mocks
TransactionalLocks transactionalLocks = m_injector.getInstance(TransactionalLocks.class);
ReadWriteLock readWriteLock = EasyMock.createStrictMock(ReadWriteLock.class);
Lock readLock = EasyMock.createStrictMock(Lock.class);
Lock writeLock = EasyMock.createStrictMock(Lock.class);
// expectations
EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
writeLock.lock();
EasyMock.expectLastCall().once();
writeLock.unlock();
EasyMock.expectLastCall().once();
// replay
EasyMock.replay(transactionalLocks, readWriteLock, readLock, writeLock);
// invoke method with annotations
HostRoleCommandDAO hostRoleCommandDAO = m_injector.getInstance(HostRoleCommandDAO.class);
hostRoleCommandDAO.mergeAll(new ArrayList<HostRoleCommandEntity>());
// verify locks are called
EasyMock.verify(transactionalLocks, readWriteLock, readLock, writeLock);
}
/**
* Tests that a {@link TransactionalLock} called within the constructs of an
* earlier transaction will still lock.
*
* @throws Throwable
*/
@Test
public void testNestedTransactional() throws Throwable {
// create mocks
TransactionalLocks transactionalLocks = m_injector.getInstance(TransactionalLocks.class);
ReadWriteLock readWriteLock = EasyMock.createStrictMock(ReadWriteLock.class);
Lock readLock = EasyMock.createStrictMock(Lock.class);
Lock writeLock = EasyMock.createStrictMock(Lock.class);
// expectations
EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
writeLock.lock();
EasyMock.expectLastCall().once();
writeLock.unlock();
EasyMock.expectLastCall().once();
// replay
EasyMock.replay(transactionalLocks, readWriteLock, readLock, writeLock);
// invoke method with annotations
TestObject testObject = m_injector.getInstance(TestObject.class);
testObject.testLockMethodAsChildOfActiveTransaction();
// verify locks are called
EasyMock.verify(transactionalLocks, readWriteLock, readLock, writeLock);
}
/**
* Tests that a {@link TransactionalLock} called within the constructs of an
* earlier transaction will still lock.
*
* @throws Throwable
*/
@Test
public void testMultipleLocks() throws Throwable {
// create mocks
TransactionalLocks transactionalLocks = m_injector.getInstance(TransactionalLocks.class);
ReadWriteLock readWriteLock = EasyMock.createStrictMock(ReadWriteLock.class);
Lock readLock = EasyMock.createStrictMock(Lock.class);
Lock writeLock = EasyMock.createStrictMock(Lock.class);
// expectations
EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
writeLock.lock();
EasyMock.expectLastCall().once();
writeLock.unlock();
EasyMock.expectLastCall().once();
// another round of expectations
EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
writeLock.lock();
EasyMock.expectLastCall().once();
writeLock.unlock();
EasyMock.expectLastCall().once();
// replay
EasyMock.replay(transactionalLocks, readWriteLock, readLock, writeLock);
// invoke method with annotations
TestObject testObject = m_injector.getInstance(TestObject.class);
testObject.testMultipleLocks();
// verify locks are called
EasyMock.verify(transactionalLocks, readWriteLock, readLock, writeLock);
}
/**
* Tests that two invocations of a {@link TransactionalLock} with the same
* {@link TransactionalLock} will only lock once on the {@link LockArea}.
*
* @throws Throwable
*/
@Test
public void testNestedMultipleLocks() throws Throwable {
// create mocks
TransactionalLocks transactionalLocks = m_injector.getInstance(TransactionalLocks.class);
ReadWriteLock readWriteLock = EasyMock.createStrictMock(ReadWriteLock.class);
Lock readLock = EasyMock.createStrictMock(Lock.class);
Lock writeLock = EasyMock.createStrictMock(Lock.class);
// expectations
EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
writeLock.lock();
EasyMock.expectLastCall().once();
writeLock.unlock();
EasyMock.expectLastCall().once();
// replay
EasyMock.replay(transactionalLocks, readWriteLock, readLock, writeLock);
// invoke method with annotations
TestObject testObject = m_injector.getInstance(TestObject.class);
testObject.testMultipleNestedLocks();
// verify locks are called
EasyMock.verify(transactionalLocks, readWriteLock, readLock, writeLock);
}
/**
* A test object which has methods annotated for use with this test class.
*/
public static class TestObject {
/**
* Calls:
* <ul>
* <li>@Transactional</li>
* <li>-> @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType
* = LockType.WRITE)</li>
* </ul>
*/
public void testLockMethodAsChildOfActiveTransaction() {
transactionMethodCallingAnotherWithLock();
}
/**
* Calls:
* <ul>
* <li>@TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType =
* LockType.WRITE)</li>
* <li>@TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType =
* LockType.WRITE)</li>
* </ul>
*/
public void testMultipleLocks() {
transactionMethodWithLock();
transactionMethodWithLock();
}
/**
* Calls:
* <ul>
* <li>@TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType =
* LockType.WRITE)</li>
* <li>-> @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType
* = LockType.WRITE)</li>
* </ul>
*/
public void testMultipleNestedLocks() {
transactionMethodWithLockCallingAnotherWithLock();
}
@Transactional
public void transactionMethodCallingAnotherWithLock() {
transactionMethodWithLock();
}
@Transactional
@TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
public void transactionMethodWithLock() {
}
@Transactional
@TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
public void transactionMethodWithLockCallingAnotherWithLock() {
transactionMethodWithLock();
}
}
/**
*
*/
private class MockModule implements Module {
/**
*
*/
@Override
public void configure(Binder binder) {
binder.bind(TransactionalLocks.class).toInstance(
EasyMock.createNiceMock(TransactionalLocks.class));
}
}
}