001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.cache.transactional;
016    
017    import com.liferay.portal.kernel.cache.AggregatedPortalCacheListener;
018    import com.liferay.portal.kernel.cache.PortalCache;
019    import com.liferay.portal.kernel.cache.PortalCacheHelperUtil;
020    import com.liferay.portal.kernel.dao.orm.EntityCacheUtil;
021    import com.liferay.portal.kernel.dao.orm.FinderCacheUtil;
022    import com.liferay.portal.kernel.transaction.Propagation;
023    import com.liferay.portal.kernel.transaction.TransactionAttribute;
024    import com.liferay.portal.kernel.transaction.TransactionDefinition;
025    import com.liferay.portal.kernel.transaction.TransactionLifecycleListener;
026    import com.liferay.portal.kernel.transaction.TransactionStatus;
027    import com.liferay.portal.kernel.util.GetterUtil;
028    import com.liferay.portal.kernel.util.InitialThreadLocal;
029    import com.liferay.portal.kernel.util.PropsKeys;
030    import com.liferay.portal.kernel.util.PropsUtil;
031    
032    import java.io.Serializable;
033    
034    import java.util.ArrayList;
035    import java.util.HashMap;
036    import java.util.List;
037    import java.util.Map;
038    
039    /**
040     * @author Shuyang Zhou
041     */
042    public class TransactionalPortalCacheHelper {
043    
044            public static final TransactionLifecycleListener
045                    TRANSACTION_LIFECYCLE_LISTENER = new TransactionLifecycleListener() {
046    
047                            @Override
048                            public void committed(
049                                    TransactionAttribute transactionAttribute,
050                                    TransactionStatus transactionStatus) {
051    
052                                    if (!_isTransactionalCacheEnabled()) {
053                                            return;
054                                    }
055    
056                                    Propagation propagation = transactionAttribute.getPropagation();
057    
058                                    if (propagation.value() >=
059                                                    TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
060    
061                                            List<List<PortalCacheMap>> backupPortalCacheMaps =
062                                                    _backupPortalCacheMapsThreadLocal.get();
063    
064                                            _portalCacheMapsThreadLocal.set(
065                                                    backupPortalCacheMaps.remove(
066                                                            backupPortalCacheMaps.size() - 1));
067                                    }
068                                    else if (transactionStatus.isNewTransaction()) {
069                                            commit();
070                                    }
071                            }
072    
073                            @Override
074                            public void created(
075                                    TransactionAttribute transactionAttribute,
076                                    TransactionStatus transactionStatus) {
077    
078                                    if (!_isTransactionalCacheEnabled()) {
079                                            return;
080                                    }
081    
082                                    Propagation propagation = transactionAttribute.getPropagation();
083    
084                                    if (propagation.value() >=
085                                                    TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
086    
087                                            List<List<PortalCacheMap>> backupPortalCacheMaps =
088                                                    _backupPortalCacheMapsThreadLocal.get();
089    
090                                            backupPortalCacheMaps.add(
091                                                    _portalCacheMapsThreadLocal.get());
092    
093                                            _portalCacheMapsThreadLocal.remove();
094                                    }
095                                    else if (transactionStatus.isNewTransaction()) {
096                                            begin();
097                                    }
098                            }
099    
100                            @Override
101                            public void rollbacked(
102                                    TransactionAttribute transactionAttribute,
103                                    TransactionStatus transactionStatus, Throwable throwable) {
104    
105                                    if (!_isTransactionalCacheEnabled()) {
106                                            return;
107                                    }
108    
109                                    Propagation propagation = transactionAttribute.getPropagation();
110    
111                                    if (propagation.value() >=
112                                                    TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
113    
114                                            List<List<PortalCacheMap>> backupPortalCacheMaps =
115                                                    _backupPortalCacheMapsThreadLocal.get();
116    
117                                            _portalCacheMapsThreadLocal.set(
118                                                    backupPortalCacheMaps.remove(
119                                                            backupPortalCacheMaps.size() - 1));
120                                    }
121                                    else if (transactionStatus.isNewTransaction()) {
122                                            rollback();
123    
124                                            EntityCacheUtil.clearLocalCache();
125                                            FinderCacheUtil.clearLocalCache();
126                                    }
127                            }
128    
129                    };
130    
131            public static void begin() {
132                    List<PortalCacheMap> portalCacheMaps =
133                            _portalCacheMapsThreadLocal.get();
134    
135                    portalCacheMaps.add(new PortalCacheMap());
136            }
137    
138            public static void commit() {
139                    PortalCacheMap portalCacheMap = _popPortalCacheMap();
140    
141                    for (Map.Entry
142                                    <PortalCache<? extends Serializable, ?>, UncommittedBuffer>
143                                            portalCacheMapEntry : portalCacheMap.entrySet()) {
144    
145                            PortalCache<Serializable, Object> portalCache =
146                                    (PortalCache<Serializable, Object>)portalCacheMapEntry.getKey();
147    
148                            UncommittedBuffer uncommittedBuffer =
149                                    portalCacheMapEntry.getValue();
150    
151                            uncommittedBuffer.commitTo(portalCache);
152                    }
153    
154                    portalCacheMap.clear();
155            }
156    
157            public static boolean isEnabled() {
158                    if (!_isTransactionalCacheEnabled()) {
159                            return false;
160                    }
161    
162                    List<PortalCacheMap> portalCacheMaps =
163                            _portalCacheMapsThreadLocal.get();
164    
165                    return !portalCacheMaps.isEmpty();
166            }
167    
168            public static void rollback() {
169                    PortalCacheMap portalCacheMap = _popPortalCacheMap();
170    
171                    portalCacheMap.clear();
172            }
173    
174            protected static <K extends Serializable, V> V get(
175                    PortalCache<K, V> portalCache, K key) {
176    
177                    PortalCacheMap portalCacheMap = _peekPortalCacheMap();
178    
179                    UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache);
180    
181                    if (uncommittedBuffer == null) {
182                            return null;
183                    }
184    
185                    ValueEntry valueEntry = uncommittedBuffer.get(key);
186    
187                    if (valueEntry == null) {
188                            return null;
189                    }
190    
191                    return (V)valueEntry._value;
192            }
193    
194            protected static <K extends Serializable, V> void put(
195                    PortalCache<K, V> portalCache, K key, V value, int ttl) {
196    
197                    PortalCacheMap portalCacheMap = _peekPortalCacheMap();
198    
199                    UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache);
200    
201                    if (uncommittedBuffer == null) {
202                            uncommittedBuffer = new UncommittedBuffer();
203    
204                            portalCacheMap.put(portalCache, uncommittedBuffer);
205                    }
206    
207                    uncommittedBuffer.put(
208                            key,
209                            new ValueEntry(
210                                    value, ttl, AggregatedPortalCacheListener.isRemoteInvoke()));
211            }
212    
213            protected static <K extends Serializable, V> void removeAll(
214                    PortalCache<K, V> portalCache) {
215    
216                    PortalCacheMap portalCacheMap = _peekPortalCacheMap();
217    
218                    UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache);
219    
220                    if (uncommittedBuffer == null) {
221                            uncommittedBuffer = new UncommittedBuffer();
222    
223                            portalCacheMap.put(portalCache, uncommittedBuffer);
224                    }
225    
226                    uncommittedBuffer.removeAll(
227                            AggregatedPortalCacheListener.isRemoteInvoke());
228            }
229    
230            protected static class PortalCacheMap
231                    extends HashMap
232                            <PortalCache<? extends Serializable, ?>, UncommittedBuffer> {
233            }
234    
235            private static boolean _isTransactionalCacheEnabled() {
236                    if (_transactionalCacheEnabled == null) {
237                            _transactionalCacheEnabled = GetterUtil.getBoolean(
238                                    PropsUtil.get(PropsKeys.TRANSACTIONAL_CACHE_ENABLED));
239                    }
240    
241                    return _transactionalCacheEnabled;
242            }
243    
244            private static PortalCacheMap _peekPortalCacheMap() {
245                    List<PortalCacheMap> portalCacheMaps =
246                            _portalCacheMapsThreadLocal.get();
247    
248                    return portalCacheMaps.get(portalCacheMaps.size() - 1);
249            }
250    
251            private static PortalCacheMap _popPortalCacheMap() {
252                    List<PortalCacheMap> portalCacheMaps =
253                            _portalCacheMapsThreadLocal.get();
254    
255                    return portalCacheMaps.remove(portalCacheMaps.size() - 1);
256            }
257    
258            private static final ValueEntry _NULL_HOLDER_VALUE_ENTRY = new ValueEntry(
259                    TransactionalPortalCache.NULL_HOLDER, PortalCache.DEFAULT_TIME_TO_LIVE,
260                    false);
261    
262            private static final ThreadLocal<List<List<PortalCacheMap>>>
263                    _backupPortalCacheMapsThreadLocal =
264                            new InitialThreadLocal<List<List<PortalCacheMap>>>(
265                                    TransactionalPortalCacheHelper.class.getName() +
266                                            "._backupPortalCacheMapsThreadLocal",
267                                    new ArrayList<List<PortalCacheMap>>());
268            private static final ThreadLocal<List<PortalCacheMap>>
269                    _portalCacheMapsThreadLocal =
270                            new InitialThreadLocal<List<PortalCacheMap>>(
271                                    TransactionalPortalCacheHelper.class.getName() +
272                                            "._portalCacheMapsThreadLocal",
273                                    new ArrayList<PortalCacheMap>());
274    
275            private volatile static Boolean _transactionalCacheEnabled;
276    
277            private static class UncommittedBuffer {
278    
279                    public void commitTo(PortalCache<Serializable, Object> portalCache) {
280                            if (_removeAll) {
281                                    if (_skipReplicator) {
282                                            PortalCacheHelperUtil.removeAllWithoutReplicator(
283                                                    portalCache);
284                                    }
285                                    else {
286                                            portalCache.removeAll();
287                                    }
288                            }
289    
290                            for (Map.Entry<? extends Serializable, ValueEntry> entry :
291                                            _uncommittedMap.entrySet()) {
292    
293                                    ValueEntry valueEntry = entry.getValue();
294    
295                                    valueEntry.commitTo(portalCache, entry.getKey());
296                            }
297                    }
298    
299                    public ValueEntry get(Serializable key) {
300                            ValueEntry valueEntry = _uncommittedMap.get(key);
301    
302                            if ((valueEntry == null) && _removeAll) {
303                                    valueEntry = _NULL_HOLDER_VALUE_ENTRY;
304                            }
305    
306                            return valueEntry;
307                    }
308    
309                    public void put(Serializable key, ValueEntry valueEntry) {
310                            ValueEntry oldValueEntry = _uncommittedMap.put(key, valueEntry);
311    
312                            if (oldValueEntry != null) {
313                                    oldValueEntry.merge(valueEntry);
314                            }
315                    }
316    
317                    public void removeAll(boolean skipReplicator) {
318                            _uncommittedMap.clear();
319    
320                            _removeAll = true;
321    
322                            if (_skipReplicator) {
323                                    _skipReplicator = skipReplicator;
324                            }
325                    }
326    
327                    private boolean _removeAll;
328                    private boolean _skipReplicator = true;
329                    private final Map<Serializable, ValueEntry> _uncommittedMap =
330                            new HashMap<>();
331    
332            }
333    
334            private static class ValueEntry {
335    
336                    public ValueEntry(Object value, int ttl, boolean skipReplicator) {
337                            _value = value;
338                            _ttl = ttl;
339                            _skipReplicator = skipReplicator;
340                    }
341    
342                    public void commitTo(
343                            PortalCache<Serializable, Object> portalCache, Serializable key) {
344    
345                            if (_value == TransactionalPortalCache.NULL_HOLDER) {
346                                    if (_skipReplicator) {
347                                            PortalCacheHelperUtil.removeWithoutReplicator(
348                                                    portalCache, key);
349                                    }
350                                    else {
351                                            portalCache.remove(key);
352                                    }
353                            }
354                            else {
355                                    if (_skipReplicator) {
356                                            PortalCacheHelperUtil.putWithoutReplicator(
357                                                    portalCache, key, _value, _ttl);
358                                    }
359                                    else {
360                                            portalCache.put(key, _value, _ttl);
361                                    }
362                            }
363                    }
364    
365                    public void merge(ValueEntry valueEntry) {
366                            if (!_skipReplicator) {
367                                    valueEntry._skipReplicator = false;
368                            }
369                    }
370    
371                    private boolean _skipReplicator;
372                    private final int _ttl;
373                    private final Object _value;
374    
375            }
376    
377    }