Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Tentackle - https://tentackle.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.tentackle.pdo;
import org.tentackle.app.Application;
import org.tentackle.common.StringHelper;
import org.tentackle.log.Logger;
import org.tentackle.misc.UserHomeJavaDir;
import org.tentackle.session.ModificationTracker;
import org.tentackle.session.Session;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* A file-based backup store for {@link PdoCache}.
* Serializes the cache to the filesystem to improve client startup times,
* especially for preloading caches.
* Makes only sense for remote client applications.
*
* @param the PDO type
* @author harald
*/
public class PdoCacheFileStore> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.get(PdoCacheFileStore.class);
private static final String CACHE_FILE_EXTENSION = ".ser"; // extension for cache files
private transient PdoCache cache; // the cache to back up
// persisted data
private final String applicationName; // the application name (null if file-backup disabled, i.e. not a client application)
private final String backendName; // the name of the backend
private final String fileName; // the filename
@SuppressWarnings("serial")
private List objects; // the objects in the cache
@SuppressWarnings("serial")
private Map tableSerials; // map of table name to table serial
/**
* Creates a file store for a cache.
*
* @param cache the cache
*/
public PdoCacheFileStore(PdoCache cache) {
this.cache = cache;
Application application = Application.getInstance();
if (application != null) {
Session session = application.getSession();
applicationName = StringHelper.toAsciiLetterOrDigit(application.getName());
backendName = StringHelper.toAsciiLetterOrDigit(session.getUrl());
}
else {
applicationName = null;
backendName = null;
}
fileName = StringHelper.toAsciiLetterOrDigit(cache.getObjectClass().getName()) + CACHE_FILE_EXTENSION;
}
/**
* Writes the cache to persistent storage.
* Throws PdoRuntimeException if writing failed.
*
* @return true if cache has been persisted, false if cache isn't persistable (application is not a client)
*/
public synchronized boolean writeToFile() {
if (isPersistable()) {
initializeForWrite();
File file = getFile();
try (ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
os.writeObject(this);
LOGGER.info(() -> objects.size() + " objects written to " + file.getAbsolutePath());
return true;
}
catch (IOException ioe) {
throw new PdoRuntimeException("cannot write to file " + file.getAbsolutePath(), ioe);
}
}
else {
return false;
}
}
/**
* Loads a cache from persistent storage.
* Throws PdoRuntimeException if reading failed.
*
* @param session the session
* @return true if loaded from persistent storage or data was missing,
* false if cache isn't persistable (application is not a client)
*/
public synchronized boolean readFromFile(Session session) {
if (isPersistable()) {
File file = getFile();
try (ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
Object object = is.readObject();
if (object instanceof PdoCacheFileStore) {
@SuppressWarnings("unchecked")
PdoCacheFileStore cacheFileStore = (PdoCacheFileStore) object;
cacheFileStore.verifyAfterRead();
cacheFileStore.applyCache(session, cache);
LOGGER.info(() -> cache.getObjects().size() + " objects loaded from " + file.getAbsolutePath());
return true;
}
else {
throw new PdoRuntimeException(
"file " + file.getAbsolutePath() + " does not contain cache data! Found: " + object.getClass().getName());
}
}
catch (ClassNotFoundException cnfe) {
throw new PdoRuntimeException("cannot load java class from file " + file.getAbsolutePath(), cnfe);
}
catch (FileNotFoundException fnfe) {
// this is okay: no such file -> nothing loaded
return true;
}
catch (IOException ioe) {
throw new PdoRuntimeException("cannot read from file " + file.getAbsolutePath(), ioe);
}
}
else {
return false;
}
}
/**
* Determines whether cache is persistable.
*
* @return true if persistable
*/
private boolean isPersistable() {
return applicationName != null && fileName != null;
}
/**
* Initializes the cache data for being written to storage.
*/
private void initializeForWrite() {
// build cache data (no check for key change)
objects = cache.getObjects(null, false);
// get current table serials
tableSerials = new HashMap<>();
for (T object : objects) {
tableSerials.computeIfAbsent(object.getTableName(), this::getTableSerial);
}
}
/**
* Verifies the cache data after being read from storage.
*/
private void verifyAfterRead() {
String expectedApplicationName = null;
String expectedBackendName = null;
Application application = Application.getInstance();
if (application != null) {
expectedApplicationName = StringHelper.toAsciiLetterOrDigit(application.getName());
Session session = application.getSession();
if (session.isRemote()) {
expectedBackendName = StringHelper.toAsciiLetterOrDigit(session.getUrl());
}
}
String expectedFileName = StringHelper.toAsciiLetterOrDigit(cache.getObjectClass().getName()) + CACHE_FILE_EXTENSION;
if (!Objects.equals(expectedApplicationName, applicationName)) {
throw new PdoRuntimeException("cache data belongs to application '" + applicationName +
"', expected (running) application is '" + expectedApplicationName + "'");
}
if (!Objects.equals(expectedBackendName, backendName)) {
throw new PdoRuntimeException("cache data belongs to backend '" + backendName +
"', expected (running) backend is '" + expectedBackendName + "'");
}
if (!Objects.equals(expectedFileName, fileName)) {
throw new PdoRuntimeException("cache data belongs to file '" + fileName +
"', expected filename is '" + expectedFileName + "'");
}
}
/**
* Applies the cache data to the cache.
*
* @param session the session assumed for loaded objects
* @param cache the cache
*/
private void applyCache(Session session, PdoCache cache) {
cache.invalidate(); // remove all objects from cache (if any)
for (T object : objects) {
object.setSession(session); // this is transient
cache.add(object);
}
// set up the domain context list and set the selectAll-lists if cache is preloading
cache.updateContextInfo();
// check if cache is expired by asking the server
for (Map.Entry entry : tableSerials.entrySet()) {
String tableName = entry.getKey();
long tableSerial = entry.getValue();
long currentSerial = getTableSerial(tableName);
if (currentSerial != tableSerial) {
cache.expire(null, tableName, currentSerial);
}
}
this.cache = cache;
}
/**
* Gets the current table serial.
*
* @param tableName the table name
* @return the serial, 0 if no access made yet
*/
private long getTableSerial(String tableName) {
if (objects != null && !objects.isEmpty()) {
return ModificationTracker.getInstance().getSerial(tableName);
}
else {
return 0;
}
}
/**
* Gets the file for this cache.
*
* @return the file
*/
private File getFile() {
File dir;
File cacheDir = new File(UserHomeJavaDir.getDirectory("cache", "pdo"), applicationName);
if (!cacheDir.exists()) {
// make application directory if it doesn't exist
cacheDir.mkdir();
LOGGER.info(() -> "created cache application directory " + cacheDir.getAbsolutePath());
}
if (backendName != null) {
File backendDir = new File(cacheDir, backendName);
if (!backendDir.exists()) {
backendDir.mkdir();
LOGGER.info(() -> "created cache backend application directory " + backendDir.getAbsolutePath());
}
dir = backendDir;
}
else {
dir = cacheDir;
}
return new File(dir, fileName);
}
}