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