/*******************************************************************************
* Copyright (c) 2002, 2008 Innoopract Informationssysteme GmbH.
* 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
******************************************************************************/
package org.eclipse.rwt.service;
import java.io.*;
import java.util.*;
import org.eclipse.rwt.RWT;
import org.eclipse.rwt.internal.util.ParamCheck;
/**
* This {@link ISettingStore} implementation persists all settings on the
* file system using Java {@link Properties}.
* <p>
* @since 1.1
*/
public final class FileSettingStore implements ISettingStore {
/**
* This key (value "org.eclipse.rwt.service.FileSettingStore.dir") can be
* used to configure the working directory for file settings stores.
* See {@link RWTFileSettingStoreFactory} and
* <code>WorkbenchFileSettingStoreFactory</code>.
*/
public static final String FILE_SETTING_STORE_DIR
= "org.eclipse.rwt.service.FileSettingStore.dir";
private static final Random RANDOM = new Random( System.currentTimeMillis() );
private final File workDir;
private final Properties props = new Properties();
private final Set listeners = new HashSet();
private String id;
/**
* Create a {@link FileSettingStore} instance. The store will be initialized
* with a unique random id and will contain no attributes. Use
* {@link #loadById(String)} to initialize an existing store with previously
* persisted attributes.
*
* @param workDir a non-null File instance denoting an existing directory,
* which will be used by this class persist its settings.
* @throws NullPointerException if the argument workDir is <code>null</code>
* @throws IllegalArgumentException if the argument workDir is not a directory
* @see #loadById(String)
*/
public FileSettingStore( final File workDir ) {
ParamCheck.notNull( workDir, "workDir" );
if( !workDir.isDirectory() ) {
String msg = "workDir is not a directory: " + workDir;
throw new IllegalArgumentException( msg );
}
this.workDir = workDir;
id = String.valueOf( System.currentTimeMillis() )
+ "_"
+ RANDOM.nextInt( Short.MAX_VALUE );
}
////////////////////////
// ISettingStore methods
public String getId() {
return id;
}
public synchronized String getAttribute( final String name ) {
ParamCheck.notNull( name, "name" );
return props.getProperty( name );
}
public synchronized void setAttribute( final String name,
final String value )
throws SettingStoreException
{
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();
}
}
}
public synchronized Enumeration getAttributeNames() {
return props.keys();
}
public synchronized void loadById( final String id )
throws SettingStoreException
{
ParamCheck.notNullOrEmpty( id, "id" );
this.id = id;
notifyForEachAttribute( true );
props.clear();
BufferedInputStream bis = getInputStream( id );
if( bis != null ) {
try {
try {
props.load( bis );
notifyForEachAttribute( false );
} finally {
bis.close();
}
} catch( IOException ioe ) {
String msg = "Failed to load into file setting store; id= " + id;
throw new SettingStoreException( msg, ioe );
}
}
}
public synchronized void removeAttribute( final String name )
throws SettingStoreException
{
String oldValue = ( String )props.remove( name );
if( oldValue != null ) {
notifyListeners( name, oldValue, null );
persist();
}
}
public synchronized void addSettingStoreListener( final SettingStoreListener listener ) {
ParamCheck.notNull( listener, "listener" );
listeners.add( listener );
}
public synchronized void removeSettingStoreListener( final 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( final 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( final String streamId )
throws FileNotFoundException {
File file = getStoreFile( streamId );
return new BufferedOutputStream( new FileOutputStream( file ) );
}
private File getStoreFile( final String fileName ) {
return new File( workDir, fileName );
}
private void log( final String msg, final Throwable throwable ) {
RWT.getRequest().getSession().getServletContext().log( msg, throwable );
}
private synchronized void notifyForEachAttribute( final boolean removed ) {
Enumeration 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( final String attribute,
final String oldValue,
final String newValue ) {
SettingStoreEvent event
= new SettingStoreEvent( this, attribute, oldValue, newValue );
Iterator iter = listeners.iterator();
while( iter.hasNext() ) {
SettingStoreListener listener = ( SettingStoreListener )iter.next();
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 SettingStoreException {
try {
BufferedOutputStream bos = getOutputStream( id );
try {
props.store( bos, FileSettingStore.class.getName() );
} finally {
bos.close();
}
} catch( IOException ioe ) {
String msg = "Failed to persist file setting store; id= " + id;
throw new SettingStoreException( msg, ioe );
}
}
}