/* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright 2006-2012 by respective authors (see below). All rights reserved. * * Licensed 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.red5.server.persistence; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import org.red5.server.api.persistence.IPersistable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread that writes modified persistent objects to the file system * periodically. * * @author The Red5 Project (red5@osflash.org) * @author Joachim Bauch (jojo@struktur.de) */ public class FilePersistenceThread implements Runnable { /** * Logger */ private Logger log = LoggerFactory.getLogger(FilePersistenceThread.class); /** * Interval to serialize modified objects in milliseconds. * */ // TODO: make this configurable private long storeInterval = 10000; /** * Modified objects. */ private Map<UpdateEntry, FilePersistence> objects = new ConcurrentHashMap<UpdateEntry, FilePersistence>(); /** * Singleton instance. */ private static volatile FilePersistenceThread instance = null; /** * Each FilePersistenceThread has its own scheduler and the executor is * guaranteed to only run a single task at a time. */ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); /** * Return singleton instance of the thread. * * @return singleton instance of thread. */ public static FilePersistenceThread getInstance() { return instance; } /** * Create instance of the thread. */ private FilePersistenceThread() { if (instance != null) { log.error("Instance was not null, this is not a good sign"); } instance = this; @SuppressWarnings("unused") final ScheduledFuture<?> instanceHandle = scheduler.scheduleAtFixedRate(this, storeInterval, storeInterval, java.util.concurrent.TimeUnit.MILLISECONDS); } /** * Notify thread that an object was modified in a persistence store. * * @param object * @param store */ protected void modified(IPersistable object, FilePersistence store) { FilePersistence previous = objects.put(new UpdateEntry(object, store), store); if (previous != null && !previous.equals(store)) { log.warn("Object {} was also modified in {}, saving instantly", new Object[] { object, previous }); previous.saveObject(object); } } /** * Write any pending objects for the given store to disk. * * @param store */ protected void notifyClose(FilePersistence store) { // Store pending objects for this store for (UpdateEntry entry : objects.keySet()) { if (!store.equals(entry.store)) { // Object is from different store continue; } try { objects.remove(entry); store.saveObject(entry.object); } catch (Throwable e) { log.error("Error while saving {} in {}. {}", new Object[] { entry.object, store, e }); } } } /** * Write modified objects to the file system periodically. */ public void run() { if (!objects.isEmpty()) { for (UpdateEntry entry : objects.keySet()) { try { objects.remove(entry); entry.store.saveObject(entry.object); } catch (Throwable e) { log.error("Error while saving {} in {}. {}", new Object[] { entry.object, entry.store, e }); } } } } /** * Cleanly shutdown the tasks */ public void shutdown() { scheduler.shutdown(); } /** * Informations about one entry to object. */ private static class UpdateEntry { /** Object to store. */ IPersistable object; /** Store the object should be serialized to. */ FilePersistence store; /** * Create new update entry. * * @param object object to serialize * @param store store the object should be serialized in */ UpdateEntry(IPersistable object, FilePersistence store) { this.object = object; this.store = store; } /** * Compare with another entry. * * @param other entry to compare to * @return <code>true</code> if entries match, otherwise <code>false</code> */ @Override public boolean equals(Object other) { if (!(other instanceof UpdateEntry)) { return false; } return (object.equals(((UpdateEntry) other).object) && store.equals(((UpdateEntry) other).store)); } /** * Return hash value for entry. * * @return the hash value */ @Override public int hashCode() { return object.hashCode() + store.hashCode(); } } }