| // Copyright (C) 2014 The Android Open Source Project |
| // |
| // 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 com.googlesource.gerrit.plugins.websession.flatfile; |
| |
| import com.google.common.cache.Cache; |
| import com.google.common.cache.CacheStats; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.httpd.WebSessionManager; |
| import com.google.gerrit.httpd.WebSessionManager.Val; |
| import com.google.inject.Inject; |
| import com.google.inject.Singleton; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| 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.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.OutputStream; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.UUID; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.ExecutionException; |
| |
| @Singleton |
| public class FlatFileWebSessionCache implements |
| Cache<String, WebSessionManager.Val> { |
| private static final Logger log = LoggerFactory |
| .getLogger(FlatFileWebSessionCache.class); |
| |
| private final File dir; |
| |
| @Inject |
| public FlatFileWebSessionCache(@WebSessionDir File dir) { |
| this.dir = dir; |
| if (!dir.exists()) { |
| log.info(dir + " not found. Creating it."); |
| dir.mkdir(); |
| } |
| } |
| |
| @Override |
| public ConcurrentMap<String, Val> asMap() { |
| ConcurrentMap<String, Val> map = new ConcurrentHashMap<>(); |
| File[] files = dir.listFiles(); |
| if (files == null) { |
| return map; |
| } |
| for (File f : files) { |
| Val v = readFile(f); |
| if (v != null) { |
| map.put(f.getName(), v); |
| } |
| } |
| return map; |
| } |
| |
| @Override |
| public void cleanUp() { |
| // do nothing |
| } |
| |
| @Override |
| public Val get(String key, Callable<? extends Val> valueLoader) |
| throws ExecutionException { |
| Val value = getIfPresent(key); |
| if (value == null) { |
| try { |
| value = valueLoader.call(); |
| } catch (Exception e) { |
| throw new ExecutionException(e); |
| } |
| } |
| return value; |
| } |
| |
| @Override |
| public ImmutableMap<String, Val> getAllPresent(Iterable<?> keys) { |
| ImmutableMap.Builder<String, Val> mapBuilder = |
| new ImmutableMap.Builder<>(); |
| for (Object key : keys) { |
| Val v = getIfPresent(key); |
| if (v != null) { |
| mapBuilder.put((String) key, v); |
| } |
| } |
| return mapBuilder.build(); |
| } |
| |
| @Override |
| @Nullable |
| public Val getIfPresent(Object key) { |
| if (key instanceof String) { |
| File f = new File(dir, (String) key); |
| return readFile(f); |
| } |
| return null; |
| } |
| |
| @Override |
| public void invalidate(Object key) { |
| if (key instanceof String) { |
| deleteFile(new File(dir, (String) key)); |
| } |
| } |
| |
| @Override |
| public void invalidateAll() { |
| File[] files = dir.listFiles(); |
| if (files != null) { |
| for (File f : files) { |
| deleteFile(f); |
| } |
| } |
| } |
| |
| @Override |
| public void invalidateAll(Iterable<?> keys) { |
| for (Object key : keys) { |
| invalidate(key); |
| } |
| } |
| |
| @Override |
| public void put(String key, Val value) { |
| File tempFile = null; |
| try { |
| tempFile = File.createTempFile(UUID.randomUUID().toString(), null, dir); |
| try (OutputStream fileStream = new FileOutputStream(tempFile); |
| ObjectOutputStream objStream = new ObjectOutputStream(fileStream)) { |
| objStream.writeObject(value); |
| |
| File f = new File(dir, key); |
| if (!tempFile.renameTo(f)) { |
| log.warn("Cannot put into cache " + dir.getAbsolutePath() |
| + "; error renaming temp file"); |
| } |
| } |
| } catch (FileNotFoundException e) { |
| log.warn("Cannot put into cache " + dir.getAbsolutePath(), e); |
| } catch (IOException e) { |
| log.warn("Cannot put into cache " + dir.getAbsolutePath(), e); |
| } finally { |
| if (tempFile != null) { |
| deleteFile(tempFile); |
| } |
| } |
| } |
| |
| @Override |
| public void putAll(Map<? extends String, ? extends Val> keys) { |
| for (Entry<? extends String, ? extends Val> e : keys.entrySet()) { |
| put(e.getKey(), e.getValue()); |
| } |
| } |
| |
| @Override |
| public long size() { |
| String[] files = dir.list(); |
| if (files == null) { |
| return 0; |
| } |
| return files.length; |
| } |
| |
| @Override |
| public CacheStats stats() { |
| log.warn("stats() unimplemented"); |
| return null; |
| } |
| |
| private Val readFile(File f) { |
| try (InputStream fileStream = new FileInputStream(f); |
| ObjectInputStream objStream = new ObjectInputStream(fileStream)) { |
| return (Val) objStream.readObject(); |
| } catch (ClassNotFoundException e) { |
| log.warn("Entry " + f.getName() + " in cache " + dir.getAbsolutePath() |
| + " has an incompatible class and can't be deserialized. " |
| + "Invalidating entry."); |
| invalidate(f.getName()); |
| } catch (IOException e) { |
| log.warn("Cannot read cache " + dir.getAbsolutePath(), e); |
| } |
| return null; |
| } |
| |
| private void deleteFile(File f) { |
| if (f.exists() && !f.delete()) { |
| log.warn("Cannot delete file " + f.getName() + " from cache " |
| + dir.getAbsolutePath()); |
| } |
| } |
| } |