/*
* Copyright (c) 2014 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.schema.persist;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import eu.esdihumboldt.hale.common.core.io.CachingImportProvider;
import eu.esdihumboldt.hale.common.core.io.HaleIO;
import eu.esdihumboldt.hale.common.core.io.IOProviderConfigurationException;
import eu.esdihumboldt.hale.common.core.io.ProgressIndicator;
import eu.esdihumboldt.hale.common.core.io.Value;
import eu.esdihumboldt.hale.common.core.io.report.IOReport;
import eu.esdihumboldt.hale.common.core.io.report.IOReporter;
import eu.esdihumboldt.hale.common.core.io.report.impl.IOMessageImpl;
import eu.esdihumboldt.hale.common.schema.io.impl.AbstractSchemaReader;
import eu.esdihumboldt.hale.common.schema.model.Schema;
/**
* Base class for schema readers that are able to cache a loaded schema through
* a {@link Value} representation.
*
* @author Simon Templer
*/
public abstract class AbstractCachedSchemaReaderBase extends AbstractSchemaReader
implements CachingImportProvider {
private Value cache;
private Schema schema;
private boolean cacheUpdate = false;
private boolean provideCache = false;
@Override
protected IOReport execute(ProgressIndicator progress, IOReporter reporter)
throws IOProviderConfigurationException, IOException {
if (validCache(cache) && useCache(cache)) {
reporter.info(new IOMessageImpl("Loading schema from cached result...", null));
schema = loadFromCache(cache, progress, reporter);
}
else {
try {
schema = loadFromSource(progress, reporter);
} catch (Exception e) {
reporter.error("Error loading schema from source", e);
if (reporter.isSuccess()) {
reporter.setSuccess(false);
}
}
if (provideCache && schema != null && reporter.isSuccess()) {
try {
cache = storeInCache(schema);
reporter.info(new IOMessageImpl("Created cached schema representation", null));
} catch (Exception e) {
reporter.error(new IOMessageImpl(
"Failed to create a representation of the schema for caching", e));
// invalidate cache
cache = null;
}
cacheUpdate = true;
}
if (!reporter.isSuccess() && validCache(cache) && useCacheAsFallback()) {
schema = loadFromCache(cache, progress, reporter);
}
}
return reporter;
}
/**
* @return if the cache should be used as fall-back if loading the source
* fails
*/
protected boolean useCacheAsFallback() {
// by default use the cache as fall-back if loading the source fails
return true;
}
@Override
public void setProvideCache() {
provideCache = true;
}
/**
* Store the given schema as a value to be cached.
*
* @param schema the schema to cache
* @return the cache value, may be <code>null</code>
* @throws Exception if an error occurs storing the schema as {@link Value}
*/
protected abstract Value storeInCache(Schema schema) throws Exception;
/**
* Load the schema from the given cache. On success the reporter must be
* updated accordingly.<br>
* <br>
* This method is called if both {@link #validCache(Value)} and
* {@link #useCache(Value)} returned <code>true</code>.<br>
*
* @param cache the cache value
* @param progress the progress
* @param reporter the reporter
* @return the schema loaded from the cache value
*/
protected abstract Schema loadFromCache(Value cache, ProgressIndicator progress,
IOReporter reporter);
/**
* Determines if with the current configuration and source the cache should
* be used to load the schema or not. This method is only called after
* {@link #validCache(Value)} returned <code>true</code>.<br>
* <br>
* The default implementation checks if the source location/stream is
* readable and allows using the cache if it's not.
*
* @param cache the cache value
* @return if the cache should be used to load the schema
*/
protected boolean useCache(Value cache) {
// test if the source is accessible
URI loc = getSource().getLocation();
if (loc != null) {
// try based on URI
return !HaleIO.testStream(loc, true);
}
else {
// try opening stream
try (InputStream in = getSource().getInput()) {
in.read();
return false;
} catch (Exception e) {
return true;
}
}
}
/**
* Determines if the given value provides valid cache information that could
* be used in {@link #loadFromCache(Value, ProgressIndicator, IOReporter)}.
* <br>
* <br>
* The default implementation classifies the cache value as valid if it is
* not <code>null</code> or the NULL value.
*
* @param cache the cache value
* @return if the cache value is valid
*/
protected boolean validCache(Value cache) {
return cache != null && cache.getValue() != null;
}
/**
* Load the schema from the {@link #getSource()}. On success the reporter
* must be updated accordingly.
*
* @param progress the progress indicator
* @param reporter the reporter
* @return the loaded schema
* @throws IOProviderConfigurationException if the I/O provider was not
* configured properly
* @throws IOException if an I/O operation fails
*/
protected abstract Schema loadFromSource(ProgressIndicator progress, IOReporter reporter)
throws IOProviderConfigurationException, IOException;
@Override
public Schema getSchema() {
return schema;
}
@Override
public boolean isCancelable() {
return false;
}
@Override
public void setCache(Value cache) {
this.cache = cache;
}
@Override
public Value getCache() {
return cache;
}
@Override
public boolean isCacheUpdate() {
return cacheUpdate;
}
}