All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.qwazr.jdbc.cache.ResultSetOnDiskCacheImpl Maven / Gradle / Ivy

/*
 * Copyright 2016-2017 Emmanuel Keller / QWAZR
 * 

* 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.qwazr.jdbc.cache; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.stream.Stream; class ResultSetOnDiskCacheImpl extends ResultSetCacheImpl { private final Path cacheDirectory; private final ConcurrentHashMap activeKeys; ResultSetOnDiskCacheImpl(final Path cacheDirectory) { if (!Files.exists(cacheDirectory)) { try { Files.createDirectories(cacheDirectory); } catch (IOException e) { throw CacheException.of("Cannot create the cache directory: " + cacheDirectory, e); } } if (!Files.isDirectory(cacheDirectory)) throw CacheException .of("The path is not a directory, or the directory cannot be created: " + cacheDirectory); this.cacheDirectory = cacheDirectory; this.activeKeys = new ConcurrentHashMap<>(); } /** * Return the cached ResultSet for the given key. * If the entry does not exist the ResultSet is extracted by calling the given resultSetProvider. * If the entry does not exist and no resultSetProvider is given (null) an SQLException is thrown. * * @param statement the cached statement * @param key the generated key for this statement * @param resultSetProvider the optional result provider * @return the cached ResultSet * @throws SQLException if the statement cannot be executed */ public CachedOnDiskResultSet get(final CachedStatement statement, final String key, final Provider resultSetProvider) throws SQLException { final Path resultSetPath = cacheDirectory.resolve(key); if (!Files.exists(resultSetPath)) { if (resultSetProvider == null) throw new SQLException("No cache available"); buildCache(key, resultSetPath, resultSetProvider); } try { return new CachedOnDiskResultSet(statement, resultSetPath); } catch (IOException e) { throw new SQLException("Can not read cache", e); } } private void buildCache(final String key, final Path resultSetPath, final Provider resultSetProvider) throws SQLException { final Lock keyLock = activeKeys.computeIfAbsent(key, s -> new ReentrantLock(true)); try { keyLock.lock(); try { final Path tempPath = cacheDirectory.resolve(key + ".tmp"); try { final ResultSet providedResultSet = resultSetProvider.provide(); ResultSetWriter.write(tempPath, providedResultSet); Files.move(tempPath, resultSetPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } catch (IOException e) { throw new SQLException("Failed in renaming the file " + tempPath, e); } finally { try { Files.deleteIfExists(tempPath); } catch (IOException e) { // Quiet } } } finally { keyLock.unlock(); } } finally { activeKeys.remove(key); } } /** * Check if an entry is available for this key. * * @param key the computed key * @return always true if the cache entry exists */ public boolean checkIfExists(final String key) { final Path resultSetPath = cacheDirectory.resolve(key); return Files.exists(resultSetPath); } private void parse(final Consumer consumer) throws SQLException { try (final Stream stream = Files.list(cacheDirectory)) { stream.forEach(path -> { final String name = path.getFileName().toString(); if (!name.endsWith(".tmp")) { final Lock keyLock = activeKeys.computeIfAbsent(name, s -> new ReentrantLock(true)); try { keyLock.lock(); try { consumer.accept(path); } finally { keyLock.unlock(); } } finally { activeKeys.remove(name); } } }); } catch (CacheException e) { throw e.getSQLException(); } catch (IOException e) { throw CacheException.of(e).getSQLException(); } } @Override public void flush() throws SQLException { parse(path -> { try { Files.deleteIfExists(path); } catch (IOException e) { throw CacheException.of(e); } }); } private Path checkCacheDirectory() { return Objects.requireNonNull(cacheDirectory, "No cache directory"); } @Override public void flush(final Statement stmt) throws SQLException { try { Files.deleteIfExists(cacheDirectory.resolve(checkKey(stmt))); } catch (IOException e) { throw CacheException.of(e); } } @Override public int size() throws SQLException { final AtomicInteger counter = new AtomicInteger(); parse(path -> { if (!path.endsWith(".tmp")) counter.incrementAndGet(); }); return counter.get(); } @Override public boolean exists(Statement stmt) throws SQLException { return Files.exists(checkCacheDirectory().resolve(checkKey(stmt))); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy