001    /**
002     * Copyright (c) 2000-2013 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.dao.orm.common;
016    
017    import com.liferay.portal.dao.shard.advice.ShardAdvice;
018    import com.liferay.portal.kernel.cache.CacheRegistryItem;
019    import com.liferay.portal.kernel.cache.CacheRegistryUtil;
020    import com.liferay.portal.kernel.cache.MultiVMPool;
021    import com.liferay.portal.kernel.cache.PortalCache;
022    import com.liferay.portal.kernel.dao.orm.EntityCache;
023    import com.liferay.portal.kernel.dao.orm.Session;
024    import com.liferay.portal.kernel.dao.orm.SessionFactory;
025    import com.liferay.portal.kernel.dao.shard.ShardUtil;
026    import com.liferay.portal.kernel.log.Log;
027    import com.liferay.portal.kernel.log.LogFactoryUtil;
028    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
029    import com.liferay.portal.kernel.util.AutoResetThreadLocal;
030    import com.liferay.portal.kernel.util.HashUtil;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.model.BaseModel;
033    import com.liferay.portal.model.CacheModel;
034    import com.liferay.portal.util.PropsValues;
035    
036    import java.io.Externalizable;
037    import java.io.IOException;
038    import java.io.ObjectInput;
039    import java.io.ObjectOutput;
040    import java.io.Serializable;
041    
042    import java.util.Map;
043    import java.util.concurrent.ConcurrentHashMap;
044    import java.util.concurrent.ConcurrentMap;
045    
046    import org.apache.commons.collections.map.LRUMap;
047    
048    import org.springframework.beans.factory.BeanFactory;
049    import org.springframework.beans.factory.BeanFactoryAware;
050    
051    /**
052     * @author Brian Wing Shun Chan
053     * @author Shuyang Zhou
054     */
055    @DoPrivileged
056    public class EntityCacheImpl
057            implements BeanFactoryAware, CacheRegistryItem, EntityCache {
058    
059            public static final String CACHE_NAME = EntityCache.class.getName();
060    
061            public void afterPropertiesSet() {
062                    CacheRegistryUtil.register(this);
063            }
064    
065            @Override
066            public void clearCache() {
067                    clearLocalCache();
068    
069                    for (PortalCache<?, ?> portalCache : _portalCaches.values()) {
070                            portalCache.removeAll();
071                    }
072            }
073    
074            @Override
075            public void clearCache(String className) {
076                    clearLocalCache();
077    
078                    PortalCache<?, ?> portalCache = _getPortalCache(className, true);
079    
080                    if (portalCache != null) {
081                            portalCache.removeAll();
082                    }
083            }
084    
085            @Override
086            public void clearLocalCache() {
087                    if (_localCacheAvailable) {
088                            _localCache.remove();
089                    }
090            }
091    
092            @Override
093            public PortalCache<Serializable, Serializable> getPortalCache(
094                    Class<?> clazz) {
095    
096                    return _getPortalCache(clazz.getName(), true);
097            }
098    
099            @Override
100            public String getRegistryName() {
101                    return CACHE_NAME;
102            }
103    
104            @Override
105            public Serializable getResult(
106                    boolean entityCacheEnabled, Class<?> clazz, Serializable primaryKey) {
107    
108                    if (!PropsValues.VALUE_OBJECT_ENTITY_CACHE_ENABLED ||
109                            !entityCacheEnabled || !CacheRegistryUtil.isActive()) {
110    
111                            return null;
112                    }
113    
114                    Serializable result = null;
115    
116                    Map<Serializable, Serializable> localCache = null;
117    
118                    Serializable localCacheKey = null;
119    
120                    if (_localCacheAvailable) {
121                            localCache = _localCache.get();
122    
123                            localCacheKey = _encodeLocalCacheKey(clazz, primaryKey);
124    
125                            result = localCache.get(localCacheKey);
126                    }
127    
128                    if (result == null) {
129                            PortalCache<Serializable, Serializable> portalCache =
130                                    _getPortalCache(clazz.getName(), true);
131    
132                            Serializable cacheKey = _encodeCacheKey(primaryKey);
133    
134                            result = portalCache.get(cacheKey);
135    
136                            if (result == null) {
137                                    result = StringPool.BLANK;
138                            }
139    
140                            if (_localCacheAvailable) {
141                                    localCache.put(localCacheKey, result);
142                            }
143                    }
144    
145                    return _toEntityModel(result);
146            }
147    
148            @Override
149            public void invalidate() {
150                    clearCache();
151            }
152    
153            @Override
154            public Serializable loadResult(
155                    boolean entityCacheEnabled, Class<?> clazz, Serializable primaryKey,
156                    SessionFactory sessionFactory) {
157    
158                    if (!PropsValues.VALUE_OBJECT_ENTITY_CACHE_ENABLED ||
159                            !entityCacheEnabled || !CacheRegistryUtil.isActive()) {
160    
161                            Session session = null;
162    
163                            try {
164                                    session = sessionFactory.openSession();
165    
166                                    return (Serializable)session.load(clazz, primaryKey);
167                            }
168                            finally {
169                                    sessionFactory.closeSession(session);
170                            }
171                    }
172    
173                    Serializable result = null;
174    
175                    Map<Serializable, Serializable> localCache = null;
176    
177                    Serializable localCacheKey = null;
178    
179                    if (_localCacheAvailable) {
180                            localCache = _localCache.get();
181    
182                            localCacheKey = _encodeLocalCacheKey(clazz, primaryKey);
183    
184                            result = localCache.get(localCacheKey);
185                    }
186    
187                    Serializable loadResult = null;
188    
189                    if (result == null) {
190                            PortalCache<Serializable, Serializable> portalCache =
191                                    _getPortalCache(clazz.getName(), true);
192    
193                            Serializable cacheKey = _encodeCacheKey(primaryKey);
194    
195                            result = portalCache.get(cacheKey);
196    
197                            if (result == null) {
198                                    if (_log.isDebugEnabled()) {
199                                            _log.debug(
200                                                    "Load " + clazz + " " + primaryKey + " from session");
201                                    }
202    
203                                    Session session = null;
204    
205                                    try {
206                                            session = sessionFactory.openSession();
207    
208                                            loadResult = (Serializable)session.load(clazz, primaryKey);
209                                    }
210                                    finally {
211                                            if (loadResult == null) {
212                                                    result = StringPool.BLANK;
213                                            }
214                                            else {
215                                                    result = ((BaseModel<?>)loadResult).toCacheModel();
216                                            }
217    
218                                            portalCache.put(cacheKey, result);
219    
220                                            sessionFactory.closeSession(session);
221                                    }
222                            }
223    
224                            if (_localCacheAvailable) {
225                                    localCache.put(localCacheKey, result);
226                            }
227                    }
228    
229                    if (loadResult != null) {
230                            return loadResult;
231                    }
232    
233                    return _toEntityModel(result);
234            }
235    
236            @Override
237            public void putResult(
238                    boolean entityCacheEnabled, Class<?> clazz, Serializable primaryKey,
239                    Serializable result) {
240    
241                    if (!PropsValues.VALUE_OBJECT_ENTITY_CACHE_ENABLED ||
242                            !entityCacheEnabled || !CacheRegistryUtil.isActive() ||
243                            (result == null)) {
244    
245                            return;
246                    }
247    
248                    result = ((BaseModel<?>)result).toCacheModel();
249    
250                    if (_localCacheAvailable) {
251                            Map<Serializable, Serializable> localCache = _localCache.get();
252    
253                            Serializable localCacheKey = _encodeLocalCacheKey(
254                                    clazz, primaryKey);
255    
256                            localCache.put(localCacheKey, result);
257                    }
258    
259                    PortalCache<Serializable, Serializable> portalCache = _getPortalCache(
260                            clazz.getName(), true);
261    
262                    Serializable cacheKey = _encodeCacheKey(primaryKey);
263    
264                    portalCache.put(cacheKey, result);
265            }
266    
267            @Override
268            public void removeCache(String className) {
269                    _portalCaches.remove(className);
270    
271                    String groupKey = _GROUP_KEY_PREFIX.concat(className);
272    
273                    _multiVMPool.removeCache(groupKey);
274            }
275    
276            @Override
277            public void removeResult(
278                    boolean entityCacheEnabled, Class<?> clazz, Serializable primaryKey) {
279    
280                    if (!PropsValues.VALUE_OBJECT_ENTITY_CACHE_ENABLED ||
281                            !entityCacheEnabled || !CacheRegistryUtil.isActive()) {
282    
283                            return;
284                    }
285    
286                    if (_localCacheAvailable) {
287                            Map<Serializable, Serializable> localCache = _localCache.get();
288    
289                            Serializable localCacheKey = _encodeLocalCacheKey(
290                                    clazz, primaryKey);
291    
292                            localCache.remove(localCacheKey);
293                    }
294    
295                    PortalCache<Serializable, Serializable> portalCache = _getPortalCache(
296                            clazz.getName(), true);
297    
298                    Serializable cacheKey = _encodeCacheKey(primaryKey);
299    
300                    portalCache.remove(cacheKey);
301            }
302    
303            @Override
304            public void setBeanFactory(BeanFactory beanFactory) {
305                    if (beanFactory.containsBean(ShardAdvice.class.getName())) {
306                            _shardEnabled = true;
307                    }
308            }
309    
310            public void setMultiVMPool(MultiVMPool multiVMPool) {
311                    _multiVMPool = multiVMPool;
312            }
313    
314            private Serializable _encodeCacheKey(Serializable primaryKey) {
315                    if (_shardEnabled) {
316                            return new CacheKey(ShardUtil.getCurrentShardName(), primaryKey);
317                    }
318    
319                    return primaryKey;
320            }
321    
322            private Serializable _encodeLocalCacheKey(
323                    Class<?> clazz, Serializable primaryKey) {
324    
325                    if (_shardEnabled) {
326                            return new ShardLocalCacheKey(
327                                    ShardUtil.getCurrentShardName(), clazz.getName(), primaryKey);
328                    }
329    
330                    return new LocalCacheKey(clazz.getName(), primaryKey);
331            }
332    
333            private PortalCache<Serializable, Serializable> _getPortalCache(
334                    String className, boolean createIfAbsent) {
335    
336                    PortalCache<Serializable, Serializable> portalCache = _portalCaches.get(
337                            className);
338    
339                    if ((portalCache == null) && createIfAbsent) {
340                            String groupKey = _GROUP_KEY_PREFIX.concat(className);
341    
342                            portalCache =
343                                    (PortalCache<Serializable, Serializable>)_multiVMPool.getCache(
344                                            groupKey, PropsValues.VALUE_OBJECT_ENTITY_BLOCKING_CACHE);
345    
346                            PortalCache<Serializable, Serializable> previousPortalCache =
347                                    _portalCaches.putIfAbsent(className, portalCache);
348    
349                            if (previousPortalCache != null) {
350                                    portalCache = previousPortalCache;
351                            }
352                    }
353    
354                    return portalCache;
355            }
356    
357            private Serializable _toEntityModel(Serializable result) {
358                    if (result == StringPool.BLANK) {
359                            return null;
360                    }
361    
362                    CacheModel<?> cacheModel = (CacheModel<?>)result;
363    
364                    BaseModel<?> entityModel = (BaseModel<?>)cacheModel.toEntityModel();
365    
366                    entityModel.setCachedModel(true);
367    
368                    return entityModel;
369            }
370    
371            private static final String _GROUP_KEY_PREFIX = CACHE_NAME.concat(
372                    StringPool.PERIOD);
373    
374            private static Log _log = LogFactoryUtil.getLog(EntityCacheImpl.class);
375    
376            private static ThreadLocal<LRUMap> _localCache;
377            private static boolean _localCacheAvailable;
378    
379            static {
380                    if (PropsValues.VALUE_OBJECT_ENTITY_THREAD_LOCAL_CACHE_MAX_SIZE > 0) {
381                            _localCache = new AutoResetThreadLocal<LRUMap>(
382                                    EntityCacheImpl.class + "._localCache",
383                                    new LRUMap(
384                                            PropsValues.
385                                                    VALUE_OBJECT_ENTITY_THREAD_LOCAL_CACHE_MAX_SIZE));
386                            _localCacheAvailable = true;
387                    }
388            }
389    
390            private MultiVMPool _multiVMPool;
391            private ConcurrentMap<String, PortalCache<Serializable, Serializable>>
392                    _portalCaches =
393                            new ConcurrentHashMap
394                                    <String, PortalCache<Serializable, Serializable>>();
395            private boolean _shardEnabled;
396    
397            private static class CacheKey implements Externalizable {
398    
399                    @SuppressWarnings("unused")
400                    public CacheKey() {
401                    }
402    
403                    public CacheKey(String shardName, Serializable primaryKey) {
404                            _shardName = shardName;
405                            _primaryKey = primaryKey;
406                    }
407    
408                    @Override
409                    public boolean equals(Object obj) {
410                            CacheKey cacheKey = (CacheKey)obj;
411    
412                            if (cacheKey._shardName.equals(_shardName) &&
413                                    cacheKey._primaryKey.equals(_primaryKey)) {
414    
415                                    return true;
416                            }
417    
418                            return false;
419                    }
420    
421                    @Override
422                    public int hashCode() {
423                            return _shardName.hashCode() * 11 + _primaryKey.hashCode();
424                    }
425    
426                    @Override
427                    public void readExternal(ObjectInput objectInput)
428                            throws ClassNotFoundException, IOException {
429    
430                            _primaryKey = (Serializable)objectInput.readObject();
431                            _shardName = objectInput.readUTF();
432                    }
433    
434                    @Override
435                    public void writeExternal(ObjectOutput objectOutput)
436                            throws IOException {
437    
438                            objectOutput.writeObject(_primaryKey);
439                            objectOutput.writeUTF(_shardName);
440                    }
441    
442                    private static final long serialVersionUID = 1L;
443    
444                    private Serializable _primaryKey;
445                    private String _shardName;
446    
447            }
448    
449            private static class LocalCacheKey implements Serializable {
450    
451                    public LocalCacheKey(String className, Serializable primaryKey) {
452                            _className = className;
453                            _primaryKey = primaryKey;
454                    }
455    
456                    @Override
457                    public boolean equals(Object obj) {
458                            LocalCacheKey localCacheKey = (LocalCacheKey)obj;
459    
460                            if (localCacheKey._className.equals(_className) &&
461                                    localCacheKey._primaryKey.equals(_primaryKey)) {
462    
463                                    return true;
464                            }
465    
466                            return false;
467                    }
468    
469                    @Override
470                    public int hashCode() {
471                            return _className.hashCode() * 11 + _primaryKey.hashCode();
472                    }
473    
474                    private static final long serialVersionUID = 1L;
475    
476                    private final String _className;
477                    private final Serializable _primaryKey;
478    
479            }
480    
481            private static class ShardLocalCacheKey implements Serializable {
482    
483                    public ShardLocalCacheKey(
484                            String shardName, String className, Serializable primaryKey) {
485    
486                            _shardName = shardName;
487                            _className = className;
488                            _primaryKey = primaryKey;
489                    }
490    
491                    @Override
492                    public boolean equals(Object obj) {
493                            ShardLocalCacheKey shardLocalCacheKey = (ShardLocalCacheKey)obj;
494    
495                            if (shardLocalCacheKey._shardName.equals(_shardName) &&
496                                    shardLocalCacheKey._className.equals(_className) &&
497                                    shardLocalCacheKey._primaryKey.equals(_primaryKey)) {
498    
499                                    return true;
500                            }
501    
502                            return false;
503                    }
504    
505                    @Override
506                    public int hashCode() {
507                            int hashCode = HashUtil.hash(0, _shardName);
508    
509                            hashCode = HashUtil.hash(hashCode, _className);
510                            hashCode = HashUtil.hash(hashCode, _primaryKey);
511    
512                            return hashCode;
513                    }
514    
515                    private static final long serialVersionUID = 1L;
516    
517                    private final String _className;
518                    private final Serializable _primaryKey;
519                    private final String _shardName;
520    
521            }
522    
523    }