/*! * 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.settings.prefs; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.designer.core.util.exceptions.UncaughtExceptionsModel; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Properties; import java.util.prefs.AbstractPreferences; import java.util.prefs.BackingStoreException; /** * Todo: Document Me * * @author Thomas Morgner */ public class BinaryPreferences extends AbstractPreferences { private static final Log logger = LogFactory.getLog( BinaryPreferences.class ); private Properties properties; private BinaryPreferences parent; private long lastModificationTime; private String rootPath; public BinaryPreferences( final String rootPath ) { super( null, "" ); this.rootPath = rootPath; } public BinaryPreferences( final BinaryPreferences parent, final String name ) { super( parent, name ); this.parent = parent; this.properties = new Properties(); } protected void putSpi( final String key, final String value ) { initCache(); properties.setProperty( key, value ); lastModificationTime = Math.max( lastModificationTime + 1, System.currentTimeMillis() ); try { flush(); } catch ( BackingStoreException e ) { if ( logger.isInfoEnabled() ) { logger.info( "Failed to flush configuration changes" );//NON-NLS } else if ( logger.isDebugEnabled() ) { logger.info( "Failed to flush configuration changes", e );//NON-NLS } } } private void initCache() { if ( lastModificationTime == 0 ) { final File pathForNode = new File( getPathForNode() ); try { load( pathForNode ); } catch ( BackingStoreException e ) { logger.warn( "Failed to load data", e );//NON-NLS // ignored } } } protected String getSpi( final String key ) { initCache(); return properties.getProperty( key ); } protected void removeSpi( final String key ) { initCache(); properties.remove( key ); lastModificationTime = Math.max( lastModificationTime + 1, System.currentTimeMillis() ); try { flush(); } catch ( BackingStoreException e ) { if ( logger.isInfoEnabled() ) { logger.info( "Failed to flush configuration changes" );//NON-NLS } else if ( logger.isDebugEnabled() ) { logger.info( "Failed to flush configuration changes", e );//NON-NLS } } } public long getLastModificationTime() { return lastModificationTime; } protected String[] keysSpi() throws BackingStoreException { initCache(); return (String[]) properties.keySet().toArray( new String[ properties.size() ] ); } protected String[] childrenNamesSpi() throws BackingStoreException { final ArrayList<String> result = new ArrayList<String>(); final File pathForNode = new File( getPathForNode() ); final File[] dirContents = pathForNode.listFiles(); if ( dirContents != null ) { for ( int i = 0; i < dirContents.length; i++ ) { if ( dirContents[ i ].isDirectory() ) { result.add( decodePath( dirContents[ i ].getName() ) ); } } } return result.toArray( new String[ result.size() ] ); } protected AbstractPreferences childSpi( final String name ) { if ( name == null || name.length() == 0 ) { throw new IllegalArgumentException(); } return new BinaryPreferences( this, name ); } protected void syncSpi() throws BackingStoreException { final File pathForNode = new File( getPathForNode() ); if ( pathForNode.exists() == false && properties.isEmpty() ) { return; } load( pathForNode ); if ( pathForNode.exists() == false ) { if ( pathForNode.mkdirs() == false ) { throw new BackingStoreException( "Failed to write config " + pathForNode ); //$NON-NLS-1$ } } final File target = new File( pathForNode, "prefs.properties" );//NON-NLS if ( target.exists() == false || target.lastModified() < lastModificationTime ) { try { final OutputStream out = new BufferedOutputStream( new FileOutputStream( target ) ); try { properties.store( out, "" ); } finally { out.close(); } } catch ( final Exception e ) { throw new BackingStoreException( "Failed to write config " + target ); //$NON-NLS-1$ } } } private void load( final File pathForNode ) throws BackingStoreException { if ( pathForNode.exists() ) { // load .. final File target = new File( pathForNode, "prefs.properties" );//NON-NLS if ( target.lastModified() > lastModificationTime ) { if ( target.exists() ) { try { final InputStream out = new BufferedInputStream( new FileInputStream( target ) ); try { properties.clear(); properties.load( out ); lastModificationTime = Math.max( lastModificationTime + 1, System.currentTimeMillis() ); } finally { out.close(); } } catch ( final Exception e ) { UncaughtExceptionsModel.getInstance().addException( e ); throw new BackingStoreException( "Failed to write config " + target ); //$NON-NLS-1$ } } } } } protected void flushSpi() throws BackingStoreException { // no-op syncSpi(); } protected void removeNodeSpi() throws BackingStoreException { // delete the directory .. final File pathForNode = new File( getPathForNode() ); if ( pathForNode.exists() ) { final File target = new File( pathForNode, "prefs.properties" );//NON-NLS if ( target.delete() == false ) { throw new BackingStoreException( "Unable to delete node-backend" ); } if ( pathForNode.delete() == false ) { throw new BackingStoreException( "Unable to delete node-backend" ); } } } private String getPathForNode() { if ( parent != null ) { return parent.getPathForNode() + File.separatorChar + encodePath( name() ); } return rootPath; } /** * Encodes the given configuration path. All non-ascii characters get replaced by an escape sequence. * * @param path the path. * @return the translated path. * @throws java.util.prefs.BackingStoreException if something goes wrong. */ private static String decodePath( final String path ) throws BackingStoreException { try { final char[] data = path.toCharArray(); final StringBuffer encoded = new StringBuffer( path.length() ); int seenDollarIndex = -1; for ( int i = 0; i < data.length; i++ ) { if ( seenDollarIndex > -1 ) { if ( data[ i ] == '$' ) { encoded.append( '$' ); seenDollarIndex = -1; continue; } if ( i - seenDollarIndex == 4 ) { final int c = Integer.parseInt( path.substring( seenDollarIndex + 1, i + 1 ), 16 ); encoded.append( (char) c ); seenDollarIndex = -1; continue; } else { continue; } } if ( data[ i ] == '$' ) { seenDollarIndex = i; } else { encoded.append( data[ i ] ); } } return encoded.toString(); } catch ( NumberFormatException nfe ) { nfe.printStackTrace(); throw new BackingStoreException( "Failed to decode name: " + path ); } } /** * Encodes the given configuration path. All non-ascii characters get replaced by an escape sequence. * * @param path the path. * @return the translated path. */ private static String encodePath( final String path ) { final char[] data = path.toCharArray(); final StringBuffer encoded = new StringBuffer( path.length() ); for ( int i = 0; i < data.length; i++ ) { if ( data[ i ] == '$' ) { // double quote encoded.append( '$' ); encoded.append( '$' ); } else if ( Character.isJavaIdentifierPart( data[ i ] ) == false ) { // padded hex string encoded.append( '$' ); final String hex = Integer.toHexString( data[ i ] ); for ( int x = hex.length(); x < 4; x++ ) { encoded.append( '0' ); } encoded.append( hex ); } else { encoded.append( data[ i ] ); } } return encoded.toString(); } }