/*******************************************************************************
* Copyright (c) 2002, 2015 Innoopract Informationssysteme GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
package org.eclipse.rap.rwt.service;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import org.eclipse.rap.rwt.internal.service.ServletLog;
import org.eclipse.rap.rwt.internal.util.ParamCheck;
/**
* A setting store implementation that persists all settings on the file system using Java
* {@link Properties} files.
*
* @since 2.0
*/
public final class FileSettingStore implements SettingStore {
/**
* This key (value "org.eclipse.rap.rwt.service.FileSettingStore.dir") can be used to configure
* the working directory for file settings stores. See {@link FileSettingStoreFactory}.
*/
public static final String FILE_SETTING_STORE_DIR
= "org.eclipse.rap.rwt.service.FileSettingStore.dir";
private final File workDir;
private final Properties props;
private final Set<SettingStoreListener> listeners;
private String id;
/**
* Creates an empty instance with a random unique ID. Use {@link #loadById(String)} to initialize
* an existing store with previously persisted attributes.
*
* @param baseDirectory an existing directory to persist this store's settings in
* @throws IllegalArgumentException if the given <code>workDir</code> is not a directory
* @see #loadById(String)
*/
public FileSettingStore( File baseDirectory ) {
ParamCheck.notNull( baseDirectory, "baseDirectory" );
checkWorkDir( baseDirectory );
workDir = baseDirectory;
props = new Properties();
listeners = new HashSet<>();
}
@Override
public String getId() {
return id;
}
@Override
public synchronized String getAttribute( String name ) {
ParamCheck.notNull( name, "name" );
return props.getProperty( name );
}
@Override
public synchronized void setAttribute( String name, String value ) throws IOException {
ParamCheck.notNull( name, "name" );
if( value == null ) {
removeAttribute( name );
} else {
String oldValue = ( String )props.setProperty( name, value );
if( !value.equals( oldValue ) ) {
notifyListeners( name, oldValue, value );
persist();
}
}
}
@Override
public synchronized Enumeration<String> getAttributeNames() {
final Enumeration<Object> keys = props.keys();
return new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return keys.hasMoreElements();
}
@Override
public String nextElement() {
return ( String )keys.nextElement();
}
};
}
@Override
public synchronized void loadById( String id ) throws IOException {
ParamCheck.notNullOrEmpty( id, "id" );
this.id = id;
notifyForEachAttribute( true );
props.clear();
BufferedInputStream inputStream = getInputStream( id );
if( inputStream != null ) {
try {
props.load( inputStream );
notifyForEachAttribute( false );
} finally {
inputStream.close();
}
}
}
@Override
public synchronized void removeAttribute( String name ) throws IOException {
String oldValue = ( String )props.remove( name );
if( oldValue != null ) {
notifyListeners( name, oldValue, null );
persist();
}
}
@Override
public synchronized void addSettingStoreListener( SettingStoreListener listener ) {
ParamCheck.notNull( listener, "listener" );
listeners.add( listener );
}
@Override
public synchronized void removeSettingStoreListener( SettingStoreListener listener ) {
ParamCheck.notNull( listener, "listener" );
listeners.remove( listener );
}
//////////////////
// helping methods
/**
* @return a BufferedInputStream or <code>null</code> if this file does not exist
*/
private BufferedInputStream getInputStream( String streamId ) {
BufferedInputStream result = null;
File file = getStoreFile( streamId );
if( file.exists() ) {
try {
result = new BufferedInputStream( new FileInputStream( file ) );
} catch( FileNotFoundException fnf ) {
log( "Should not happen", fnf );
}
}
return result;
}
private BufferedOutputStream getOutputStream( String streamId )
throws FileNotFoundException {
File file = getStoreFile( streamId );
return new BufferedOutputStream( new FileOutputStream( file ) );
}
private File getStoreFile( String fileName ) {
return new File( workDir, fileName );
}
private static void log( String msg, Throwable throwable ) {
ServletLog.log( msg, throwable );
}
private synchronized void notifyForEachAttribute( boolean removed ) {
Enumeration<Object> attributes = props.keys();
while( attributes.hasMoreElements() ) {
String attribute = ( String )attributes.nextElement();
String value = props.getProperty( attribute );
if( removed ) {
notifyListeners( attribute, value, null );
} else {
notifyListeners( attribute, null, value );
}
}
}
private synchronized void notifyListeners( String attribute, String oldValue, String newValue ) {
SettingStoreEvent event = new SettingStoreEvent( this, attribute, oldValue, newValue );
// TODO [rh] create a snapshot of listeners before invoking (possible concurrent modification)
for( SettingStoreListener listener : listeners ) {
try {
listener.settingChanged( event );
} catch( Exception exc ) {
String msg = "Exception when invoking listener " + listener.getClass().getName();
log( msg, exc );
} catch( LinkageError le ) {
String msg = "Linkage error when invoking listener " + listener.getClass().getName();
log( msg, le );
}
}
}
private void persist() throws IOException {
BufferedOutputStream outputStream = getOutputStream( id );
try {
props.store( outputStream, FileSettingStore.class.getName() );
} finally {
outputStream.close();
}
}
private static void checkWorkDir( File workDir ) {
if( !workDir.isDirectory() ) {
throw new IllegalArgumentException( "workDir is not a directory: " + workDir );
}
}
}