NativeStoreConfig.java

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
package org.eclipse.rdf4j.sail.nativerdf.config;

import static org.eclipse.rdf4j.model.util.Values.literal;
import static org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreSchema.FORCE_SYNC;
import static org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreSchema.NAMESPACE_CACHE_SIZE;
import static org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreSchema.NAMESPACE_ID_CACHE_SIZE;
import static org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreSchema.TRIPLE_INDEXES;
import static org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreSchema.VALUE_CACHE_SIZE;
import static org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreSchema.VALUE_ID_CACHE_SIZE;

import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.util.Configurations;
import org.eclipse.rdf4j.model.util.ModelException;
import org.eclipse.rdf4j.model.vocabulary.CONFIG;
import org.eclipse.rdf4j.sail.base.config.BaseSailConfig;
import org.eclipse.rdf4j.sail.config.SailConfigException;

/**
 * @author Arjohn Kampman
 */
public class NativeStoreConfig extends BaseSailConfig {

	private String tripleIndexes;
	private boolean forceSync = false;
	private int valueCacheSize = -1;
	private int valueIDCacheSize = -1;
	private int namespaceCacheSize = -1;
	private int namespaceIDCacheSize = -1;

	// WAL: expose max segment bytes via config (optional)
	private long walMaxSegmentBytes = -1L;

	// Additional WAL configuration options
	private int walQueueCapacity = -1;
	private int walBatchBufferBytes = -1;
	private String walSyncPolicy; // expects one of ValueStoreWalConfig.SyncPolicy
	private long walSyncIntervalMillis = -1L;
	private long walIdlePollIntervalMillis = -1L;
	private String walDirectoryName; // relative to dataDir

	// When true, WAL bootstrap runs synchronously during open before accepting new values
	private boolean walSyncBootstrapOnOpen = false;

	// When true, reconstruct ValueStore from WAL during open if empty/missing
	private boolean walAutoRecoverOnOpen = false;

	// When false, completely disable the ValueStore WAL
	private boolean walEnabled = true;

	public NativeStoreConfig() {
		super(NativeStoreFactory.SAIL_TYPE);
	}

	public NativeStoreConfig(String tripleIndexes) {
		this();
		setTripleIndexes(tripleIndexes);
	}

	public NativeStoreConfig(String tripleIndexes, boolean forceSync) {
		this(tripleIndexes);
		setForceSync(forceSync);
	}

	/*---------*
	 * Methods *
	 *---------*/

	public String getTripleIndexes() {
		return tripleIndexes;
	}

	public void setTripleIndexes(String tripleIndexes) {
		this.tripleIndexes = tripleIndexes;
	}

	public boolean getForceSync() {
		return forceSync;
	}

	public void setForceSync(boolean forceSync) {
		this.forceSync = forceSync;
	}

	public int getValueCacheSize() {
		return valueCacheSize;
	}

	public void setValueCacheSize(int valueCacheSize) {
		this.valueCacheSize = valueCacheSize;
	}

	public int getValueIDCacheSize() {
		return valueIDCacheSize;
	}

	public void setValueIDCacheSize(int valueIDCacheSize) {
		this.valueIDCacheSize = valueIDCacheSize;
	}

	public int getNamespaceCacheSize() {
		return namespaceCacheSize;
	}

	public void setNamespaceCacheSize(int namespaceCacheSize) {
		this.namespaceCacheSize = namespaceCacheSize;
	}

	public int getNamespaceIDCacheSize() {
		return namespaceIDCacheSize;
	}

	public void setNamespaceIDCacheSize(int namespaceIDCacheSize) {
		this.namespaceIDCacheSize = namespaceIDCacheSize;
	}

	public long getWalMaxSegmentBytes() {
		return walMaxSegmentBytes;
	}

	public void setWalMaxSegmentBytes(long walMaxSegmentBytes) {
		this.walMaxSegmentBytes = walMaxSegmentBytes;
	}

	public int getWalQueueCapacity() {
		return walQueueCapacity;
	}

	public void setWalQueueCapacity(int walQueueCapacity) {
		this.walQueueCapacity = walQueueCapacity;
	}

	public int getWalBatchBufferBytes() {
		return walBatchBufferBytes;
	}

	public void setWalBatchBufferBytes(int walBatchBufferBytes) {
		this.walBatchBufferBytes = walBatchBufferBytes;
	}

	public String getWalSyncPolicy() {
		return walSyncPolicy;
	}

	public void setWalSyncPolicy(String walSyncPolicy) {
		this.walSyncPolicy = walSyncPolicy;
	}

	public long getWalSyncIntervalMillis() {
		return walSyncIntervalMillis;
	}

	public void setWalSyncIntervalMillis(long walSyncIntervalMillis) {
		this.walSyncIntervalMillis = walSyncIntervalMillis;
	}

