/*! * 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.pentaho.reporting.designer.core.util.exceptions.UncaughtExceptionsModel; import org.pentaho.reporting.libraries.base.util.PasswordObscurification; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; public abstract class PreferencesMap<T extends PreferencesMap.ConfigurationData> { public static interface ConfigurationData { public String getKey(); public String getOption( String key ); public void setOption( String key, String value ); public String[] getDefinedOptions(); public long getLastChanged(); public void setLastChanged( long time ); } private static class DataHolder<T extends PreferencesMap.ConfigurationData> { private T configurationData; private String nodeName; private DataHolder( final T configurationData ) { this.configurationData = configurationData; } public T getConfigurationData() { return configurationData; } public String getKey() { return configurationData.getKey(); } public String getNodeName() { return nodeName; } public void setNodeName( final String nodeName ) { this.nodeName = nodeName; } public long getLastChanged() { return configurationData.getLastChanged(); } public void setLastChanged( final long time ) { configurationData.setLastChanged( time ); } } private static final String VERSION_KEY = "#version"; private static final String URL_KEY = "#url"; private LinkedHashMap<String, DataHolder<T>> backend; private LinkedHashMap<String, DataHolder<T>> removedNodes; private Preferences storageBackend; public PreferencesMap( final Preferences preferences ) { if ( preferences == null ) { throw new NullPointerException(); } storageBackend = preferences; //Preferences.userNodeForPackage(StoredPublishLocations.class).node("StoredPublishLocations"); backend = new LinkedHashMap<String, DataHolder<T>>(); removedNodes = new LinkedHashMap<String, DataHolder<T>>(); } protected abstract T create( String key, long time ); protected T get( final String key ) { final DataHolder<T> holder = backend.get( key ); if ( holder != null ) { return holder.getConfigurationData(); } return null; } protected void init() { try { sync(); } catch ( Exception e ) { UncaughtExceptionsModel.getInstance().addException( e ); } } protected String[] getKnownKeys() { return backend.keySet().toArray( new String[ backend.size() ] ); } protected void add( final T configurationData ) { final DataHolder<T> data = new DataHolder<T>( configurationData ); final DataHolder<T> oldData = backend.put( data.getKey(), data ); if ( oldData != null ) { data.setNodeName( oldData.getNodeName() ); } try { sync(); } catch ( Exception e ) { UncaughtExceptionsModel.getInstance().addException( e ); } } protected void remove( final String key ) { final DataHolder<T> data = backend.remove( key ); if ( data != null ) { removedNodes.put( key, data ); try { sync(); } catch ( Exception e ) { UncaughtExceptionsModel.getInstance().addException( e ); } } } public void sync() throws BackingStoreException { final Iterator<DataHolder<T>> removedNodes = this.removedNodes.values().iterator(); while ( removedNodes.hasNext() ) { final DataHolder<T> s = removedNodes.next(); removedNodes.remove(); final String nodeName = s.getNodeName(); if ( nodeName != null ) { storageBackend.node( nodeName ); } } final LinkedHashMap<String, DataHolder<T>> dataFromStore = new LinkedHashMap<String, DataHolder<T>>(); final String[] strings = storageBackend.childrenNames(); Arrays.sort( strings ); for ( int i = 0; i < strings.length; i++ ) { final String name = strings[ i ]; if ( name.startsWith( "#" ) == false ) { continue; } final Preferences authNode = storageBackend.node( name ); final String url = authNode.get( URL_KEY, null ); if ( url == null ) { authNode.removeNode(); continue; } final long authTime = authNode.getLong( VERSION_KEY, 0 ); final DataHolder<T> data = new DataHolder<T>( create( url, authTime ) ); final String[] options = authNode.keys(); for ( int j = 0; j < options.length; j++ ) { final String option = options[ j ]; if ( option.startsWith( "#" ) ) { continue; } data.getConfigurationData().setOption( option, PasswordObscurification.decryptPasswordWithOptionalEncoding( authNode.get( option, null ) ) ); } data.setNodeName( name ); data.setLastChanged( authTime ); dataFromStore.put( url, data ); } final ArrayList<DataHolder<T>> dataToWrite = new ArrayList<DataHolder<T>>(); final ArrayList<DataHolder<T>> datas = new ArrayList<DataHolder<T>>( backend.values() ); for ( final DataHolder<T> local : datas ) { final String url = local.getKey(); final DataHolder<T> fromStore = dataFromStore.get( url ); if ( fromStore == null ) { // new item .. dataToWrite.add( local ); } else if ( local.getLastChanged() > fromStore.getLastChanged() ) { // in-memory item is newer dataToWrite.add( local ); dataFromStore.remove( url ); } else if ( local.getLastChanged() < fromStore.getLastChanged() ) { // external item is newer backend.put( url, fromStore ); dataFromStore.remove( url ); } else { // item is unchanged .. dataFromStore.remove( url ); } } // all that is left in the collection are new items from the config-store this.backend.putAll( dataFromStore ); // and the newly created in memory items .. final HashSet<String> names = new HashSet<String>( Arrays.asList( strings ) ); for ( int i = 0; i < dataToWrite.size(); i++ ) { final DataHolder<T> data = dataToWrite.get( i ); final String existingName = data.getNodeName(); String name = null; if ( existingName != null ) { final Preferences preferences = storageBackend.node( existingName ); final String key = preferences.get( URL_KEY, null ); if ( data.getKey().equals( key ) ) { name = existingName; } } if ( name == null ) { name = generateName( names ); } if ( name != null ) { final Preferences subnode = storageBackend.node( name ); data.setNodeName( name ); subnode.putLong( VERSION_KEY, data.getLastChanged() ); subnode.put( URL_KEY, data.getKey() ); final String[] optionKeys = data.getConfigurationData().getDefinedOptions(); for ( int j = 0; j < optionKeys.length; j++ ) { final String optionKey = optionKeys[ j ]; if ( optionKey.startsWith( "#" ) ) { continue; } final String password = data.getConfigurationData().getOption( optionKey ); if ( password != null ) { subnode.put( optionKey, PasswordObscurification.encryptPasswordWithOptionalEncoding( password ) ); } } } } } private String generateName( final HashSet names ) throws BackingStoreException { for ( int i = 0; i < names.size(); i++ ) { final String nodeName = "#" + i; if ( names.contains( nodeName ) == false ) { return nodeName; } } for ( int i = 0; i < 9999; i++ ) { final String nodeName = "#" + i; if ( storageBackend.nodeExists( nodeName ) == false ) { return nodeName; } } return null; } public String getMostRecentEntry() { long age = 0; String result = null; for ( final Map.Entry<String, DataHolder<T>> entry : backend.entrySet() ) { final DataHolder holder = entry.getValue(); if ( holder.getLastChanged() > age ) { age = holder.getLastChanged(); result = entry.getKey(); } } return result; } }