package com.laytonsmith.persistence; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.annotations.MustUseOverride; import com.laytonsmith.core.CHVersion; import com.laytonsmith.core.SimpleDocumentation; import java.io.IOException; import java.util.Map; import java.util.Set; /** * All data sources must implement this interface. It provides methods to gather * data about a data source, gather data from the data source, and (possibly) * write to the data source. * * */ @MustUseOverride public interface DataSource extends SimpleDocumentation { /** * Returns a list of keys stored in this interface. If keyBase is empty, * all keys are returned, otherwise, only keys matching the * namespace will be returned. * @return */ public Set<String[]> keySet(String [] keyBase) throws DataSourceException; /** * Returns a list of keys, pre-concatenated into dot notation. This may * be equally inefficient for all data sources (getting keySet, then * doing the concatenation one at a time), however if a data source is * able to optimize for this, it is able. The same rule applies as keySet, * if keyBase is empty, all keys are returned, otherwise, only the * keys for that namespace are returned. * * @return */ public Set<String> stringKeySet(String [] keyBase) throws DataSourceException; /** * Given a namespace, returns all the keys in this data source that are * in the namespace. For instance, if a.b.c is requested, then both the * keys a.b.c.d and a.b.c.e would be returned. * * @param namespace * @return */ public Set<String[]> getNamespace(String[] namespace) throws DataSourceException; /** * Retrieves a single value from the data source. If the value doesn't exist * at all, null should be returned. * @param key * @return */ public String get(String[] key) throws DataSourceException; /** * Retrieves a namespace from the data source. Unlike get, this * will return multiple values, mapped to their key. For instance, * getValues({"a"}) might return {"a": "value1", "a.b": "value2"}, where * the "lead value" "a" is the namespace for which we retrieve all values * under that key, including the value at "a" itself, if set. * @param leadKey * @return * @throws DataSourceException */ public Map<String[], String> getValues(String[] leadKey) throws DataSourceException; /** * Sets a value in the data source. If value is null, the key is * removed. * * @param key * @param value * @return True if the value was changed, false otherwise. * @throws ReadOnlyException If this data source is inherently read * only, it will throw a read only exception if this method is called. * @throws IllegalArgumentException If the key is invalid */ public boolean set(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException, IllegalArgumentException; /** * Instructs this data source to repopulate its internal structure based * on this data provided. The method will be called if the data source * needs to refresh itself, though for inherently transient data sources * (or if the transient data source flag is set), this method may do * nothing. If the data source is unable to populate itself, it may * throw an exception informing the user that there is no way to read * the data at this time. */ public void populate() throws DataSourceException; /** * For this instance of the data source, adds a modifier flag to the * data source. This does not preclude a data source from inherently * having certain flags, nor will having a flag in the array returned by * invalidModifiers() preclude it from being set here. Some settings may * not need to be inherently acted upon, however they may be referenced * for informational purposes if nothing else. * * @param modifier */ public void addModifier(DataSourceModifier modifier); /** * If a data source always has a particular modifier, it should return * those here. This is used to determine when to display configuration * warnings, if a modifier is used in cases where it is implied. If the * array would be empty, null may be returned. * * @param modifier * @return */ public DataSourceModifier[] implicitModifiers(); /** * If a data source has no possible way of acting on a modifier, it * should return those here. This is used to determine when to display * configuration warnings, if a modifier is used in cases where it can't * be acted on. If the array would be empty, null may be returned. * * @return */ public DataSourceModifier[] invalidModifiers(); /** * Returns a list of modifiers attached to this data source instance. * * @return */ public Set<DataSourceModifier> getModifiers(); /** * Returns true if this data source has the specified modifier. * @param modifier The modifier to check * @return True if the modifier is present */ public boolean hasModifier(DataSourceModifier modifier); /** * Returns true if the data source contains this key or not. * @param key * @return */ public boolean hasKey(String[] key) throws DataSourceException; /** * Removes the key entirely from this data source. * @param key * @throws DataSourceException */ public void clearKey(DaemonManager dm, String [] key) throws DataSourceException, ReadOnlyException, IOException; /** * Starts a transaction for this data source. If the data source is not * transient, this has no effect, but if it is, then all reads and writes * will not be transient until the transaction is stopped. */ public void startTransaction(DaemonManager dm); /** * When stopping the transaction, any pending writes will potentially * occur then, so this can throw an exception at that point, * much like set could, however, it will not throw ReadOnlyExceptions. * If an exception is thrown during the middle of the transaction, it is up * to the calling code to call stopTransaction(). If rollback is true, then * any changes since the last startTransaction call will be rolled * back. If any exceptions are thrown from this method, and rollback is * true, then no changes will have been made to the data set. * @param rollback If true, changes since the transaction started will * be rolled back. * @throws IOException If any cached data couldn't be written out * @throws DataSourceException If any other exception occurs */ public void stopTransaction(DaemonManager dm, boolean rollback) throws DataSourceException, IOException; /** * Disconnects the Data Source. This may not do anything for some connection types, but * for streaming connection types, this will close the connection. Regardless, after * calling this method, it should be assumed that future calls to this data source * will be invalid, and might throw errors if used. * @throws DataSourceException In the event that some kind of internal error occurs, this * may be thrown. */ public void disconnect() throws DataSourceException; /** * These are the valid modifiers for a generic connection. Not all data * sources can support all of these, and some are inherently present or * unsupportable on certain connection types. */ public enum DataSourceModifier implements SimpleDocumentation { READONLY("Makes the connection read-only. That is to say, calls to store_data() on the keys mapped to this data source will always fail.", CHVersion.V3_3_1), TRANSIENT("The data from this source is not cached. Note that for file based data sources, this makes it incredibly inefficient for large data sources," + " but makes it possible for multiple things to read and write to a source at the same time. If the connection is not read-only, a lock file will" + " be created while the file is being written to (which will be the filename with .lock appended), which should be respected by other applications" + " to prevent corruption. During read/write operations, if the lock file exists, the call to retrieve that data will block until the lock file" + " goes away. File based connections that are NOT transient are loaded up at startup, and only writes require file system access from that point" + " on. It is assumed that nothing else will be editing the data source, and so data is not re-read again, which means that leaving off the transient" + " flag makes connections much more efficient. Database driven connections are always transient. ", CHVersion.V3_3_1), HTTP("Makes the connection source be retrieved via http instead of assuming a local file. Connections via http are always read-only." + " If the connection is also transient, a call to get_value() cannot be used in synchronous mode, and will fail if async" + " mode is not used. ", CHVersion.V3_3_1), HTTPS("Makes the connection source be retrieved via https instead of assuming a local file. Connections via http are always read-only." + " If the connection is also transient, a call to get_value() cannot be used in synchronous mode, and will fail if async" + " mode is not used. ", CHVersion.V3_3_1), ASYNC("Forces retrievals to this connection to require asyncronous usage. This is handy if an otherwise blocking data source has gotten" + " too large to allow synchonous connections, or if you are using a medium/large data source transiently.", CHVersion.V3_3_1), PRETTYPRINT("For text based files, where it is applicable and possible, if there is a way to \"Pretty Print\" the data, do so. This usually comes" + " at the cost of file size, but makes it easier to read in a text editor. For some data sources, this is not possible, due to the file" + " layout requirements of the protocol itself.", CHVersion.V3_3_1), SSH("Retrieves the file via SSH. This cannot be used in combination with the HTTP or HTTPS flags. The file path must match the syntax used" + " by SCP connections, for instance: ssh:yml://user@host:/path/to/file/over/ssh.yml. This will only work with public-key authentication" + " however, since there is no practical way to input your password otherwise. Since this is a remote IO connection, async is implied if this" + " modifier is set.", CHVersion.V3_3_1); private CHVersion since; private String documentation; private DataSourceModifier(String documentation, CHVersion since) { this.documentation = documentation; this.since = since; } @Override public String getName() { return name().toLowerCase(); } @Override public String docs() { return documentation; } @Override public CHVersion since() { return since; } public static boolean isModifier(String scheme) { for (DataSourceModifier modifier : DataSourceModifier.values()) { if (modifier.getName().equalsIgnoreCase(scheme)) { return true; } } return false; } public static DataSourceModifier getModifier(String scheme) { return DataSourceModifier.valueOf(scheme.toUpperCase()); } } }