	public long getWalIdlePollIntervalMillis() {
		return walIdlePollIntervalMillis;
	}

	public void setWalIdlePollIntervalMillis(long walIdlePollIntervalMillis) {
		this.walIdlePollIntervalMillis = walIdlePollIntervalMillis;
	}

	public String getWalDirectoryName() {
		return walDirectoryName;
	}

	public void setWalDirectoryName(String walDirectoryName) {
		this.walDirectoryName = walDirectoryName;
	}

	public boolean getWalSyncBootstrapOnOpen() {
		return walSyncBootstrapOnOpen;
	}

	public void setWalSyncBootstrapOnOpen(boolean walSyncBootstrapOnOpen) {
		this.walSyncBootstrapOnOpen = walSyncBootstrapOnOpen;
	}

	public boolean getWalAutoRecoverOnOpen() {
		return walAutoRecoverOnOpen;
	}

	public void setWalAutoRecoverOnOpen(boolean walAutoRecoverOnOpen) {
		this.walAutoRecoverOnOpen = walAutoRecoverOnOpen;
	}

	public boolean getWalEnabled() {
		return walEnabled;
	}

	public void setWalEnabled(boolean walEnabled) {
		this.walEnabled = walEnabled;
	}

	@Override
	public Resource export(Model m) {
		if (Configurations.useLegacyConfig()) {
			return exportLegacy(m);
		}

		Resource implNode = super.export(m);
		m.setNamespace(CONFIG.NS);

		if (tripleIndexes != null) {
			m.add(implNode, CONFIG.Native.tripleIndexes, literal(tripleIndexes));
		}
		if (forceSync) {
			m.add(implNode, CONFIG.Native.forceSync, literal(forceSync));
		}
		if (valueCacheSize >= 0) {
			m.add(implNode, CONFIG.Native.valueCacheSize, literal(valueCacheSize));
		}
		if (valueIDCacheSize >= 0) {
			m.add(implNode, CONFIG.Native.valueIDCacheSize, literal(valueIDCacheSize));
		}
		if (namespaceCacheSize >= 0) {
			m.add(implNode, CONFIG.Native.namespaceCacheSize, literal(namespaceCacheSize));
		}
		if (namespaceIDCacheSize >= 0) {
			m.add(implNode, CONFIG.Native.namespaceIDCacheSize, literal(namespaceIDCacheSize));
		}
		// WAL configuration properties
		if (walMaxSegmentBytes >= 0) {
			m.add(implNode, CONFIG.Native.walMaxSegmentBytes, literal(walMaxSegmentBytes));
		}
		if (walQueueCapacity > 0) {
			m.add(implNode, CONFIG.Native.walQueueCapacity, literal(walQueueCapacity));
		}
		if (walBatchBufferBytes > 0) {
			m.add(implNode, CONFIG.Native.walBatchBufferBytes, literal(walBatchBufferBytes));
		}
		if (walSyncPolicy != null) {
			m.add(implNode, CONFIG.Native.walSyncPolicy, literal(walSyncPolicy));
		}
		if (walSyncIntervalMillis >= 0) {
			m.add(implNode, CONFIG.Native.walSyncIntervalMillis, literal(walSyncIntervalMillis));
		}
		if (walIdlePollIntervalMillis >= 0) {
			m.add(implNode, CONFIG.Native.walIdlePollIntervalMillis, literal(walIdlePollIntervalMillis));
		}
		if (walDirectoryName != null) {
			m.add(implNode, CONFIG.Native.walDirectoryName, literal(walDirectoryName));
		}
		// Only export when true to avoid noise
		if (walSyncBootstrapOnOpen) {
			m.add(implNode, CONFIG.Native.walSyncBootstrapOnOpen, literal(true));
		}
		if (walAutoRecoverOnOpen) {
			m.add(implNode, CONFIG.Native.walAutoRecoverOnOpen, literal(true));
		}
		if (!walEnabled) {
			m.add(implNode, CONFIG.Native.walEnabled, literal(false));
		}

		return implNode;
	}

	private Resource exportLegacy(Model m) {
		Resource implNode = super.export(m);
		m.setNamespace("ns", NativeStoreSchema.NAMESPACE);

		if (tripleIndexes != null) {
			m.add(implNode, TRIPLE_INDEXES, literal(tripleIndexes));
		}
		if (forceSync) {
			m.add(implNode, FORCE_SYNC, literal(forceSync));
		}
		if (valueCacheSize >= 0) {
			m.add(implNode, VALUE_CACHE_SIZE, literal(valueCacheSize));
		}
		if (valueIDCacheSize >= 0) {
			m.add(implNode, VALUE_ID_CACHE_SIZE, literal(valueIDCacheSize));
		}
		if (namespaceCacheSize >= 0) {
			m.add(implNode, NAMESPACE_CACHE_SIZE, literal(namespaceCacheSize));
		}
		if (namespaceIDCacheSize >= 0) {
			m.add(implNode, NAMESPACE_ID_CACHE_SIZE, literal(namespaceIDCacheSize));
		}
		// legacy export does not define a schema term; omit for legacy

		return implNode;
	}

