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

com.peterphi.std.guice.common.serviceprops.jaxbref.JAXBNamedResourceFactory Maven / Gradle / Ivy

There is a newer version: 10.1.5
Show newest version
package com.peterphi.std.guice.common.serviceprops.jaxbref;

import com.peterphi.std.guice.common.lifecycle.GuiceLifecycleListener;
import com.peterphi.std.guice.common.serviceprops.composite.GuiceConfig;
import com.peterphi.std.util.jaxb.JAXBSerialiserFactory;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * The actual factory that constructs JAXB objects from configuration. Not user-facing.
 *
 * @param 
 */
class JAXBNamedResourceFactory
{
	/**
	 * Don't reload any more often than once a minute
	 */
	private long maxReloadRate = 60 * 1000;

	private final GuiceConfig config;
	private final JAXBSerialiserFactory factory;

	private final String name;
	private final Class clazz;

	/**
	 * Cache info for literal value
	 */
	private SoftReference inMemoryString = null;

	// Cache info for File value
	private File lastFile;
	private Long lastFileModified;

	/**
	 * Cache info for resolving the property value to a File / Resource.
* If this is set and lastFile is set then an identically-named resource can be immediately mapped to lastFile.
* If this is set and lastFile is null then this must be loaded as a classpath resource. */ private String lastResourceOrFile; // Reload rate-limiting private long lastLoaded = 0; private T cached = null; public JAXBNamedResourceFactory(final GuiceConfig config, final JAXBSerialiserFactory factory, final String name, final Class clazz) { this.config = config; this.factory = factory; this.name = name; this.clazz = clazz; } /** * Resolve this property reference to a deserialised JAXB value * * @return */ public T get() { String value = config.getRaw(name, null); if (value == null) throw new RuntimeException("Missing property for JAXB resource: " + name); if (isLiteralXML(value)) { return loadLiteralValue(value); } else { // If the value contains a variable reference then we should resolve them if (value.contains("${")) { value = config.get(name, null); // Now that the variables have been resolved the value might end up being a valid XML literal if (isLiteralXML(value)) return loadLiteralValue(value); } // Try to load from disk (N.B. if we have a cached value, respect max reload rate) if (cached == null || System.currentTimeMillis() > lastLoaded + maxReloadRate) { // The value must be a File or Resource reference! // Resolve the reference and check return loadFileOrResourceValue(value); } else { return cached; } } } /** * Returns true if and only if the string is a literal XML value.
* Since properties will not store a BOM the * * @param str * * @return */ private boolean isLiteralXML(final String str) { return (!str.isEmpty() && str.charAt(0) == '<'); } /** * Loads from a classpath resource or file. If the value can be resolved to a file then the file processing is cache-aware * (based on the last modified time on the file) * * @param str * * @return */ private T loadFileOrResourceValue(final String str) { // Ideally we want to resolve str to a File so we can be smart about reloading it // N.B. Even if str is a classpath reference it's possible it could still be resolved to a file on disk // We can't be smart about reloading non-File classpath resources but we assume they will not change final File file; final URL resource; { // First consult the cache of str -> File (if present) if (lastFile != null && StringUtils.equals(str, lastResourceOrFile)) { file = lastFile; resource = null; } else if (new File(str).exists()) { file = new File(str); resource = null; } else { try { resource = getClass().getResource(str); if (resource == null) throw new IllegalArgumentException("JAXB config for " + name + ": no such file or resource: " + str); final URI uri = resource.toURI(); if (StringUtils.equalsIgnoreCase(uri.getScheme(), "file")) { file = new File(uri); } else { file = null; } } catch (URISyntaxException e) { throw new IllegalArgumentException("Error processing classpath resource " + str + " from property " + name + ": URL to URI failed", e); } } } // Now resource the file or resource if (file == null) { assert (resource != null); // We reload the cached value ONLY if the resource path changes, we don't take into account the resolved URL if (cached == null || !StringUtils.equals(str, lastResourceOrFile)) { lastResourceOrFile = str; return loadResourceValue(resource); } else { return cached; } } else { lastResourceOrFile = str; return loadFileValue(file); } } /** * Load from a classpath resource; reloads every time * * @param resource * * @return */ private T loadResourceValue(final URL resource) { try (final InputStream is = resource.openStream()) { cached = null; // Prevent the old value from being used return setCached(clazz.cast(factory.getInstance(clazz).deserialise(is))); } catch (IOException e) { throw new RuntimeException("Error loading JAXB resource " + name + " " + clazz + " from " + resource, e); } } /** * Load from a file. *

This loader is cache-aware and will only reload if the file's last modified timestamp changes

* * @param file * * @return */ private T loadFileValue(final File file) { final boolean reload; // First time / first time with this file if (cached == null || lastFile == null || lastFileModified == null || !lastFile.equals(file)) { lastFile = file; lastFileModified = file.lastModified(); reload = true; } else // Same file, so check timestamp to decide on reload { final long modified = file.lastModified(); reload = modified > lastFileModified; if (reload) lastFileModified = modified; } if (reload) { cached = null; // Prevent the old value from being used setCached(clazz.cast(factory.getInstance(clazz).deserialise(file))); } return cached; } /** * Load a literal XML document stored in a config property. *

This loader is cache-aware and will only reload if the literal XML value changes

* * @param str * * @return */ private T loadLiteralValue(final String str) { // Literal in-memory value final String cachedStr = (inMemoryString != null) ? inMemoryString.get() : null; if (StringUtils.equals(str, cachedStr)) { // Cached value is still valid! } else { cached = null; // Prevent the old value from being used // Cached value is not valid anymore, re-parse setCached(clazz.cast(factory.getInstance(clazz).deserialise(str))); inMemoryString = new SoftReference<>(str); } return cached; } private T setCached(T value) { this.cached = value; lastLoaded = System.currentTimeMillis(); // Probably a bit of a hack to reuse GuiceLifecycleListener here rather than a new JAXBLifecycle interface if (value instanceof GuiceLifecycleListener) { ((GuiceLifecycleListener) value).postConstruct(); } return value; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy