/*! * 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-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.designer.core.welcome; 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; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; 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; /** * 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(); 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; } }