/** * 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.kernel.cache.transactional; import com.liferay.portal.kernel.cache.PortalCache; import com.liferay.portal.kernel.cache.PortalCacheHelperUtil; import com.liferay.portal.kernel.cache.SkipReplicationThreadLocal; import com.liferay.portal.kernel.dao.orm.EntityCacheUtil; import com.liferay.portal.kernel.dao.orm.FinderCacheUtil; import com.liferay.portal.kernel.transaction.Propagation; import com.liferay.portal.kernel.transaction.TransactionAttribute; import com.liferay.portal.kernel.transaction.TransactionDefinition; import com.liferay.portal.kernel.transaction.TransactionLifecycleListener; import com.liferay.portal.kernel.transaction.TransactionStatus; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.InitialThreadLocal; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.PropsUtil; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author Shuyang Zhou */ public class TransactionalPortalCacheHelper { public static final TransactionLifecycleListener TRANSACTION_LIFECYCLE_LISTENER = new TransactionLifecycleListener() { @Override public void committed( TransactionAttribute transactionAttribute, TransactionStatus transactionStatus) { if (!_isTransactionalCacheEnabled()) { return; } Propagation propagation = transactionAttribute.getPropagation(); if (propagation.value() >= TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { List<List<PortalCacheMap>> backupPortalCacheMaps = _backupPortalCacheMapsThreadLocal.get(); _portalCacheMapsThreadLocal.set( backupPortalCacheMaps.remove( backupPortalCacheMaps.size() - 1)); } else if (transactionStatus.isNewTransaction()) { commit(); } } @Override public void created( TransactionAttribute transactionAttribute, TransactionStatus transactionStatus) { if (!_isTransactionalCacheEnabled()) { return; } Propagation propagation = transactionAttribute.getPropagation(); if (propagation.value() >= TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { List<List<PortalCacheMap>> backupPortalCacheMaps = _backupPortalCacheMapsThreadLocal.get(); backupPortalCacheMaps.add( _portalCacheMapsThreadLocal.get()); _portalCacheMapsThreadLocal.remove(); } else if (transactionStatus.isNewTransaction()) { begin(); } } @Override public void rollbacked( TransactionAttribute transactionAttribute, TransactionStatus transactionStatus, Throwable throwable) { if (!_isTransactionalCacheEnabled()) { return; } Propagation propagation = transactionAttribute.getPropagation(); if (propagation.value() >= TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { List<List<PortalCacheMap>> backupPortalCacheMaps = _backupPortalCacheMapsThreadLocal.get(); _portalCacheMapsThreadLocal.set( backupPortalCacheMaps.remove( backupPortalCacheMaps.size() - 1)); } else if (transactionStatus.isNewTransaction()) { rollback(); EntityCacheUtil.clearLocalCache(); FinderCacheUtil.clearLocalCache(); } } }; public static void begin() { List<PortalCacheMap> portalCacheMaps = _portalCacheMapsThreadLocal.get(); portalCacheMaps.add(new PortalCacheMap()); } public static void commit() { PortalCacheMap portalCacheMap = _popPortalCacheMap(); for (Map.Entry <PortalCache<? extends Serializable, ?>, UncommittedBuffer> portalCacheMapEntry : portalCacheMap.entrySet()) { PortalCache<Serializable, Object> portalCache = (PortalCache<Serializable, Object>)portalCacheMapEntry.getKey(); UncommittedBuffer uncommittedBuffer = portalCacheMapEntry.getValue(); uncommittedBuffer.commitTo(portalCache); } portalCacheMap.clear(); } public static <K extends Serializable, V> V get( PortalCache<K, V> portalCache, K key) { PortalCacheMap portalCacheMap = _peekPortalCacheMap(); UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache); if (uncommittedBuffer == null) { return null; } ValueEntry valueEntry = uncommittedBuffer.get(key); if (valueEntry == null) { return null; } return (V)valueEntry._value; } public static Serializable getNullHolder() { return _NULL_HOLDER; } public static boolean isEnabled() { if (!_isTransactionalCacheEnabled()) { return false; } List<PortalCacheMap> portalCacheMaps = _portalCacheMapsThreadLocal.get(); return !portalCacheMaps.isEmpty(); } public static <K extends Serializable, V> void put( PortalCache<K, V> portalCache, K key, V value, int ttl) { PortalCacheMap portalCacheMap = _peekPortalCacheMap(); UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache); if (uncommittedBuffer == null) { uncommittedBuffer = new UncommittedBuffer(); portalCacheMap.put(portalCache, uncommittedBuffer); } uncommittedBuffer.put( key, new ValueEntry(value, ttl, SkipReplicationThreadLocal.isEnabled())); } public static <K extends Serializable, V> void removeAll( PortalCache<K, V> portalCache) { PortalCacheMap portalCacheMap = _peekPortalCacheMap(); UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache); if (uncommittedBuffer == null) { uncommittedBuffer = new UncommittedBuffer(); portalCacheMap.put(portalCache, uncommittedBuffer); } uncommittedBuffer.removeAll(SkipReplicationThreadLocal.isEnabled()); } public static void rollback() { PortalCacheMap portalCacheMap = _popPortalCacheMap(); portalCacheMap.clear(); } protected static class PortalCacheMap extends HashMap <PortalCache<? extends Serializable, ?>, UncommittedBuffer> { } private static boolean _isTransactionalCacheEnabled() { if (_transactionalCacheEnabled == null) { _transactionalCacheEnabled = GetterUtil.getBoolean( PropsUtil.get(PropsKeys.TRANSACTIONAL_CACHE_ENABLED)); } return _transactionalCacheEnabled; } private static PortalCacheMap _peekPortalCacheMap() { List<PortalCacheMap> portalCacheMaps = _portalCacheMapsThreadLocal.get(); return portalCacheMaps.get(portalCacheMaps.size() - 1); } private static PortalCacheMap _popPortalCacheMap() { List<PortalCacheMap> portalCacheMaps = _portalCacheMapsThreadLocal.get(); return portalCacheMaps.remove(portalCacheMaps.size() - 1); } private static final Serializable _NULL_HOLDER = "NULL_HOLDER"; private static final ValueEntry _NULL_HOLDER_VALUE_ENTRY = new ValueEntry( _NULL_HOLDER, PortalCache.DEFAULT_TIME_TO_LIVE, false); private static final ThreadLocal<List<List<PortalCacheMap>>> _backupPortalCacheMapsThreadLocal = new InitialThreadLocal<>( TransactionalPortalCacheHelper.class.getName() + "._backupPortalCacheMapsThreadLocal", ArrayList::new); private static final ThreadLocal<List<PortalCacheMap>> _portalCacheMapsThreadLocal = new InitialThreadLocal<>( TransactionalPortalCacheHelper.class.getName() + "._portalCacheMapsThreadLocal", ArrayList::new); private static volatile Boolean _transactionalCacheEnabled; private static class UncommittedBuffer { public void commitTo(PortalCache<Serializable, Object> portalCache) { if (_removeAll) { if (_skipReplicator) { PortalCacheHelperUtil.removeAllWithoutReplicator( portalCache); } else { portalCache.removeAll(); } } for (Map.Entry<? extends Serializable, ValueEntry> entry : _uncommittedMap.entrySet()) { ValueEntry valueEntry = entry.getValue(); valueEntry.commitTo(portalCache, entry.getKey()); } } public ValueEntry get(Serializable key) { ValueEntry valueEntry = _uncommittedMap.get(key); if ((valueEntry == null) && _removeAll) { valueEntry = _NULL_HOLDER_VALUE_ENTRY; } return valueEntry; } public void put(Serializable key, ValueEntry valueEntry) { ValueEntry oldValueEntry = _uncommittedMap.put(key, valueEntry); if (oldValueEntry != null) { oldValueEntry.merge(valueEntry); } } public void removeAll(boolean skipReplicator) { _uncommittedMap.clear(); _removeAll = true; if (_skipReplicator) { _skipReplicator = skipReplicator; } } private boolean _removeAll; private boolean _skipReplicator = true; private final Map<Serializable, ValueEntry> _uncommittedMap = new HashMap<>(); } private static class ValueEntry { public ValueEntry(Object value, int ttl, boolean skipReplicator) { _value = value; _ttl = ttl; _skipReplicator = skipReplicator; } public void commitTo( PortalCache<Serializable, Object> portalCache, Serializable key) { if (_value == _NULL_HOLDER) { if (_skipReplicator) { PortalCacheHelperUtil.removeWithoutReplicator( portalCache, key); } else { portalCache.remove(key); } } else { if (_skipReplicator) { PortalCacheHelperUtil.putWithoutReplicator( portalCache, key, _value, _ttl); } else { portalCache.put(key, _value, _ttl); } } } public void merge(ValueEntry valueEntry) { if (!_skipReplicator) { valueEntry._skipReplicator = false; } } private boolean _skipReplicator; private final int _ttl; private final Object _value; } }