/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.service;
import com.liferay.portal.kernel.dao.jdbc.DataAccess;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.model.PermissionedModel;
import com.liferay.portal.kernel.model.ResourceBlockPermissionsContainer;
import com.liferay.portal.kernel.service.ResourceBlockLocalServiceUtil;
import com.liferay.portal.kernel.test.rule.AggregateTestRule;
import com.liferay.portal.kernel.util.NamedThreadFactory;
import com.liferay.portal.test.rule.ExpectedDBType;
import com.liferay.portal.test.rule.ExpectedLog;
import com.liferay.portal.test.rule.ExpectedLogs;
import com.liferay.portal.test.rule.ExpectedType;
import com.liferay.portal.test.rule.LiferayIntegrationTestRule;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import org.hibernate.util.JDBCExceptionReporter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
/**
* @author Connor McKay
* @author Shuyang Zhou
*/
public class ResourceBlockLocalServiceTest {
@ClassRule
@Rule
public static final AggregateTestRule aggregateTestRule =
new LiferayIntegrationTestRule();
@Before
public void setUp() throws Exception {
Connection connection = DataAccess.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"DELETE FROM ResourceBlock WHERE companyId = ? AND groupId = ? " +
"AND name = ?");
preparedStatement.setLong(1, _COMPANY_ID);
preparedStatement.setLong(2, _GROUP_ID);
preparedStatement.setString(3, _MODEL_NAME);
preparedStatement.executeUpdate();
DataAccess.cleanUp(connection, preparedStatement);
}
@ExpectedLogs(
expectedLogs = {
@ExpectedLog(
expectedDBType = ExpectedDBType.DB2,
expectedLog = "Error for batch element",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.DB2,
expectedLog = "[jcc][t4][102][10040][4.16.53] Batch failure.",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.HYPERSONIC,
expectedLog = "integrity constraint violation: unique constraint or index violation:",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.MYSQL,
expectedLog = "Deadlock found when trying to get lock; try restarting transaction",
expectedType = ExpectedType.EXACT
),
@ExpectedLog(
expectedDBType = ExpectedDBType.MYSQL,
expectedLog = "Duplicate entry ",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.ORACLE,
expectedLog = "ORA-00001: unique constraint",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.POSTGRESQL,
expectedLog = "Batch entry 0 insert into ResourceBlock ",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.POSTGRESQL,
expectedLog = "ERROR: duplicate key value violates unique constraint ",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.SYBASE,
expectedLog = "Attempt to insert duplicate key row in object 'ResourceBlock'",
expectedType = ExpectedType.CONTAINS
)
},
level = "ERROR", loggerClass = JDBCExceptionReporter.class
)
@Test
public void testConcurrentAccessing() throws Exception {
PermissionedModel permissionedModel = new MockPermissionedModel();
ResourceBlockPermissionsContainer resourceBlockPermissionsContainer =
new ResourceBlockPermissionsContainer();
resourceBlockPermissionsContainer.addPermission(_ROLE_ID, _ACTION_IDS);
Semaphore semaphore = new Semaphore(0);
Callable<Void> updateResourceBlockIdCallable =
new UpdateResourceBlockIdCallable(
permissionedModel, resourceBlockPermissionsContainer,
semaphore);
Callable<Void> releaseResourceBlockCallable =
new ReleaseResourceBlockCallable(permissionedModel, semaphore);
List<Callable<Void>> callables = new ArrayList<>(_REFERENCE_COUNT * 2);
for (int i = 0; i < _REFERENCE_COUNT; i++) {
callables.add(updateResourceBlockIdCallable);
callables.add(releaseResourceBlockCallable);
}
ExecutorService executorService = Executors.newFixedThreadPool(
_THREAD_COUNT,
new NamedThreadFactory(
"Concurrent Accessing-", Thread.NORM_PRIORITY, null));
List<Future<Void>> futures = executorService.invokeAll(callables);
for (Future<Void> future : futures) {
future.get();
}
executorService.shutdownNow();
_assertNoSuchResourceBlock(_COMPANY_ID, _GROUP_ID, _MODEL_NAME);
}
@Test
public void testConcurrentReleaseResourceBlock() throws Exception {
_addResourceBlock(_RESOURCE_BLOCK_ID, _REFERENCE_COUNT);
PermissionedModel permissionedModel = new MockPermissionedModel();
permissionedModel.setResourceBlockId(_RESOURCE_BLOCK_ID);
Callable<Void> releaseResourceBlockCallable =
new ReleaseResourceBlockCallable(permissionedModel, null);
List<Callable<Void>> callables = new ArrayList<>(_REFERENCE_COUNT);
for (int i = 0; i < _REFERENCE_COUNT; i++) {
callables.add(releaseResourceBlockCallable);
}
ExecutorService executorService = Executors.newFixedThreadPool(
_THREAD_COUNT,
new NamedThreadFactory(
"Concurrent Release-", Thread.NORM_PRIORITY, null));
List<Future<Void>> futures = executorService.invokeAll(callables);
for (Future<Void> future : futures) {
future.get();
}
executorService.shutdownNow();
_assertNoSuchResourceBlock(_RESOURCE_BLOCK_ID);
}
@ExpectedLogs(
expectedLogs = {
@ExpectedLog(
expectedDBType = ExpectedDBType.DB2,
expectedLog = "Error for batch element",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.DB2,
expectedLog = "[jcc][t4][102][10040][4.16.53] Batch failure.",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.HYPERSONIC,
expectedLog = "integrity constraint violation: unique constraint or index violation:",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.MYSQL,
expectedLog = "Deadlock found when trying to get lock; try restarting transaction",
expectedType = ExpectedType.EXACT
),
@ExpectedLog(
expectedDBType = ExpectedDBType.MYSQL,
expectedLog = "Duplicate entry ",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.ORACLE,
expectedLog = "ORA-00001: unique constraint",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.POSTGRESQL,
expectedLog = "Batch entry 0 insert into ResourceBlock ",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.POSTGRESQL,
expectedLog = "ERROR: duplicate key value violates unique constraint ",
expectedType = ExpectedType.PREFIX
),
@ExpectedLog(
expectedDBType = ExpectedDBType.SYBASE,
expectedLog = "Attempt to insert duplicate key row in object 'ResourceBlock'",
expectedType = ExpectedType.CONTAINS
)
},
level = "ERROR", loggerClass = JDBCExceptionReporter.class
)
@Test
public void testConcurrentUpdateResourceBlockId() throws Exception {
PermissionedModel permissionedModel = new MockPermissionedModel();
ResourceBlockPermissionsContainer resourceBlockPermissionsContainer =
new ResourceBlockPermissionsContainer();
resourceBlockPermissionsContainer.addPermission(_ROLE_ID, _ACTION_IDS);
Callable<Void> updateResourceBlockIdCallable =
new UpdateResourceBlockIdCallable(
permissionedModel, resourceBlockPermissionsContainer, null);
List<Callable<Void>> callables = new ArrayList<>(_REFERENCE_COUNT);
for (int i = 0; i < _REFERENCE_COUNT; i++) {
callables.add(updateResourceBlockIdCallable);
}
ExecutorService executorService = Executors.newFixedThreadPool(
_THREAD_COUNT,
new NamedThreadFactory(
"Concurrent Update-", Thread.NORM_PRIORITY, null));
List<Future<Void>> futures = executorService.invokeAll(callables);
for (Future<Void> future : futures) {
future.get();
}
executorService.shutdownNow();
_assertResourceBlockReferenceCount(
permissionedModel.getResourceBlockId(), _REFERENCE_COUNT);
}
private void _addResourceBlock(long resourceBlockId, long referenceCount)
throws Exception {
Connection connection = DataAccess.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"INSERT INTO ResourceBlock (resourceBlockId, companyId, groupId, " +
"name, referenceCount) VALUES (?, ?, ?, ?, ?)");
preparedStatement.setLong(1, resourceBlockId);
preparedStatement.setLong(2, _COMPANY_ID);
preparedStatement.setLong(3, _GROUP_ID);
preparedStatement.setString(4, _MODEL_NAME);
preparedStatement.setLong(5, referenceCount);
Assert.assertEquals(1, preparedStatement.executeUpdate());
DataAccess.cleanUp(connection, preparedStatement);
}
private void _assertNoSuchResourceBlock(long resourceBlockId)
throws Exception {
Connection connection = DataAccess.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"SELECT * FROM ResourceBlock WHERE resourceBlockId = ?");
preparedStatement.setLong(1, resourceBlockId);
ResultSet resultSet = preparedStatement.executeQuery();
Assert.assertFalse(resultSet.next());
DataAccess.cleanUp(connection, preparedStatement, resultSet);
}
private void _assertNoSuchResourceBlock(
long companyId, long groupId, String name)
throws Exception {
Connection connection = DataAccess.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"SELECT * FROM ResourceBlock WHERE companyId = ? AND groupId = ? " +
"AND name = ?");
preparedStatement.setLong(1, companyId);
preparedStatement.setLong(2, groupId);
preparedStatement.setString(3, name);
ResultSet resultSet = preparedStatement.executeQuery();
Assert.assertFalse(resultSet.next());
DataAccess.cleanUp(connection, preparedStatement, resultSet);
}
private void _assertResourceBlockReferenceCount(
long resourceBlockId, long expectedCountValue)
throws Exception {
Connection connection = DataAccess.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"SELECT referenceCount FROM ResourceBlock WHERE resourceBlockId " +
"= " + resourceBlockId);
ResultSet resultSet = preparedStatement.executeQuery();
Assert.assertTrue(resultSet.next());
Assert.assertEquals(expectedCountValue, resultSet.getLong(1));
DataAccess.cleanUp(connection, preparedStatement, resultSet);
}
private static final long _ACTION_IDS = 12;
private static final long _COMPANY_ID = -1;
private static final long _GROUP_ID = -1;
private static final String _MODEL_NAME = "permissionedmodel";
private static final int _REFERENCE_COUNT = 1000;
private static final long _RESOURCE_BLOCK_ID = -1;
private static final long _ROLE_ID = -1;
private static final int _THREAD_COUNT = 10;
private static class MockPermissionedModel implements PermissionedModel {
@Override
public long getResourceBlockId() {
return _resourceBlockId;
}
@Override
public void persist() {
}
@Override
public void setResourceBlockId(long resourceBlockId) {
_resourceBlockId = resourceBlockId;
}
private long _resourceBlockId;
}
private static class ReleaseResourceBlockCallable
implements Callable<Void> {
@Override
public Void call() throws Exception {
if (_semaphore != null) {
_semaphore.acquire();
}
ResourceBlockLocalServiceUtil.releasePermissionedModelResourceBlock(
_permissionedModel);
return null;
}
private ReleaseResourceBlockCallable(
PermissionedModel permissionedModel, Semaphore semaphore) {
_permissionedModel = permissionedModel;
_semaphore = semaphore;
}
private final PermissionedModel _permissionedModel;
private final Semaphore _semaphore;
}
private static class UpdateResourceBlockIdCallable
implements Callable<Void> {
@Override
public Void call() throws Exception {
while (true) {
try {
ResourceBlockLocalServiceUtil.updateResourceBlockId(
_COMPANY_ID, _GROUP_ID, _MODEL_NAME, _permissionedModel,
_resourceBlockPermissionsContainer.getPermissionsHash(),
_resourceBlockPermissionsContainer);
if (_semaphore != null) {
_semaphore.release();
}
break;
}
catch (SystemException se) {
}
}
return null;
}
private UpdateResourceBlockIdCallable(
PermissionedModel permissionedModel,
ResourceBlockPermissionsContainer resourceBlockPermissionsContainer,
Semaphore semaphore) {
_permissionedModel = permissionedModel;
_resourceBlockPermissionsContainer =
resourceBlockPermissionsContainer;
_semaphore = semaphore;
}
private final PermissionedModel _permissionedModel;
private final ResourceBlockPermissionsContainer
_resourceBlockPermissionsContainer;
private final Semaphore _semaphore;
}
}