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

org.tentackle.pdo.PdoCacheFileStore Maven / Gradle / Ivy

/*
 * 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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy