Java tutorial
/* * 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) 2001 - 2013 Object Refinery Ltd, Hitachi Vantara and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.cache; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.AbstractDataFactory; import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot; import org.pentaho.reporting.engine.classic.core.CompoundDataFactory; import org.pentaho.reporting.engine.classic.core.CompoundDataFactorySupport; import org.pentaho.reporting.engine.classic.core.DataFactory; import org.pentaho.reporting.engine.classic.core.DataFactoryContext; import org.pentaho.reporting.engine.classic.core.DataFactoryDesignTimeSupport; import org.pentaho.reporting.engine.classic.core.DataRow; import org.pentaho.reporting.engine.classic.core.MetaTableModel; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.engine.classic.core.StaticDataRow; import org.pentaho.reporting.engine.classic.core.metadata.DataFactoryMetaData; import org.pentaho.reporting.engine.classic.core.metadata.MetaDataLookupException; import org.pentaho.reporting.engine.classic.core.util.CloseableTableModel; import org.pentaho.reporting.libraries.base.config.Configuration; import org.pentaho.reporting.libraries.base.util.ArgumentNullException; import javax.swing.table.TableModel; import java.util.HashMap; public class CachingDataFactory extends AbstractDataFactory implements CompoundDataFactorySupport { private enum QueryStyle { General, Static, FreeForm } private static final Log logger = LogFactory.getLog(CachingDataFactory.class); private DataCache dataCache; private HashMap<DataCacheKey, TableModel> sessionCache; private CompoundDataFactory backend; private boolean closed; private boolean debugDataSources; private boolean profileDataSources; private boolean noClose; private static final String[] EMPTY_NAMES = new String[0]; public CachingDataFactory(final DataFactory backend, final boolean dataCacheEnabled) { this(backend, false, dataCacheEnabled); } public CachingDataFactory(final DataFactory backend, final boolean noClose, final boolean dataCacheEnabled) { this(backend, noClose, produceDefault(dataCacheEnabled)); } private static DataCache produceDefault(final boolean dataCacheEnabled) { if (dataCacheEnabled) { return DataCacheFactory.getCache(); } else { return null; } } public CachingDataFactory(final DataFactory backend, final boolean noClose, final DataCache dataCache) { if (backend == null) { throw new NullPointerException(); } this.noClose = noClose; if (noClose) { this.backend = CompoundDataFactory.normalize(backend, false); } else { this.backend = CompoundDataFactory.normalize(backend, true); } final Configuration configuration = ClassicEngineBoot.getInstance().getGlobalConfig(); this.sessionCache = new HashMap<DataCacheKey, TableModel>(); this.dataCache = dataCache; this.debugDataSources = "true".equals( configuration.getConfigProperty("org.pentaho.reporting.engine.classic.core.DebugDataSources")); this.profileDataSources = "true".equals( configuration.getConfigProperty("org.pentaho.reporting.engine.classic.core.ProfileDataSources")); } public void initialize(final DataFactoryContext dataFactoryContext) throws ReportDataFactoryException { super.initialize(dataFactoryContext); backend.initialize(dataFactoryContext); } public boolean isQueryExecutable(final String query, final DataRow parameters) { if (query == null) { throw new NullPointerException(); } if (parameters == null) { throw new NullPointerException(); } if (backend.isQueryExecutable(query, parameters)) { return true; } return false; } public boolean isFreeFormQueryExecutable(final String query, final DataRow parameter) { if (query == null) { throw new NullPointerException(); } if (parameter == null) { throw new NullPointerException(); } return backend.isFreeFormQueryExecutable(query, parameter); } public TableModel queryStatic(final String query, final DataRow parameters) throws ReportDataFactoryException { if (query == null) { throw new NullPointerException(); } if (parameters == null) { throw new NullPointerException(); } final DataCacheKey key = createCacheKey(query, parameters, false); if (key != null) { TableModel model = sessionCache.get(key); if (model == null) { model = dataCache.get(key); } if (model != null) { logger.debug("Returning cached data for static query '" + query + "'."); return wrapAsIndexed(model); } } if (!backend.isStaticQueryExecutable(query, parameters)) { throw new ReportDataFactoryException("The specified query '" + query + "' is not executable here."); } TableModel data = queryInternal(query, parameters, QueryStyle.Static); if (data == null) { return null; } data = putInCache(key, data); return wrapAsIndexed(data); } public TableModel queryDesignTimeStructureStatic(final String query, final DataRow parameters) throws ReportDataFactoryException { if (query == null) { throw new NullPointerException(); } if (parameters == null) { throw new NullPointerException(); } final DataCacheKey key = createCacheKey(query, parameters, false); if (key != null) { TableModel model = sessionCache.get(key); if (model == null) { model = dataCache.get(key); } if (model != null) { logger.debug("Returning cached data for design-time query '" + query + "'."); return wrapAsIndexed(model); } } if (!backend.isStaticQueryExecutable(query, parameters)) { throw new ReportDataFactoryException("The specified query '" + query + "' is not executable here."); } TableModel data = backend.queryDesignTimeStructureStatic(query, parameters); if (data == null) { return null; } data = putInCache(key, data); return wrapAsIndexed(data); } public TableModel queryFreeForm(final String query, final DataRow parameters) throws ReportDataFactoryException { if (query == null) { throw new NullPointerException(); } if (parameters == null) { throw new NullPointerException(); } final DataCacheKey key = createCacheKey(query, parameters, false); if (key != null) { TableModel model = sessionCache.get(key); if (model == null) { model = dataCache.get(key); } if (model != null) { logger.debug("Returning cached data for freeform query '" + query + "'."); return wrapAsIndexed(model); } } if (!backend.isFreeFormQueryExecutable(query, parameters)) { throw new ReportDataFactoryException("The specified query '" + query + "' is not executable here."); } TableModel data = queryInternal(query, parameters, QueryStyle.FreeForm); if (data == null) { return null; } data = putInCache(key, data); return wrapAsIndexed(data); } public TableModel queryDesignTimeStructureFreeForm(final String query, final DataRow parameters) throws ReportDataFactoryException { if (query == null) { throw new NullPointerException(); } if (parameters == null) { throw new NullPointerException(); } final DataCacheKey key = createCacheKey(query, parameters, false); if (key != null) { TableModel model = sessionCache.get(key); if (model == null) { model = dataCache.get(key); } if (model != null) { logger.debug("Returning cached data for free-form design time query '" + query + "'."); return wrapAsIndexed(model); } } if (!backend.isFreeFormQueryExecutable(query, parameters)) { throw new ReportDataFactoryException("The specified query '" + query + "' is not executable here."); } TableModel data = backend.queryDesignTimeStructureFreeForm(query, parameters); if (data == null) { return null; } data = putInCache(key, data); return wrapAsIndexed(data); } public boolean isStaticQueryExecutable(final String query, final DataRow parameters) { if (query == null) { throw new NullPointerException(); } if (parameters == null) { throw new NullPointerException(); } return backend.isStaticQueryExecutable(query, parameters); } /** * Queries a datasource. The string 'query' defines the name of the query. The Parameterset given here may contain * more data than actually needed. * <p/> * The dataset may change between two calls, do not assume anything! * * @param query * @param parameters * @return */ public TableModel queryData(final String query, final DataRow parameters) throws ReportDataFactoryException { ArgumentNullException.validate("query", query); ArgumentNullException.validate("parameters", parameters); final DataCacheKey key = createCacheKey(query, parameters, false); if (key != null) { TableModel model = sessionCache.get(key); if (model == null) { model = dataCache.get(key); } if (model != null) { logger.debug("Returning cached data for query '" + query + "'."); return wrapAsIndexed(model); } } if (backend.isQueryExecutable(query, parameters)) { TableModel data = queryInternal(query, parameters, QueryStyle.General); if (data != null) { data = putInCache(key, data); return wrapAsIndexed(data); } } throw new ReportDataFactoryException("The specified query '" + query + "' is not executable here."); } public TableModel queryDesignTimeStructure(final String query, final DataRow parameters) throws ReportDataFactoryException { ArgumentNullException.validate("query", query); ArgumentNullException.validate("parameters", parameters); final DataCacheKey key = createCacheKey(query, parameters, true); if (key != null) { final TableModel model = dataCache.get(key); if (model != null) { logger.debug("Returning cached data for design-time query '" + query + "'."); return wrapAsIndexed(model); } } if (backend.isQueryExecutable(query, parameters)) { TableModel data = queryDesignTimeStructureInternal(query, parameters); if (data != null) { data = putInCache(key, data); return wrapAsIndexed(data); } } throw new ReportDataFactoryException("The specified query '" + query + "' is not executable here."); } private TableModel putInCache(final DataCacheKey key, TableModel data) { if (key != null) { final TableModel newData = dataCache.put(key, data); if (newData != data && data instanceof CloseableTableModel) { final CloseableTableModel closeableTableModel = (CloseableTableModel) data; closeableTableModel.close(); } sessionCache.put(key, newData); data = newData; } return data; } private TableModel wrapAsIndexed(final TableModel data) { if (data instanceof MetaTableModel) { return new IndexedMetaTableModel((MetaTableModel) data); } else { return new IndexedTableModel(data); } } private DataCacheKey createCacheKey(final String query, final DataRow parameters, final boolean designTime) { try { if (dataCache == null) { return null; } final DataCacheKey key; DataFactoryMetaData metaData = backend.getMetaData(); final String[] referencedFields = metaData.getReferencedFields(backend, query, parameters); if (referencedFields != null) { final Object queryHash = metaData.getQueryHash(backend, query, parameters); if (queryHash == null) { logger.debug("Query hash is null, caching is disabled for query '" + query + "'."); key = null; } else { key = new DataCacheKey(); for (int i = 0; i < referencedFields.length; i++) { final String field = referencedFields[i]; key.addParameter(field, parameters.get(field)); } key.addAttribute(DataCacheKey.QUERY_CACHE, queryHash); key.addAttribute(DataFactoryDesignTimeSupport.DESIGN_TIME, designTime); // The data cache maps are immutable - make sure of it. key.makeReadOnly(); } } else { logger.debug("No Referenced fields, caching is disabled for query '" + query + "'."); key = null; } return key; } catch (final MetaDataLookupException mle) { logger.error(String.format( "Data-source used for query '%s' does not provide metadata. Caching will be disabled.", query), mle); return null; } } private TableModel queryInternal(final String query, final DataRow parameters, final QueryStyle queryStyle) throws ReportDataFactoryException { if (profileDataSources && CachingDataFactory.logger.isDebugEnabled()) { CachingDataFactory.logger .debug(System.identityHashCode(Thread.currentThread()) + ": Query processing time: Starting"); } final long startTime = System.currentTimeMillis(); try { final StaticDataRow params = new StaticDataRow(parameters); final TableModel dataFromQuery; switch (queryStyle) { case FreeForm: dataFromQuery = backend.queryFreeForm(query, params); break; case Static: dataFromQuery = backend.queryStatic(query, params); break; case General: dataFromQuery = backend.queryData(query, params); break; default: throw new IllegalStateException(); } if (dataFromQuery == null) { // final DefaultTableModel value = new DefaultTableModel(); if (debugDataSources && CachingDataFactory.logger.isDebugEnabled()) { CachingDataFactory.logger.debug("Query failed for query '" + query + '\''); } return null; } else { if (debugDataSources && CachingDataFactory.logger.isDebugEnabled()) { CachingDataFactory.printTableModelContents(dataFromQuery); } // totally new query here. CachingDataFactory.logger.debug("Query returned a data-set for query '" + query + '\''); return dataFromQuery; } } finally { final long queryTime = System.currentTimeMillis(); if (profileDataSources && CachingDataFactory.logger.isDebugEnabled()) { CachingDataFactory.logger.debug(System.identityHashCode(Thread.currentThread()) + ": Query processing time: " + ((queryTime - startTime) / 1000.0)); } } } private TableModel queryDesignTimeStructureInternal(final String query, final DataRow parameters) throws ReportDataFactoryException { if (profileDataSources && CachingDataFactory.logger.isDebugEnabled()) { CachingDataFactory.logger .debug(System.identityHashCode(Thread.currentThread()) + ": Query processing time: Starting"); } final long startTime = System.currentTimeMillis(); try { return backend.queryDesignTimeStructure(query, parameters); } finally { final long queryTime = System.currentTimeMillis(); if (profileDataSources && CachingDataFactory.logger.isDebugEnabled()) { CachingDataFactory.logger.debug(System.identityHashCode(Thread.currentThread()) + ": Query processing time: " + ((queryTime - startTime) / 1000.0)); } } } /** * Closes the report data factory and all report data instances that have been returned by this instance. */ public void close() { if (closed == false) { for (final TableModel map : sessionCache.values()) { if (map instanceof CloseableTableModel == false) { continue; } final CloseableTableModel ct = (CloseableTableModel) map; ct.close(); } sessionCache.clear(); if (noClose == false) { backend.close(); } closed = true; } } /** * Derives a freshly initialized report data factory, which is independend of the original data factory. Opening or * Closing one data factory must not affect the other factories. * * @return nothing, the method dies instead. * @throws UnsupportedOperationException * as this class is not derivable. */ public DataFactory derive() { // If you see that exception, then you've probably tried to use that // datafactory from outside of the report processing. You deserve the // exception in that case .. throw new UnsupportedOperationException("The CachingReportDataFactory cannot be derived."); } /** * Prints a table model to standard output. * * @param mod * the model. */ public static void printTableModelContents(final TableModel mod) { if (mod == null) { throw new NullPointerException(); } logger.debug("Tablemodel contains " + mod.getRowCount() + " rows."); //$NON-NLS-1$ //$NON-NLS-2$ for (int i = 0; i < mod.getColumnCount(); i++) { logger.debug("Column: " + i + " Name = " + mod.getColumnName(i) + "; DataType = " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + mod.getColumnClass(i)); } logger.debug("Checking the data inside"); //$NON-NLS-1$ for (int rows = 0; rows < mod.getRowCount(); rows++) { for (int i = 0; i < mod.getColumnCount(); i++) { final Object value = mod.getValueAt(rows, i); logger.debug("ValueAt (" + rows + ", " + i + ") is " + value); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } } public String[] getQueryNames() { return EMPTY_NAMES; } public void cancelRunningQuery() { } public CachingDataFactory clone() { final CachingDataFactory cdf = (CachingDataFactory) super.clone(); cdf.backend = (CompoundDataFactory) backend.clone(); cdf.sessionCache = (HashMap<DataCacheKey, TableModel>) sessionCache.clone(); return cdf; } public DataFactory getDataFactoryForQuery(final String queryName, final boolean freeform) { return backend.getDataFactoryForQuery(queryName, freeform); } }