PageRenderTime 97ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/timerservice/timerservice-mk2/src/main/java/org/jboss/ejb3/timerservice/mk2/persistence/filestore/FileTimerPersistence.java

https://github.com/bmaxwell/jboss-ejb3
Java | 336 lines | 258 code | 33 blank | 45 comment | 31 complexity | 057f94d204413b5c1ec1121ed0af6df2 MD5 | raw file
  1. /*
  2. * JBoss, Home of Professional Open Source
  3. * Copyright 2010, Red Hat Inc., and individual contributors as indicated
  4. * by the @authors tag. See the copyright.txt in the distribution for a
  5. * full listing of individual contributors.
  6. *
  7. * This is free software; you can redistribute it and/or modify it
  8. * under the terms of the GNU Lesser General Public License as
  9. * published by the Free Software Foundation; either version 2.1 of
  10. * the License, or (at your option) any later version.
  11. *
  12. * This software is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this software; if not, write to the Free
  19. * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  20. * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  21. */
  22. package org.jboss.ejb3.timerservice.mk2.persistence.filestore;
  23. import org.jboss.ejb3.timerservice.mk2.persistence.TimerEntity;
  24. import org.jboss.ejb3.timerservice.mk2.persistence.TimerPersistence;
  25. import org.jboss.logging.Logger;
  26. import org.jboss.marshalling.InputStreamByteInput;
  27. import org.jboss.marshalling.Marshaller;
  28. import org.jboss.marshalling.MarshallerFactory;
  29. import org.jboss.marshalling.MarshallingConfiguration;
  30. import org.jboss.marshalling.ModularClassResolver;
  31. import org.jboss.marshalling.OutputStreamByteOutput;
  32. import org.jboss.marshalling.Unmarshaller;
  33. import org.jboss.marshalling.river.RiverMarshallerFactory;
  34. import org.jboss.modules.ModuleLoader;
  35. import javax.transaction.Status;
  36. import javax.transaction.Synchronization;
  37. import javax.transaction.SystemException;
  38. import javax.transaction.TransactionManager;
  39. import javax.transaction.TransactionSynchronizationRegistry;
  40. import java.io.File;
  41. import java.io.FileInputStream;
  42. import java.io.FileNotFoundException;
  43. import java.io.FileOutputStream;
  44. import java.io.IOException;
  45. import java.util.ArrayList;
  46. import java.util.HashMap;
  47. import java.util.List;
  48. import java.util.Map;
  49. import java.util.concurrent.ConcurrentHashMap;
  50. import java.util.concurrent.ConcurrentMap;
  51. import java.util.concurrent.locks.Lock;
  52. import java.util.concurrent.locks.ReentrantLock;
  53. /**
  54. * File based persistent timer store.
  55. * <p/>
  56. * TODO: this is fairly hackey at the moment, it should be registered as an XA resource to support proper XA semantics
  57. *
  58. * @author Stuart Douglas
  59. */
  60. public class FileTimerPersistence implements TimerPersistence {
  61. private final TransactionManager transactionManager;
  62. private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
  63. private final File baseDir;
  64. private final boolean createIfNotExists;
  65. private static final Logger logger = Logger.getLogger(FileTimerPersistence.class);
  66. private final MarshallerFactory factory;
  67. private final MarshallingConfiguration configuration;
  68. /**
  69. * map of timed object id : timer id : timer
  70. */
  71. private final ConcurrentMap<String, Map<String, TimerEntity>> timers = new ConcurrentHashMap<String, Map<String, TimerEntity>>();
  72. private final ConcurrentMap<String, Lock> locks = new ConcurrentHashMap<String, Lock>();
  73. private final ConcurrentMap<String, String> directories = new ConcurrentHashMap<String, String>();
  74. private volatile boolean started = false;
  75. public FileTimerPersistence(final TransactionManager transactionManager, final TransactionSynchronizationRegistry transactionSynchronizationRegistry, final File baseDir, final boolean createIfNotExists, final ModuleLoader moduleLoader) {
  76. this.transactionManager = transactionManager;
  77. this.transactionSynchronizationRegistry = transactionSynchronizationRegistry;
  78. this.baseDir = baseDir;
  79. this.createIfNotExists = createIfNotExists;
  80. RiverMarshallerFactory factory = new RiverMarshallerFactory();
  81. MarshallingConfiguration configuration = new MarshallingConfiguration();
  82. configuration.setClassResolver(ModularClassResolver.getInstance(moduleLoader));
  83. this.configuration = configuration;
  84. this.factory = factory;
  85. }
  86. @Override
  87. public synchronized void start() {
  88. started = true;
  89. if (!baseDir.exists()) {
  90. if (createIfNotExists) {
  91. if (!baseDir.mkdirs()) {
  92. throw new RuntimeException("Could not create timer file store directory " + baseDir);
  93. }
  94. } else {
  95. throw new RuntimeException("Timer file store directory " + baseDir + " does not exist");
  96. }
  97. }
  98. if (!baseDir.isDirectory()) {
  99. throw new RuntimeException("Timer file store directory " + baseDir + " is not a directory");
  100. }
  101. }
  102. @Override
  103. public synchronized void stop() {
  104. timers.clear();
  105. started = false;
  106. }
  107. @Override
  108. public void persistTimer(final TimerEntity timerEntity) {
  109. final Lock lock = getLock(timerEntity.getTimedObjectId());
  110. try {
  111. final int status = transactionManager.getStatus();
  112. if (status == Status.STATUS_NO_TRANSACTION ||
  113. status == Status.STATUS_UNKNOWN) {
  114. try {
  115. lock.lock();
  116. Map<String, TimerEntity> map = getTimers(timerEntity.getTimedObjectId());
  117. map.put(timerEntity.getId(), timerEntity);
  118. writeFile(timerEntity);
  119. } finally {
  120. lock.unlock();
  121. }
  122. } else {
  123. transactionSynchronizationRegistry.registerInterposedSynchronization(new PersistTransactionSynchronization(timerEntity, lock));
  124. }
  125. } catch (SystemException e) {
  126. throw new RuntimeException(e);
  127. }
  128. }
  129. @Override
  130. public TimerEntity loadTimer(final String id, final String timedObjectId) {
  131. final Lock lock = getLock(timedObjectId);
  132. try {
  133. lock.lock();
  134. final Map<String, TimerEntity> timers = getTimers(timedObjectId);
  135. return timers.get(id);
  136. } finally {
  137. lock.unlock();
  138. }
  139. }
  140. @Override
  141. public void removeTimer(final TimerEntity timerEntity) {
  142. final Lock lock = getLock(timerEntity.getTimedObjectId());
  143. try {
  144. lock.lock();
  145. //remove is not a transactional operation, as it only happens once the timer has expired
  146. final Map<String, TimerEntity> timers = getTimers(timerEntity.getTimedObjectId());
  147. timers.remove(timerEntity.getId());
  148. File file = fileName(timerEntity.getTimedObjectId(), timerEntity.getId());
  149. if(file.exists()) {
  150. if(!file.delete()) {
  151. logger.error("Could not remove persistent timer " + file);
  152. }
  153. }
  154. } finally {
  155. lock.unlock();
  156. }
  157. }
  158. @Override
  159. public List<TimerEntity> loadActiveTimers(final String timedObjectId) {
  160. final Lock lock = getLock(timedObjectId);
  161. try {
  162. lock.lock();
  163. final Map<String, TimerEntity> timers = getTimers(timedObjectId);
  164. return new ArrayList<TimerEntity>(timers.values());
  165. } finally {
  166. lock.unlock();
  167. }
  168. }
  169. private Lock getLock(final String timedObjectId) {
  170. Lock lock = locks.get(timedObjectId);
  171. if (lock == null) {
  172. final Lock addedLock = new ReentrantLock();
  173. lock = locks.putIfAbsent(timedObjectId, addedLock);
  174. if (lock == null) {
  175. lock = addedLock;
  176. }
  177. }
  178. return lock;
  179. }
  180. /**
  181. * Gets the timer map, loading from the persistent store if necessary. Should be called under lock
  182. *
  183. * @param timedObjectId The timed object id
  184. * @return The timers for the object
  185. */
  186. private Map<String, TimerEntity> getTimers(final String timedObjectId) {
  187. Map<String, TimerEntity> map = timers.get(timedObjectId);
  188. if (map == null) {
  189. map = loadTimersFromFile(timedObjectId);
  190. timers.put(timedObjectId, map);
  191. }
  192. return map;
  193. }
  194. private Map<String, TimerEntity> loadTimersFromFile(final String timedObjectId) {
  195. final Map<String, TimerEntity> timers = new HashMap<String, TimerEntity>();
  196. try {
  197. final File file = new File(getDirectory(timedObjectId));
  198. if (!file.exists()) {
  199. //no timers exist yet
  200. return timers;
  201. } else if (!file.isDirectory()) {
  202. logger.error(file + " is not a directory, could not restore timers");
  203. return timers;
  204. }
  205. Unmarshaller unmarshaller = factory.createUnmarshaller(configuration);
  206. for (File timerFile : file.listFiles()) {
  207. FileInputStream in = null;
  208. try {
  209. in = new FileInputStream(timerFile);
  210. unmarshaller.start(new InputStreamByteInput(in));
  211. final TimerEntity entity = unmarshaller.readObject(TimerEntity.class);
  212. timers.put(entity.getId(), entity);
  213. unmarshaller.finish();
  214. } catch (Exception e) {
  215. logger.error("Could not restore timer from " + timerFile, e);
  216. } finally {
  217. if (in != null) {
  218. try {
  219. in.close();
  220. } catch (IOException e) {
  221. logger.error("error closing file ", e);
  222. }
  223. }
  224. }
  225. }
  226. } catch (Exception e) {
  227. logger.error("Could not restore timers for " + timedObjectId, e);
  228. }
  229. return timers;
  230. }
  231. private File fileName(String timedObjectId, String timerId) {
  232. return new File(getDirectory(timedObjectId) + File.separator + timerId.replace(File.separator, "-"));
  233. }
  234. /**
  235. * Gets the directory for a given timed object, making sure it exists.
  236. * @param timedObjectId The timed object
  237. * @return The directory
  238. */
  239. private String getDirectory(String timedObjectId) {
  240. String dirName = directories.get(timedObjectId);
  241. if (dirName == null) {
  242. dirName = baseDir.getAbsolutePath() + File.separator + timedObjectId.replace(File.separator, "-");
  243. File file = new File(dirName);
  244. if(!file.exists()) {
  245. if(!file.mkdirs()) {
  246. logger.error("Could not create directory " + file + " to persist EJB timers.");
  247. }
  248. }
  249. directories.put(timedObjectId, dirName);
  250. }
  251. return dirName;
  252. }
  253. private void writeFile(TimerEntity entity) {
  254. //write out our temporary file
  255. final File file = fileName(entity.getTimedObjectId(), entity.getId());
  256. FileOutputStream fileOutputStream = null;
  257. try {
  258. fileOutputStream = new FileOutputStream(file, false);
  259. final Marshaller marshaller = factory.createMarshaller(configuration);
  260. marshaller.start(new OutputStreamByteOutput(fileOutputStream));
  261. marshaller.writeObject(entity);
  262. marshaller.finish();
  263. fileOutputStream.flush();
  264. fileOutputStream.getFD().sync();
  265. } catch (FileNotFoundException e) {
  266. throw new RuntimeException(e);
  267. } catch (IOException e) {
  268. throw new RuntimeException(e);
  269. } finally {
  270. if (fileOutputStream != null) {
  271. try {
  272. fileOutputStream.close();
  273. } catch (IOException e) {
  274. logger.error("IOException closing file ", e);
  275. }
  276. }
  277. }
  278. }
  279. private final class PersistTransactionSynchronization implements Synchronization {
  280. private final TimerEntity timer;
  281. private final Lock lock;
  282. public PersistTransactionSynchronization(final TimerEntity timer, final Lock lock) {
  283. this.timer = timer;
  284. this.lock = lock;
  285. }
  286. @Override
  287. public void beforeCompletion() {
  288. }
  289. @Override
  290. public void afterCompletion(final int status) {
  291. try {
  292. lock.lock();
  293. if (status == Status.STATUS_COMMITTED) {
  294. Map<String, TimerEntity> map = getTimers(timer.getTimedObjectId());
  295. map.put(timer.getId(), timer);
  296. writeFile(timer);
  297. }
  298. } finally {
  299. lock.unlock();
  300. }
  301. }
  302. }
  303. }