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.PortalCache;
018    import com.liferay.portal.kernel.cache.PortalCacheHelperUtil;
019    import com.liferay.portal.kernel.cache.SkipReplicationThreadLocal;
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 <K extends Serializable, V> V get(
158                    PortalCache<K, V> portalCache, K key) {
159    
160                    PortalCacheMap portalCacheMap = _peekPortalCacheMap();
161    
162                    UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache);
163    
164                    if (uncommittedBuffer == null) {
165                            return null;
166                    }
167    
168                    ValueEntry valueEntry = uncommittedBuffer.get(key);
169    
170                    if (valueEntry == null) {
171                            return null;
172                    }
173    
174                    return (V)valueEntry._value;
175            }
176    
177            public static Serializable getNullHolder() {
178                    return _NULL_HOLDER;
179            }
180    
181            public static boolean isEnabled() {
182                    if (!_isTransactionalCacheEnabled()) {
183                            return false;
184                    }
185    
186                    List<PortalCacheMap> portalCacheMaps =
187                            _portalCacheMapsThreadLocal.get();
188    
189                    return !portalCacheMaps.isEmpty();
190            }
191    
192            public static <K extends Serializable, V> void put(
193                    PortalCache<K, V> portalCache, K key, V value, int ttl) {
194    
195                    PortalCacheMap portalCacheMap = _peekPortalCacheMap();
196    
197                    UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache);
198    
199                    if (uncommittedBuffer == null) {
200                            uncommittedBuffer = new UncommittedBuffer();
201    
202                            portalCacheMap.put(portalCache, uncommittedBuffer);
203                    }
204    
205                    uncommittedBuffer.put(
206                            key,
207                            new ValueEntry(value, ttl, SkipReplicationThreadLocal.isEnabled()));
208            }
209    
210            public static <K extends Serializable, V> void removeAll(
211                    PortalCache<K, V> portalCache) {
212    
213                    PortalCacheMap portalCacheMap = _peekPortalCacheMap();
214    
215                    UncommittedBuffer uncommittedBuffer = portalCacheMap.get(portalCache);
216    
217                    if (uncommittedBuffer == null) {
218                            uncommittedBuffer = new UncommittedBuffer();
219    
220                            portalCacheMap.put(portalCache, uncommittedBuffer);
221                    }
222    
223                    uncommittedBuffer.removeAll(SkipReplicationThreadLocal.isEnabled());
224            }
225    
226            public static void rollback() {
227                    PortalCacheMap portalCacheMap = _popPortalCacheMap();
228    
229                    portalCacheMap.clear();
230            }
231    
232            protected static class PortalCacheMap
233                    extends HashMap
234                            <PortalCache<? extends Serializable, ?>, UncommittedBuffer> {
235            }
236    
237            private static boolean _isTransactionalCacheEnabled() {
238                    if (_transactionalCacheEnabled == null) {
239                            _transactionalCacheEnabled = GetterUtil.getBoolean(
240                                    PropsUtil.get(PropsKeys.TRANSACTIONAL_CACHE_ENABLED));
241                    }
242    
243                    return _transactionalCacheEnabled;
244            }
245    
246            private static PortalCacheMap _peekPortalCacheMap() {
247                    List<PortalCacheMap> portalCacheMaps =
248                            _portalCacheMapsThreadLocal.get();
249    
250                    return portalCacheMaps.get(portalCacheMaps.size() - 1);
251            }
252    
253            private static PortalCacheMap _popPortalCacheMap() {
254                    List<PortalCacheMap> portalCacheMaps =
255                            _portalCacheMapsThreadLocal.get();
256    
257                    return portalCacheMaps.remove(portalCacheMaps.size() - 1);
258            }
259    
260            private static final Serializable _NULL_HOLDER = "NULL_HOLDER";
261    
262            private static final ValueEntry _NULL_HOLDER_VALUE_ENTRY = new ValueEntry(
263                    _NULL_HOLDER, PortalCache.DEFAULT_TIME_TO_LIVE, false);
264    
265            private static final ThreadLocal<List<List<PortalCacheMap>>>
266                    _backupPortalCacheMapsThreadLocal =
267                            new InitialThreadLocal<List<List<PortalCacheMap>>>(
268                                    TransactionalPortalCacheHelper.class.getName() +
269                                            "._backupPortalCacheMapsThreadLocal",
270                                    new ArrayList<List<PortalCacheMap>>());
271            private static final ThreadLocal<List<PortalCacheMap>>
272                    _portalCacheMapsThreadLocal =
273                            new InitialThreadLocal<List<PortalCacheMap>>(
274                                    TransactionalPortalCacheHelper.class.getName() +
275                                            "._portalCacheMapsThreadLocal",
276                                    new ArrayList<PortalCacheMap>());
277    
278            private volatile static Boolean _transactionalCacheEnabled;
279    
280            private static class UncommittedBuffer {
281    
282                    public void commitTo(PortalCache<Serializable, Object> portalCache) {
283                            if (_removeAll) {
284                                    if (_skipReplicator) {
285                                            PortalCacheHelperUtil.removeAllWithoutReplicator(
286                                                    portalCache);
287                                    }
288                                    else {
289                                            portalCache.removeAll();
290                                    }
291                            }
292    
293                            for (Map.Entry<? extends Serializable, ValueEntry> entry :
294                                            _uncommittedMap.entrySet()) {
295    
296                                    ValueEntry valueEntry = entry.getValue();
297    
298                                    valueEntry.commitTo(portalCache, entry.getKey());
299                            }
300                    }
301    
302                    public ValueEntry get(Serializable key) {
303                            ValueEntry valueEntry = _uncommittedMap.get(key);
304    
305                            if ((valueEntry == null) && _removeAll) {
306                                    valueEntry = _NULL_HOLDER_VALUE_ENTRY;
307                            }
308    
309                            return valueEntry;
310                    }
311    
312                    public void put(Serializable key, ValueEntry valueEntry) {
313                            ValueEntry oldValueEntry = _uncommittedMap.put(key, valueEntry);
314    
315                            if (oldValueEntry != null) {
316                                    oldValueEntry.merge(valueEntry);
317                            }
318                    }
319    
320                    public void removeAll(boolean skipReplicator) {
321                            _uncommittedMap.clear();
322    
323                            _removeAll = true;
324    
325                            if (_skipReplicator) {
326                                    _skipReplicator = skipReplicator;
327                            }
328                    }
329    
330                    private boolean _removeAll;
331                    private boolean _skipReplicator = true;
332                    private final Map<Serializable, ValueEntry> _uncommittedMap =
333                            new HashMap<>();
334    
335            }
336    
337            private static class ValueEntry {
338    
339                    public ValueEntry(Object value, int ttl, boolean skipReplicator) {
340                            _value = value;
341                            _ttl = ttl;
342                            _skipReplicator = skipReplicator;
343                    }
344    
345                    public void commitTo(
346                            PortalCache<Serializable, Object> portalCache, Serializable key) {
347    
348                            if (_value == _NULL_HOLDER) {
349                                    if (_skipReplicator) {
350                                            PortalCacheHelperUtil.removeWithoutReplicator(
351                                                    portalCache, key);
352                                    }
353                                    else {
354                                            portalCache.remove(key);
355                                    }
356                            }
357                            else {
358                                    if (_skipReplicator) {
359                                            PortalCacheHelperUtil.putWithoutReplicator(
360                                                    portalCache, key, _value, _ttl);
361                                    }
362                                    else {
363                                            portalCache.put(key, _value, _ttl);
364                                    }
365                            }
366                    }
367    
368                    public void merge(ValueEntry valueEntry) {
369                            if (!_skipReplicator) {
370                                    valueEntry._skipReplicator = false;
371                            }
372                    }
373    
374                    private boolean _skipReplicator;
375                    private final int _ttl;
376                    private final Object _value;
377    
378            }
379    
380    }