blob: fbcb766cf8ddfe164e4a03b23bbceb3f09bb0a76 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.felix.useradmin.filestore;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.felix.useradmin.RoleRepositoryStore;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.useradmin.UserAdminEvent;
import org.osgi.service.useradmin.UserAdminListener;
/**
* Provides an implementation of {@link RoleRepositoryStore} using Java Serialization.
*/
public class RoleRepositoryFileStore extends RoleRepositoryMemoryStore implements Runnable, UserAdminListener, ManagedService {
/** The PID for this service to allow its configuration to be updated. */
public static final String PID = "org.apache.felix.useradmin.filestore";
static final String KEY_WRITE_DISABLED = "background.write.disabled";
static final String KEY_WRITE_DELAY_VALUE = "background.write.delay.value";
static final String KEY_WRITE_DELAY_TIMEUNIT = "background.write.delay.timeunit";
private static final String PREFIX = PID.concat(".");
private static final boolean DEFAULT_WRITE_DISABLED = Boolean.parseBoolean(System.getProperty(PREFIX.concat(KEY_WRITE_DISABLED), "false"));
private static final int DEFAULT_WRITE_DELAY_VALUE = Integer.parseInt(System.getProperty(PREFIX.concat(KEY_WRITE_DELAY_VALUE), "500"));
private static final TimeUnit DEFAULT_WRITE_DELAY_TIMEUNIT = TimeUnit.MILLISECONDS;
private static final String FILE_NAME = "ua_repo.dat";
private final File m_file;
private final AtomicReference m_timerRef;
/**
* Creates a new {@link RoleRepositoryStore} instance.
*
* @param baseDir the base directory where we can store our serialized data, cannot be <code>null</code>.
*/
public RoleRepositoryFileStore(File baseDir) {
this(baseDir, !DEFAULT_WRITE_DISABLED);
}
/**
* Creates a new {@link RoleRepositoryStore} instance.
*
* @param baseDir the base directory where we can store our serialized data, cannot be <code>null</code>;
* @param backgroundWriteEnabled <code>true</code> if background writing should be enabled, <code>false</code> to disable it.
*/
public RoleRepositoryFileStore(File baseDir, boolean backgroundWriteEnabled) {
m_file = new File(baseDir, FILE_NAME);
m_timerRef = new AtomicReference();
if (backgroundWriteEnabled) {
m_timerRef.set(new ResettableTimer(this, DEFAULT_WRITE_DELAY_VALUE, DEFAULT_WRITE_DELAY_TIMEUNIT));
}
}
public void roleChanged(UserAdminEvent event) {
scheduleTask();
}
/**
* {@inheritDoc}
*
* <p>Will be called by m_timer!</p>
*/
public void run() {
try {
// Persist everything to disk...
flush();
}
catch (IOException e) {
e.printStackTrace();
}
}
/**
* Starts this store by reading the latest version from disk.
*
* @throws IOException in case of I/O problems retrieving the store.
*/
public void start() throws IOException {
m_entries.putAll(retrieve());
}
/**
* Stops this store service.
*/
public void stop() throws IOException {
ResettableTimer timer = (ResettableTimer) m_timerRef.get();
if (timer != null) {
if (!timer.isShutDown()) {
// Shutdown and await termination...
timer.shutDown();
}
// Clear reference...
m_timerRef.compareAndSet(timer, null);
}
// Write the latest version to disk...
flush();
}
/**
* {@inheritDoc}
*/
public void updated(Dictionary properties) throws ConfigurationException {
boolean writeDisabled = DEFAULT_WRITE_DISABLED;
int writeDelayValue = DEFAULT_WRITE_DELAY_VALUE;
TimeUnit writeDelayUnit = DEFAULT_WRITE_DELAY_TIMEUNIT;
if (properties != null) {
Object wd = properties.get(KEY_WRITE_DISABLED);
if (wd == null) {
throw new ConfigurationException(KEY_WRITE_DISABLED, "Missing write disabled value!");
}
try {
writeDisabled = Boolean.parseBoolean((String) wd);
} catch (Exception e) {
throw new ConfigurationException(KEY_WRITE_DISABLED, "Invalid write disabled value!");
}
if (!writeDisabled) {
Object wdv = properties.get(KEY_WRITE_DELAY_VALUE);
if (wdv == null) {
throw new ConfigurationException(KEY_WRITE_DELAY_VALUE, "Missing write delay value!");
}
try {
writeDelayValue = Integer.parseInt((String) wdv);
} catch (Exception e) {
throw new ConfigurationException(KEY_WRITE_DELAY_VALUE, "Invalid write delay value!");
}
if (writeDelayValue <= 0) {
throw new ConfigurationException(KEY_WRITE_DELAY_VALUE, "Invalid write delay value!");
}
Object wdu = properties.get(KEY_WRITE_DELAY_TIMEUNIT);
if (wdu != null) {
try {
writeDelayUnit = TimeUnit.valueOf(((String) wdu).toUpperCase());
} catch (Exception e) {
throw new ConfigurationException(KEY_WRITE_DELAY_TIMEUNIT, "Invalid write delay unit!");
}
}
}
}
ResettableTimer timer = (ResettableTimer) m_timerRef.get();
if (timer != null) {
timer.shutDown();
}
m_timerRef.compareAndSet(timer, writeDisabled ? null : new ResettableTimer(this, writeDelayValue, writeDelayUnit));
}
/**
* Retrieves the serialized repository from disk.
*
* @return the retrieved repository, never <code>null</code>.
* @throws IOException in case the retrieval of the repository failed.
*/
protected Map retrieve() throws IOException {
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(m_file));
return new RoleRepositorySerializer().deserialize(is);
} catch (FileNotFoundException exception) {
// Don't bother; file does not exist...
return Collections.emptyMap();
} catch (IOException exception) {
exception.printStackTrace();
throw exception;
} finally {
closeSafely(is);
}
}
/**
* Stores the given repository to disk as serialized objects.
*
* @param roleRepository the repository to store, cannot be <code>null</code>.
* @throws IOException in case storing the repository failed.
*/
protected void store(Map roleRepository) throws IOException {
OutputStream os = null;
try {
os = new BufferedOutputStream(new FileOutputStream(m_file));
new RoleRepositorySerializer().serialize(roleRepository, os);
} finally {
closeSafely(os);
}
}
/**
* Closes a given resource, ignoring any exceptions that may come out of this.
*
* @param resource the resource to close, can be <code>null</code>.
*/
private void closeSafely(Closeable resource) {
if (resource != null) {
try {
resource.close();
} catch (IOException e) {
// Ignore
}
}
}
/**
* Flushes the current repository to disk.
*
* @throws IOException in case of problems storing the repository.
*/
private void flush() throws IOException {
store(new HashMap(m_entries));
}
/**
* Notifies the background timer to schedule a task for storing the
* contents of this store to disk.
*/
private void scheduleTask() {
ResettableTimer timer = (ResettableTimer) m_timerRef.get();
if (timer != null && !timer.isShutDown()) {
timer.schedule();
}
}
}