/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2002-2016 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.platform.plugin.cache;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.event.CacheEventListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.ILogoutListener;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.libraries.repository.ContentIOException;
import org.pentaho.reporting.platform.plugin.output.ReportOutputHandler;
/**
* This cache stores the report-output-handler on a map inside the user's session.
*
* @author Thomas Morgner.
*/
public class DefaultReportCache implements ReportCache {
private static String SESSION_ATTRIBUTE = DefaultReportCache.class.getName() + "-Cache";
private static String LISTENER_ADDED_ATTRIBUTE = DefaultReportCache.class.getName() + "-ListenerAdded";
private static final String CACHE_NAME = "report-output-handlers";
private static final Log logger = LogFactory.getLog( DefaultReportCache.class );
private static class LogoutHandler implements ILogoutListener {
private LogoutHandler() {
}
public void onLogout( final IPentahoSession session ) {
logger.debug( "Shutting down session " + session.getId() );
final Object attribute = session.getAttribute( SESSION_ATTRIBUTE );
if ( attribute instanceof CacheManager == false ) {
logger.debug( "Shutting down session " + session.getId() + ": No cache manager found" );
return;
}
final CacheManager manager = (CacheManager) attribute;
if ( manager.cacheExists( CACHE_NAME ) ) {
final Cache cache = manager.getCache( CACHE_NAME );
// noinspection unchecked
final List<Object> keys = new ArrayList<Object>( cache.getKeys() );
logger.debug( "Shutting down session " + session.getId() + ": Closing " + keys.size() + " open reports." );
for ( final Object key : keys ) {
// remove also closes the cache-holder and thus the report (if the report is no longer in use).
cache.remove( key );
}
}
logger.debug( "Shutting down session " + session.getId() + ": Closing cache manager." );
manager.shutdown();
}
}
private static class CacheHolder {
private ReportCacheKey realKey;
private ReportOutputHandler outputHandler;
private boolean closed;
private boolean reportInUse;
private boolean reportInCache;
private CacheHolder( final ReportCacheKey realKey, final ReportOutputHandler outputHandler ) {
if ( outputHandler == null ) {
throw new NullPointerException();
}
if ( realKey == null ) {
throw new NullPointerException();
}
this.reportInCache = true;
this.realKey = realKey;
this.outputHandler = outputHandler;
}
public synchronized void markEvicted() {
reportInCache = false;
}
public boolean isReportInCache() {
return reportInCache;
}
public ReportCacheKey getRealKey() {
return realKey;
}
public ReportOutputHandler getOutputHandler() {
return outputHandler;
}
public synchronized void close() {
if ( reportInUse && reportInCache ) {
return;
}
if ( closed == false ) {
outputHandler.close();
closed = true;
}
}
public synchronized void setReportInUse( final boolean reportInUse ) {
this.reportInUse = reportInUse;
}
}
private static class CacheEvictionHandler implements CacheEventListener {
private CacheEvictionHandler() {
}
public void notifyElementRemoved( final Ehcache ehcache, final Element element ) throws CacheException {
final Object o = element.getObjectValue();
if ( o instanceof CacheHolder == false ) {
return;
}
final CacheHolder cacheHolder = (CacheHolder) o;
cacheHolder.close();
}
public void notifyElementPut( final Ehcache ehcache, final Element element ) throws CacheException {
}
public void notifyElementUpdated( final Ehcache ehcache, final Element element ) throws CacheException {
}
public void notifyElementExpired( final Ehcache ehcache, final Element element ) {
final Object o = element.getObjectValue();
if ( o instanceof CacheHolder == false ) {
return;
}
final CacheHolder cacheHolder = (CacheHolder) o;
logger.debug( "Shutting down report on element-expired event " + cacheHolder.getRealKey().getSessionId() );
cacheHolder.close();
}
/**
* This method is called when a element is automatically removed from the cache. We then clear it here.
*
* @param ehcache
* @param element
*/
public void notifyElementEvicted( final Ehcache ehcache, final Element element ) {
final Object o = element.getObjectValue();
if ( o instanceof CacheHolder == false ) {
return;
}
final CacheHolder cacheHolder = (CacheHolder) o;
cacheHolder.markEvicted();
logger.debug( "Shutting down report on element-evicted event " + cacheHolder.getRealKey().getSessionId() );
cacheHolder.close();
}
public void notifyRemoveAll( final Ehcache ehcache ) {
// could be that we are to late already here, the javadoc is not very clear on this one ..
// noinspection unchecked
final List keys = new ArrayList( ehcache.getKeys() );
for ( final Object key : keys ) {
final Element element = ehcache.get( key );
final Object o = element.getValue();
if ( o instanceof CacheHolder ) {
final CacheHolder cacheHolder = (CacheHolder) o;
cacheHolder.markEvicted();
logger.debug( "Shutting down report on remove-all event " + cacheHolder.getRealKey().getSessionId() );
cacheHolder.close();
}
}
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void dispose() {
}
}
private static class CachedReportOutputHandler implements ReportOutputHandler {
private CacheHolder parent;
private CachedReportOutputHandler( final CacheHolder parent ) {
this.parent = parent;
this.parent.setReportInUse( true );
}
public int paginate( final MasterReport report, final int yieldRate ) throws ReportProcessingException,
IOException, ContentIOException {
return parent.getOutputHandler().paginate( report, yieldRate );
}
public int generate( final MasterReport report, final int acceptedPage, final OutputStream outputStream,
final int yieldRate ) throws ReportProcessingException, IOException, ContentIOException {
return parent.getOutputHandler().generate( report, acceptedPage, outputStream, yieldRate );
}
public boolean supportsPagination() {
return parent.getOutputHandler().supportsPagination();
}
public void close() {
this.parent.setReportInUse( false );
if ( this.parent.isReportInCache() == false ) {
this.parent.close();
}
}
public Object getReportLock() {
return parent.getOutputHandler().getReportLock();
}
}
public DefaultReportCache() {
final IPentahoSession session = PentahoSessionHolder.getSession();
synchronized ( session ) {
if ( Boolean.TRUE.equals( session.getAttribute( LISTENER_ADDED_ATTRIBUTE ) ) ) {
return;
}
PentahoSystem.addLogoutListener( new LogoutHandler() );
session.setAttribute( LISTENER_ADDED_ATTRIBUTE, Boolean.TRUE );
}
}
public ReportOutputHandler get( final ReportCacheKey key ) {
if ( key.getSessionId() == null ) {
return null;
}
final IPentahoSession session = PentahoSessionHolder.getSession();
logger.debug( "id: " + session.getId() + " - Cache.get(..) started" );
synchronized ( session ) {
final Object attribute = session.getAttribute( SESSION_ATTRIBUTE );
if ( attribute instanceof CacheManager == false ) {
logger.debug( "id: " + session.getId() + " - Cache.get(..): No cache manager" );
return null;
}
final CacheManager manager = (CacheManager) attribute;
if ( manager.cacheExists( CACHE_NAME ) == false ) {
logger.debug( "id: " + session.getId() + " - Cache.get(..): No cache registered" );
return null;
}
final Cache cache = manager.getCache( CACHE_NAME );
final Element element = cache.get( key.getSessionId() );
if ( element == null ) {
logger
.debug( "id: " + session.getId() + " - Cache.get(..): No element in cache for key: " + key.getSessionId() );
return null;
}
final Object o = element.getObjectValue();
if ( o instanceof CacheHolder == false ) {
logger.debug( "id: " + session.getId() + " - Cache.get(..): No valid element in cache for key: "
+ key.getSessionId() );
return null;
}
final CacheHolder cacheHolder = (CacheHolder) o;
if ( cacheHolder.getRealKey().equals( key ) == false ) {
logger.debug( "id: " + session.getId() + " - Cache.get(..): remove stale report after parameter changed: "
+ key.getSessionId() );
cache.remove( key.getSessionId() );
return null;
}
logger.debug( "id: " + session.getId() + " - Cache.get(..): Returning cached instance for key: "
+ key.getSessionId() );
return new CachedReportOutputHandler( cacheHolder );
}
}
public ReportOutputHandler put( final ReportCacheKey key, final ReportOutputHandler report ) {
if ( key.getSessionId() == null ) {
return report;
}
if ( report instanceof CachedReportOutputHandler ) {
throw new IllegalStateException();
}
final IPentahoSession session = PentahoSessionHolder.getSession();
logger.debug( "id: " + session.getId() + " - Cache.put(..) started" );
synchronized ( session ) {
final Object attribute = session.getAttribute( SESSION_ATTRIBUTE );
final CacheManager manager;
if ( attribute instanceof CacheManager == false ) {
logger.debug( "id: " + session.getId() + " - Cache.put(..): No cache manager; creating one" );
manager = CacheManager.create();
session.setAttribute( SESSION_ATTRIBUTE, manager );
} else {
manager = (CacheManager) attribute;
}
if ( manager.cacheExists( CACHE_NAME ) == false ) {
logger.debug( "id: " + session.getId() + " - Cache.put(..): No cache registered with manager; creating one" );
manager.addCache( CACHE_NAME );
final Cache cache = manager.getCache( CACHE_NAME );
cache.getCacheEventNotificationService().registerListener( new CacheEvictionHandler() );
}
final Cache cache = manager.getCache( CACHE_NAME );
final Element element = cache.get( key.getSessionId() );
if ( element != null ) {
final Object o = element.getObjectValue();
if ( o instanceof CacheHolder ) {
final CacheHolder cacheHolder = (CacheHolder) o;
if ( cacheHolder.getRealKey().equals( key ) == false ) {
logger.debug( "id: " + session.getId() + " - Cache.put(..): Removing stale object." );
cache.remove( key.getSessionId() );
} else {
// otherwise: Keep the element in the cache and the next put will perform an "update" operation
// on it. This will not close the report object.
logger.debug( "id: " + session.getId()
+ " - Cache.put(..): keeping existing object, no cache operation done." );
return new CachedReportOutputHandler( cacheHolder );
}
}
}
final CacheHolder cacheHolder = new CacheHolder( key, report );
cache.put( new Element( key.getSessionId(), cacheHolder ) );
logger.debug( "id: " + session.getId() + " - Cache.put(..): storing new report for key " + key.getSessionId() );
return new CachedReportOutputHandler( cacheHolder );
}
}
}