| /** |
| * 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(); |
| } |
| } |
| } |