com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate.HibernateRepositoryCache.java Source code

Java tutorial

Introduction

Here is the source code for com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate.HibernateRepositoryCache.java

Source

/*
 * Copyright (C) 2005 - 2014 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;

import com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate.util.RepositoryCacheIndicator;
import net.sf.jasperreports.engine.util.Pair;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateAccessor;
import org.springframework.orm.hibernate3.HibernateCallback;

import com.jaspersoft.jasperserver.api.common.domain.ExecutionContext;
import com.jaspersoft.jasperserver.api.metadata.common.domain.FileResource;
import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryCache;
import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryCacheableItem;
import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryService;
import com.jaspersoft.jasperserver.api.metadata.common.service.impl.HibernateDaoImpl;
import com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate.persistent.CachedItem;
import com.jaspersoft.jasperserver.api.metadata.common.util.LocalLockManager;
import com.jaspersoft.jasperserver.api.metadata.common.util.LockHandle;
import com.jaspersoft.jasperserver.api.metadata.common.util.LockManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionDefinition;

/**
 * @author Lucian Chirita (lucianc@users.sourceforge.net)
 * @version $Id: HibernateRepositoryCache.java 47331 2014-07-18 09:13:06Z kklein $
 */
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class HibernateRepositoryCache extends HibernateDaoImpl implements RepositoryCache {
    private static final Log log = LogFactory.getLog(HibernateRepositoryCache.class);

    private RepositoryService repository;
    private LockManager lockManager = new LocalLockManager();
    private boolean setFindByCriteriaToReadOnly = false;
    private AbstractPlatformTransactionManager transactionManager;

    public HibernateRepositoryCache() {
    }

    public RepositoryService getRepository() {
        return repository;
    }

    public void setRepository(RepositoryService repository) {
        this.repository = repository;
    }

    public void setTransactionManager(AbstractPlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public boolean getFindByCriteriaToReadOnly() {
        return setFindByCriteriaToReadOnly;
    }

    public void setFindByCriteriaToReadOnly(boolean setFindByCriteriaToReadOnly) {
        this.setFindByCriteriaToReadOnly = setFindByCriteriaToReadOnly;
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public InputStream cache(ExecutionContext context, FileResource resource,
            RepositoryCacheableItem cacheableItem) {
        if (isCachingOn()) {
            CachedItem cachedItem = getCachedItem(context, resource, cacheableItem);
            while (cachedItem.isItemReference()) {
                cachedItem = cachedItem.getReference();
            }
            return new ByteArrayInputStream(cachedItem.getDataBytes());
        } else {
            return new ByteArrayInputStream(cacheableItem.getData(context, resource));
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public InputStream cache(ExecutionContext context, String uri, RepositoryCacheableItem cacheableItem) {
        FileResource resource = (FileResource) repository.getResource(context, uri);
        return cache(context, resource, cacheableItem);
    }

    protected LockHandle lock(FileResource resource, RepositoryCacheableItem cacheableItem) {
        return lockManager.lock(cacheableItem.getCacheName(), resource.getURIString());
    }

    protected void unlock(LockHandle lock) {
        lockManager.unlock(lock);
    }

    protected Pair getLockKey(FileResource resource, RepositoryCacheableItem cacheableItem) {
        return new Pair(resource.getURIString(), cacheableItem.getCacheName());
    }

    protected CachedItem getCachedItem(ExecutionContext context, FileResource resource,
            RepositoryCacheableItem cacheableItem) {
        if (log.isDebugEnabled()) {
            log.debug("Looking in repository cache \"" + cacheableItem.getCacheName() + "\" for resource \""
                    + resource.getURIString() + "\", version " + resource.getVersion() + "\", version date "
                    + resource.getCreationDate());
        }

        LockHandle lock = lock(resource, cacheableItem);

        //Because of bug #25373 we need to manually manipulate with transaction:
        //open it AFTER thread acquires lock
        //and commit BEFORE thread releases the lock.
        TransactionStatus transaction = null;

        try {
            transaction = transactionManager.getTransaction(
                    new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));

            CachedItem cachedItem = getCachedItem(resource.getURIString(), cacheableItem, true);
            if (cachedItem == null || cachedItem.getVersion() < resource.getVersion()
                    || cachedItem.getVersionDate() == null
                    || cachedItem.getVersionDate().before(resource.getCreationDate())) {
                if (resource.isReference()) {
                    cachedItem = saveRefence(context, resource, cachedItem, cacheableItem);
                } else {
                    cachedItem = saveData(context, resource, cachedItem, cacheableItem);
                }
            } else if (resource.isReference()) {
                //load the reference to force updates
                FileResource ref = (FileResource) repository.getResource(context, resource.getReferenceURI());
                CachedItem refItem = getCachedItem(context, ref, cacheableItem);
                if (!refItem.equals(cachedItem.getReference())) {
                    updateReference(cachedItem, refItem);
                }
            }

            transactionManager.commit(transaction);
            return cachedItem;
        } finally {
            if (transaction != null && !transaction.isCompleted()) {
                try {
                    transactionManager.rollback(transaction);
                } catch (Exception e) {
                    // suppress exception throwing
                    log.error(e.getMessage(), e);
                }
            }

            unlock(lock);
        }
    }

    protected CachedItem getCachedItem(String uri, RepositoryCacheableItem cacheableItem) {
        return getCachedItem(uri, cacheableItem, false, null);
    }

    protected CachedItem getCachedItem(String uri, RepositoryCacheableItem cacheableItem, Session session) {
        return getCachedItem(uri, cacheableItem, false, session);
    }

    protected CachedItem getCachedItem(String uri, RepositoryCacheableItem cacheableItem,
            boolean clearPendingSavesCreatedByFindByCriteria) {
        return getCachedItem(uri, cacheableItem, clearPendingSavesCreatedByFindByCriteria, null);
    }

    protected CachedItem getCachedItem(String uri, RepositoryCacheableItem cacheableItem,
            boolean clearPendingSavesCreatedByFindByCriteria, Session session) {
        if (log.isDebugEnabled()) {
            log.debug("HibernateRepositoryCache:  Looking in repository cache \"" + cacheableItem.getCacheName()
                    + "\" for resource \"" + uri);
        }

        DetachedCriteria criteria = DetachedCriteria.forClass(CachedItem.class);
        criteria.add(Restrictions.naturalId().set("cacheName", cacheableItem.getCacheName()).set("uri", uri));
        criteria.getExecutableCriteria(getSession()).setCacheable(true);
        List list = null;

        if (setFindByCriteriaToReadOnly) {
            // flush all pending saves, updates and deletes to the database (However, it may slows down the performance, since we have to synchronize the method
            list = findByCritera(criteria, clearPendingSavesCreatedByFindByCriteria);
        } else {
            // no synchronization, but spring hibernate template creates an extra update statement when calling findByCriteria
            if (session == null)
                list = getHibernateTemplate().findByCriteria(criteria);
            else
                list = criteria.getExecutableCriteria(session).list();
        }

        CachedItem item = null;
        if (list.isEmpty()) {
            if (log.isDebugEnabled()) {
                log.debug("did not find \"" + uri + "\" in cache " + cacheableItem.getCacheName());
            }
            item = null;
        } else {
            item = (CachedItem) list.get(0);
            if (log.isDebugEnabled()) {
                log.debug("found \"" + uri + "\" in cache " + cacheableItem.getCacheName() + ". isReference: "
                        + item.isItemReference());
            }
        }
        return item;
    }

    /**
     * whenever we made a hibernate call, findByCriteria(...) for JIRepositoryCache table, it would actually execute 2 SQL statements.
     * One is a SELECT statement and the other one is an UPDATE statement
     * SOLUTION:  1) FLUSH ALL THE PENDING SAVES FIRST
     *            2) SET FLUSH MODE TO NEVER AND CALL FINDBYCRITERIA()
     *            3) CLEAR ALL THE PENDING SAVES CREATED BY FINDBYCRITERIA()
     *            4) RESET THE FLUSH MODE
     * @param criteria searching criteria
     * @param clearPendingSavesCreatedByFindByCriteria remove the "extra" update statement that caused by spring findByCriteria() method
     * @return search result
     */
    private synchronized List findByCritera(DetachedCriteria criteria,
            boolean clearPendingSavesCreatedByFindByCriteria) {
        if (clearPendingSavesCreatedByFindByCriteria) {
            throw new RuntimeException("Unsafe hibernate code blocked");
            /*
            // flush all pending saves, updates and deletes to the database
             getHibernateTemplate().flush();
             // take a snapshot of original flush mode
             int flushMode = getHibernateTemplate().getFlushMode();
             // set flush mode to read only:  to avoid unnecessary write or update that is called by findByCriteria(...)
             getHibernateTemplate().setFlushMode(HibernateAccessor.FLUSH_NEVER);
             List list = getHibernateTemplate().findByCriteria(criteria);
             try {
            // remove all objects from the session cache, and cancel all pending saves, updates, and deletes
                getHibernateTemplate().clear();
             } catch (Exception ex) {
            if (log.isDebugEnabled()) {
                log.debug("HibernateRepositoryCache:  Fail to cancel all pending saves, updates, and deletes");
            }
             }
             // set it back to original flush mode
             getHibernateTemplate().setFlushMode(flushMode);
             return list;*/
        } else {
            return getHibernateTemplate().findByCriteria(criteria);
        }
    }

    protected CachedItem saveRefence(ExecutionContext context, FileResource resource, CachedItem item,
            RepositoryCacheableItem cacheableItem) {
        FileResource ref = (FileResource) repository.getResource(context, resource.getReferenceURI());
        CachedItem refItem = getCachedItem(context, ref, cacheableItem);

        CachedItem saveItem;
        if (item == null) {
            saveItem = new CachedItem();
        } else {
            saveItem = item;
        }
        saveItem.setCacheName(cacheableItem.getCacheName());
        saveItem.setData(null);
        saveItem.setReference(refItem);
        saveItem.setUri(resource.getURIString());
        saveItem.setVersion(resource.getVersion());
        saveItem.setVersionDate(resource.getCreationDate());

        if (item == null) {
            getHibernateTemplate().save(saveItem);
        } else {
            getHibernateTemplate().update(saveItem);
        }

        return saveItem;
    }

    protected CachedItem saveData(ExecutionContext context, FileResource resource, CachedItem item,
            RepositoryCacheableItem cacheableItem) {
        if (log.isDebugEnabled()) {
            log.debug("Saving repository cache \"" + cacheableItem.getCacheName() + "\" for resource \""
                    + resource.getURIString() + "\", version " + resource.getVersion() + "\", version date "
                    + resource.getCreationDate());
        }

        byte[] data = cacheableItem.getData(context, resource);

        CachedItem saveItem;
        if (item == null) {
            saveItem = new CachedItem();
        } else {
            saveItem = item;
        }
        saveItem.setCacheName(cacheableItem.getCacheName());
        saveItem.setDataBytes(data);
        saveItem.setReference(null);
        saveItem.setUri(resource.getURIString());
        saveItem.setVersion(resource.getVersion());
        saveItem.setVersionDate(resource.getCreationDate());

        if (item == null) {
            getHibernateTemplate().save(saveItem);
        } else {
            getHibernateTemplate().update(saveItem);
        }

        return saveItem;
    }

    protected void updateReference(CachedItem item, CachedItem refItem) {
        item.setReference(refItem);
        getHibernateTemplate().update(item);
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void clearCache(final String uri, final RepositoryCacheableItem cacheableItem) {
        if (isCachingOn()) {
            executeWriteCallback(new DaoCallback() {
                public Object execute() {
                    removeCached(uri, cacheableItem);
                    return null;
                }
            }, false);
        }
    }

    protected void removeCached(String uri, RepositoryCacheableItem cacheableItem) {
        if (log.isDebugEnabled()) {
            log.debug("Clearing cache " + cacheableItem.getCacheName() + " for resource " + uri);
        }

        getHibernateTemplate().executeWithNewSession(new RemoveCachedCallback(uri, cacheableItem));
    }

    class RemoveCachedCallback implements HibernateCallback<Object> {
        String uri;
        RepositoryCacheableItem cacheableItem;

        public RemoveCachedCallback(String uri, RepositoryCacheableItem cacheableItem) {
            this.uri = uri;
            this.cacheableItem = cacheableItem;
        }

        public Object doInHibernate(Session session) {
            CachedItem cachedItem = getCachedItem(uri, cacheableItem, session);
            if (cachedItem != null) {
                session.delete(cachedItem);
            }
            return null;
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void clearCache(final RepositoryCacheableItem cacheableItem) {
        if (isCachingOn()) {
            executeWriteCallback(new DaoCallback() {
                public Object execute() {
                    removeCached(cacheableItem);
                    return null;
                }
            }, false);
        }
    }

    protected void removeCached(RepositoryCacheableItem cacheableItem) {
        if (log.isDebugEnabled()) {
            log.debug("Clearing entire cache " + cacheableItem.getCacheName());
        }

        getHibernateTemplate().bulkUpdate("delete CachedItem where cacheName = ?", cacheableItem.getCacheName());
    }

    public LockManager getLockManager() {
        return lockManager;
    }

    public void setLockManager(LockManager lockManager) {
        this.lockManager = lockManager;
    }

    private boolean isCachingOn() {
        return RepositoryCacheIndicator.isOn();
    }
}