	@Override
	public void parse(Model m, Resource implNode) throws SailConfigException {
		super.parse(m, implNode);

		try {

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.tripleIndexes, TRIPLE_INDEXES)
					.ifPresent(lit -> setTripleIndexes(lit.getLabel()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.forceSync, FORCE_SYNC)
					.ifPresent(lit -> {
						try {
							setForceSync(lit.booleanValue());
						} catch (IllegalArgumentException e) {
							throw new SailConfigException(
									"Boolean value required for " + CONFIG.Native.forceSync + " property, found "
											+ lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.valueCacheSize, VALUE_CACHE_SIZE)
					.ifPresent(lit -> {
						try {
							setValueCacheSize(lit.intValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException(
									"Integer value required for " + CONFIG.Native.valueCacheSize + " property, found "
											+ lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.valueIDCacheSize, VALUE_ID_CACHE_SIZE)
					.ifPresent(lit -> {
						try {
							setValueIDCacheSize(lit.intValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException(
									"Integer value required for " + CONFIG.Native.valueIDCacheSize + " property, found "
											+ lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.namespaceCacheSize, NAMESPACE_CACHE_SIZE)
					.ifPresent(lit -> {
						try {
							setNamespaceCacheSize(lit.intValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException(
									"Integer value required for " + CONFIG.Native.namespaceCacheSize
											+ " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.namespaceIDCacheSize, NAMESPACE_ID_CACHE_SIZE)
					.ifPresent(lit -> {
						try {
							setNamespaceIDCacheSize(lit.intValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException(
									"Integer value required for " + CONFIG.Native.namespaceIDCacheSize
											+ " property, found " + lit);
						}
					});

			// WAL configuration properties
			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walMaxSegmentBytes)
					.ifPresent(lit -> {
						try {
							setWalMaxSegmentBytes(lit.longValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException("Long value required for "
									+ CONFIG.Native.walMaxSegmentBytes + " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walQueueCapacity)
					.ifPresent(lit -> {
						try {
							setWalQueueCapacity(lit.intValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException("Integer value required for "
									+ CONFIG.Native.walQueueCapacity + " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walBatchBufferBytes)
					.ifPresent(lit -> {
						try {
							setWalBatchBufferBytes(lit.intValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException("Integer value required for "
									+ CONFIG.Native.walBatchBufferBytes + " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walSyncPolicy)
					.ifPresent(lit -> setWalSyncPolicy(lit.getLabel()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walSyncIntervalMillis)
					.ifPresent(lit -> {
						try {
							setWalSyncIntervalMillis(lit.longValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException("Long value required for "
									+ CONFIG.Native.walSyncIntervalMillis + " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walIdlePollIntervalMillis)
					.ifPresent(lit -> {
						try {
							setWalIdlePollIntervalMillis(lit.longValue());
						} catch (NumberFormatException e) {
							throw new SailConfigException("Long value required for "
									+ CONFIG.Native.walIdlePollIntervalMillis + " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walDirectoryName)
					.ifPresent(lit -> setWalDirectoryName(lit.getLabel()));

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walSyncBootstrapOnOpen)
					.ifPresent(lit -> {
						try {
							setWalSyncBootstrapOnOpen(lit.booleanValue());
						} catch (IllegalArgumentException e) {
							throw new SailConfigException("Boolean value required for "
									+ CONFIG.Native.walSyncBootstrapOnOpen + " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walAutoRecoverOnOpen)
					.ifPresent(lit -> {
						try {
							setWalAutoRecoverOnOpen(lit.booleanValue());
						} catch (IllegalArgumentException e) {
							throw new SailConfigException("Boolean value required for "
									+ CONFIG.Native.walAutoRecoverOnOpen + " property, found " + lit);
						}
					});

			Configurations.getLiteralValue(m, implNode, CONFIG.Native.walEnabled)
					.ifPresent(lit -> {
						try {
							setWalEnabled(lit.booleanValue());
						} catch (IllegalArgumentException e) {
							throw new SailConfigException(
									"Boolean value required for " + CONFIG.Native.walEnabled + " property, found "
											+ lit);
						}
					});
		} catch (ModelException e) {
			throw new SailConfigException(e.getMessage(), e);
		}
	}
}