/*
* 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) 2009 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.reporting.designer.core.welcome;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.designer.core.Messages;
import org.pentaho.reporting.designer.core.settings.SettingsUtil;
import org.pentaho.reporting.libraries.base.util.FilesystemFilter;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.resourceloader.ResourceData;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
/**
* Todo: Document Me
*
* @author Thomas Morgner
*/
public class SamplesTreeBuilder
{
private static class DirectoryNode extends DefaultMutableTreeNode
{
/**
* Creates a tree node with no parent, no children, but which allows
* children, and initializes it with the specified user object.
*
* @param userObject an Object provided by the user that constitutes
* the node's data
*/
private DirectoryNode(final Object userObject)
{
super(userObject);
}
/**
* Returns true if this node has no children. To distinguish between
* nodes that have no children and nodes that <i>cannot</i> have
* children (e.g. to distinguish files from empty directories), use this
* method in conjunction with <code>getAllowsChildren</code>
*
* @return true if this node has no children
* @see #getAllowsChildren
*/
public boolean isLeaf()
{
return false;
}
}
private static class DirectoryFileFilter implements FileFilter
{
private DirectoryFileFilter()
{
}
public boolean accept(final File file)
{
if (file.getName().length() > 0 && file.getName().charAt(0) == '.')
{
return false;
}
return file.isDirectory();
}
}
/**
* HyperLink that opens a fiel as a new report, could be combined with the RecentDocButton
*
* @author OEM
*/
public static class SampleNode extends DefaultMutableTreeNode
{
private String fileName;
public SampleNode(final String lbl, final String file)
{
super(file);
fileName = lbl;
}
public String toString()
{
return fileName;
}
}
private static final Log logger = LogFactory.getLog(SamplesTreeBuilder.class);
private static DefaultTreeModel sampleTreeModel;
private static final DirectoryFileFilter DIRECTORY_FILTER = new DirectoryFileFilter();
private static final FilesystemFilter REPORT_FILES_FILTER =
new FilesystemFilter(new String[]{".report", ".prpt"}, "", false);// NON-NLS
private SamplesTreeBuilder()
{
}
public static synchronized TreeModel getSampleTreeModel()
{
if (sampleTreeModel == null)
{
sampleTreeModel = createModel();
}
return sampleTreeModel;
}
private static DefaultTreeModel createModel()
{
final DefaultMutableTreeNode root = new DefaultMutableTreeNode(Messages.getString("WelcomePane.samples"));// NON-NLS
final DefaultTreeModel model = new DefaultTreeModel(root);
try
{
final ResourceManager resourceManager = new ResourceManager();
resourceManager.registerDefaults();
final HashMap cache = loadFromCache(resourceManager);
final File installationDirectory = SettingsUtil.computeInstallationDirectory();
if (installationDirectory != null)
{
final File configTemplateDir = new File(installationDirectory, "samples");// NON-NLS
if (configTemplateDir.exists() && configTemplateDir.isDirectory())
{
processDirectory(root, configTemplateDir, cache, resourceManager);
}
}
storeToCache(cache);
}
catch (Exception e)
{
return model;
}
return model;
}
/**
* Creates a list of SampleReports located in the /samples directory.
*/
private static void processDirectory(final DefaultMutableTreeNode root,
final File dir,
final HashMap cachedEntries,
final ResourceManager resourceManager)
{
try
{
final File[] dirs = dir.listFiles(DIRECTORY_FILTER);
if (dirs == null)
{
return;
}
for (final File f : dirs)
{
final DefaultMutableTreeNode dirNode = new DirectoryNode(f.getName());
root.add(dirNode);
processDirectory(dirNode, f, cachedEntries, resourceManager);
}
//Now add sample files
final File[] samplesArray = dir.listFiles(REPORT_FILES_FILTER);
if (samplesArray == null)
{
return;
}
Arrays.sort(samplesArray);
for (final File f : samplesArray)
{
final SampleReport entryFromCache = (SampleReport) cachedEntries.get(f.getAbsolutePath());
if (entryFromCache == null)
{
final SampleReport tempRpt = new SampleReport(f, resourceManager);
if (StringUtils.isEmpty(tempRpt.getReportName()) == false)
{
final SampleNode sample = new SampleNode(tempRpt.getReportName(), tempRpt.getFileName());
root.add(sample);
}
cachedEntries.put(f.getAbsolutePath(), tempRpt);
}
else
{
if (StringUtils.isEmpty(entryFromCache.getReportName()) == false)
{
final SampleNode sample = new SampleNode(entryFromCache.getReportName(), entryFromCache.getFileName());
root.add(sample);
}
}
}
}
catch (Exception se)
{
logger.error("Cannot access Application directory", se);// NON-NLS
}
}
protected static HashMap loadFromCache(final ResourceManager resourceManager)
{
final File location = createStorageLocation();
if (location == null)
{
return new HashMap();
}
final File ttfCache = new File(location, "samples-cache.ser");// NON-NLS
try
{
final ResourceKey resourceKey = resourceManager.createKey(ttfCache);
final ResourceData data = resourceManager.load(resourceKey);
final InputStream stream = data.getResourceAsStream(resourceManager);
final HashMap cachedSeenFiles;
try
{
final ObjectInputStream oin = new ObjectInputStream(stream);
final Object[] cache = (Object[]) oin.readObject();
if (cache.length != 1)
{
return new HashMap();
}
cachedSeenFiles = (HashMap) cache[0];
}
finally
{
stream.close();
}
// next; check the font-cache for validity. We cannot cleanly remove
// entries from the cache once they become invalid, so we have to rebuild
// the cache from scratch, if it is invalid.
//
// This should not matter that much, as font installations do not happen
// every day.
if (isCacheValid(cachedSeenFiles))
{
return cachedSeenFiles;
}
}
catch (final ClassNotFoundException cnfe)
{
// ignore the exception.
logger.debug("Failed to restore the cache: Cache was created by a different version of ReportDesigner");// NON-NLS
}
catch (Exception e)
{
logger.debug("Non-Fatal: Failed to restore the cache. The cache will be rebuilt.", e);// NON-NLS
}
return new HashMap();
}
protected static void storeToCache(final HashMap seenFiles)
{
final File location = createStorageLocation();
if (location == null)
{
return;
}
location.mkdirs();
if (location.exists() == false || location.isDirectory() == false)
{
return;
}
final File ttfCache = new File(location, "samples-cache.ser");// NON-NLS
try
{
final FileOutputStream fout = new FileOutputStream(ttfCache);
try
{
final Object[] map = new Object[1];
map[0] = seenFiles;
final ObjectOutputStream objectOut = new ObjectOutputStream(new BufferedOutputStream(fout));
objectOut.writeObject(map);
objectOut.close();
}
finally
{
try
{
fout.close();
}
catch (IOException e)
{
// ignore ..
logger.debug("Failed to store cached samples data", e);// NON-NLS
}
}
}
catch (IOException e)
{
// should not happen
logger.debug("Failed to store cached samples data", e);// NON-NLS
}
}
protected static File createStorageLocation()
{
final String homeDirectory = safeSystemGetProperty("user.home", null);// NON-NLS
if (homeDirectory == null)
{
return null;
}
final File homeFile = new File(homeDirectory);
if (homeFile.isDirectory() == false)
{
return null;
}
return new File(homeFile, ".pentaho/caches/prd-samples");// NON-NLS
}
protected static String safeSystemGetProperty(final String name,
final String defaultValue)
{
try
{
return System.getProperty(name, defaultValue);
}
catch (SecurityException se)
{
return defaultValue;
}
}
protected static boolean isCacheValid(final HashMap cachedSeenFiles)
{
final Iterator iterator = cachedSeenFiles.entrySet().iterator();
while (iterator.hasNext())
{
final Map.Entry entry = (Map.Entry) iterator.next();
final String fullFileName = (String) entry.getKey();
final SampleReport fontFileRecord = (SampleReport) entry.getValue();
final File fontFile = new File(fullFileName);
if (fontFile.isFile() == false || fontFile.exists() == false)
{
return false;
}
if (fontFile.length() != fontFileRecord.getFileSize())
{
return false;
}
if (fontFile.lastModified() != fontFileRecord.getLastAccessTime())
{
return false;
}
}
return true;
}
}