/* * Copyright 2013 NGDATA nv * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.lilyproject.repository.api; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.Set; // IMPORTANT: // The Repository implementation might be wrapped to add automatic retrying of operations in case // of IO exceptions or when no Lily servers are available. In case this fails, a // RetriesExhausted(Record|Type|Blob)Exception is thrown. Therefore, all methods in this interface // should declare this exception. Also, the remote implementation can cause IO exceptions which are // dynamically wrapped in Record|Type|BlobException, thus this exception (which is a parent class // of the RetriesExhausted exceptions) should be in the throws clause of all methods. /** * A table in a {@link Repository}. * * <p>A table is a list of {@link Record}s. A table can contain records of different {@link RecordType}s.</p> * * <p>Table provides methods for performing CRUD operations on records.</p> * * <p>Records are stored in a table sorted by their {@link RecordId}. You can * scan sequentially over the records using {@link #getScanner(RecordScan)}. */ public interface LTable { /** * Instantiates a new Record object. * * <p>This is only a factory method, nothing is created in the repository. */ Record newRecord() throws RecordException; /** * Instantiates a new Record object with the RecordId already filled in. * * <p>This is only a factory method, nothing is created in the repository. */ Record newRecord(RecordId recordId) throws RecordException; /** * Creates a new record in the repository. * * <p>A Record object can be instantiated via {@link #newRecord}. * * <p>If a recordId is given in {@link Record}, that id is used. If not, a new id is generated and available * from the returned Record object. * * @throws RecordExistsException if a record with the given recordId already exists * @throws RecordNotFoundException if the master record for a variant record does not exist * @throws InvalidRecordException if an empty record is being created * @throws FieldTypeNotFoundException * @throws RecordTypeNotFoundException */ Record create(Record record) throws RepositoryException, InterruptedException; /** * Shortcut for {@link #update(Record, boolean, boolean, java.util.List) * update(record, updateVersion, useLatestRecordType, null)}. */ Record update(Record record, boolean updateVersion, boolean useLatestRecordType) throws RepositoryException, InterruptedException; /** * Shortcut for {@link #update(Record, boolean, boolean, java.util.List) update(record, false, true, null)}. */ Record update(Record record) throws RepositoryException, InterruptedException; /** * Shortcut for {@link #update(Record, boolean, boolean, java.util.List) update(record, false, true, conditions)}. */ Record update(Record record, List<MutationCondition> conditions) throws RepositoryException, InterruptedException; /** * Updates an existing record in the repository. * * <p>An update can either update the versioned and/or non-versioned fields (in this last case a new version * will be created), or it can update the versioned-mutable fields. This last one is an update of (manipulation * of) an existing version and cannot be combined with updating fields in the versioned and non-versioned scope. * So these are two distinct operations, you have to choose which one you want to do, this is done by setting * the updateVersion argument. Most of the time you will update versioned or non-versioned fields, for this you * set updateVersion to false. * * <p>The provided Record object can either be obtained by reading a record via {@link #read} or * it can also be instantiated from scratch via {@link #newRecord}. * * <p>The Record object can be limited to contain only those fields that you are interested in changing, so it * can be sparsely filled. If you want to be sure of the value of all fields, then specify them all, even if * they have not changed compared to what you read, since another update might have been performed concurrently. * Fields that are not present in the record will not be deleted, deleting fields * needs to be done explicitly by adding them to the list of fields to delete, see {@link * Record#getFieldsToDelete}. * * <p>If the record contains any changed versioned fields, a new version will be created. The number of the created * version will be available on the returned Record object. * * <p>If no record type is specified in the record object, the record type that is currently stored (in the * non-versioned scope) will be used. Newly created versions always get the same record type as the current * record type of the non-versioned scope (= either the one specified in the Record, or if absent, from * what is currently stored). Usually you will want to move automatically to the latest version of * the record type. This can be done by setting the version to null in the record (if you also specify the name * in the record object), but more conveniently using the useLatestRecordType argument. * * <p><b>Updating an existing version: updating versioned-mutable fields</b> * * <p>The following applies to updating an existing version: * * <ul> * * <li>It is required to specify a version in the Record object. * * <li>If you do not specify a record type in the Record object, the record type of the versioned-mutable scope * of the version that is being modified will be set to the current one (= the stored one) of the non-versioned * scope, and possibly to its latest version depending on the argument useLatestRecordType. * * <li>If you do specify a record type in the Record object (using {@link Record#setRecordType(QName)}), the record * type of the versioned-mutable scope of the version that is being modified will be changed to it, but the record * type of the non-versioned scope will be left unmodified. This is in contrast to the record type of the versioned * scope, which is always brought to the record type of the non-versioned scope when a new version is created. * However, we found it should be possible to modify the versioned-mutable record type of an existing version * without influencing the current record state. * * </ul> * * <p><b>The returned record object</b> * * <p>The record object you supply as argument will not be modified, it is internally cloned an modified. Currently * these modifications are mostly limited to setting the resolved record type and version. The returned record * object will never contain any fields you did not specify in the Record object, so you might have to do a read * to see the full record situation (other fields might have been added by concurrent updates). This will be * addressed by issue <a href="http://dev.outerthought.org/trac/outerthought_lilyproject/ticket/93">93<a>.<p> * * <p><b>Conditionally updating a record: the conditions argument</b></p> * * <p>A conditional update allows to update a record only in case it satisfies certain conditions. This is * also known as "check and update (CAS - check and set)" or "optimistic concurrency control (OCC)".</p> * * <p>The condition can check on any of the record fields (of any scope), thus is not limited to the fields * provided in the record object. For versioned(-mutable) fields, the check is usually performed on the data * from the latest version, except in case updateVersion is true, then the data from version being updated * will be checked (both for the versioned and versioned-mutable fields).</p> * * <p>For more details on specifying the conditions, see {@link MutationCondition}. All the conditions should * be satisfied for the update to proceed, thus the conditions are AND-ed.</p> * * <p>In case one ore more conditions are not satisfied, NO exception is thrown, rather the responseStatus * field of the returned Record object is set to {@link ResponseStatus#CONFLICT}. So the caller who is interested * in knowing whether the update succeeded should check on this field. The returned record object will contain * the currently stored repository state, not the submitted record values.</p> * * <p>The conditions are checked before checking if the record actually needs updating, so you might get * a conflict response even if the stored record state corresponds to the supplied record state.</p> * * @param updateVersion if true, the version indicated in the record will be updated (i.e. only the mutable * fields will be updated) * otherwise, a new version of the record will be created (if it contains versioned * fields) * @param useLatestRecordType if true, the RecordType version given in the Record will be ignored and the latest * available RecordType will * be used while updating the Record * @param conditions optional (can be null), set of conditions that should be satisfied for the update to * proceed * @throws RecordNotFoundException if the record does not exist * @throws InvalidRecordException if no update information is provided * @throws RepositoryException TBD * @throws FieldTypeNotFoundException * @throws RecordTypeNotFoundException */ Record update(Record record, boolean updateVersion, boolean useLatestRecordType, List<MutationCondition> conditions) throws RepositoryException, InterruptedException; /** * Creates or updates a record, depending on whether the record already exists. * * <p>See {@link #createOrUpdate(Record, boolean)} for more details. */ Record createOrUpdate(Record record) throws RepositoryException, InterruptedException; /** * Creates or updates a record, depending on whether the record already exists. * * <p>This method has the advantage that you do not have to deal with {@link RecordExistsException} * (in case of create) or {@link RecordNotFoundException} (in case of update). * * <p>This method has the advantage over create that it can be safely retried in case of IO related problems, * without having to worry about whether the previous call did or did not go through, and thus avoiding * {@link RecordExistsException}'s or the creation of multiple records (in case the client did not * specify an ID). */ Record createOrUpdate(Record record, boolean useLatestRecordType) throws RepositoryException, InterruptedException; /** * @param recordId the id of the record to read, null is not allowed * @param fieldNames list of names of the fields to read or null to read all fields * @deprecated in favor of using varargs for the fieldNames. Please use {@link #read(List, QName...)} instead. * * Reads a record limited to a subset of the fields. Only the fields specified in the fieldNames list * will be * included. * * <p>Versioned and versioned-mutable fields will be taken from the latest version. * * <p>It is not an error if the record would not have a particular field, though it is an error to * specify * a non-existing field name. */ @Deprecated Record read(RecordId recordId, List<QName> fieldNames) throws RepositoryException, InterruptedException; /** * Reads a record. * If fieldNames are specified, the read is limited to include only this subset of fields. * Otherwise all fields of the record are read. * * <p>Versioned and versioned-mutable fields will be taken from the latest version. * * <p>It is not an error if the record would not have a particular field, though it is an error to specify * a non-existing field name. * * @param recordId the id of the record to read, null is not allowed * @param fieldNames names of the fields to read or null to read all fields */ Record read(RecordId recordId, QName... fieldNames) throws RepositoryException, InterruptedException; /** * @param recordIds or recordIds to read, null is not allowed * @param fieldNames list of names of the fields to read or null to read all fields * @return list of records that are read, can be smaller than the amount or requested ids when those are not found * @deprecated in favor of using varargs for the fieldNames. Please use {@link #read(List, QName...)} instead. * * Reads a list of records limited to a subset of the fields. Only the fields specified in the * fieldNames list will be * included. * * <p>Versioned and versioned-mutable fields will be taken from the latest version. * * <p>It is not an error if the records would not have a particular field, though it is an error to * specify * a non-existing field name. * * <p>No RecordNotFoundException is thrown when a record does not exist or has been deleted. * Instead, the returned list will not contain an entry for that requested id. */ @Deprecated List<Record> read(List<RecordId> recordIds, List<QName> fieldNames) throws RepositoryException, InterruptedException; /** * Reads a list of records. * If fieldNames are specified, the read is limited to include only this subset of fields. * Otherwise all fields are read. * * <p>Versioned and versioned-mutable fields will be taken from the latest version. * * <p>It is not an error if the records would not have a particular field, though it is an error to specify * a non-existing field name. * * <p>No RecordNotFoundException is thrown when a record does not exist or has been deleted. * Instead, the returned list will not contain an entry for that requested id. * * @param recordIds or recordIds to read, null is not allowed * @param fieldNames names of the fields to read or null to read all fields * @return list of records that are read, can be smaller than the amount or requested ids when those are not found */ List<Record> read(List<RecordId> recordIds, QName... fieldNames) throws RepositoryException, InterruptedException; /** * @deprecated in favor of using varargs for the fieldNames. Please use {@link #read(RecordId, Long, QName...)} * instead. * * Reads a specific version of a record limited to a subset of the fields. * * <p>If the given list of fields is empty, all fields will be read. */ @Deprecated Record read(RecordId recordId, Long version, List<QName> fieldNames) throws RepositoryException, InterruptedException; /** * Reads a specific version of a record. * If fieldNames are specified, the read is limited to include only this subset of fields. * Otherwise all fields are read. * * <p>If the given list of fields is empty, all fields will be read. */ Record read(RecordId recordId, Long version, QName... fieldNames) throws RepositoryException, InterruptedException; /** * @deprecated in favor of using varargs for the fieldNames. Please use * {@link #readVersions(RecordId, Long, Long, QName...)} instead. * * Reads all versions of a record between fromVersion and toVersion (both included), limited to a * subset * of the fields. * * <p>If the given list of fields is empty, all fields will be read. */ @Deprecated List<Record> readVersions(RecordId recordId, Long fromVersion, Long toVersion, List<QName> fieldNames) throws RepositoryException, InterruptedException; /** * Reads all versions of a record between fromVersion and toVersion (both included). * If fieldNames are specified, the read is limited to include only this subset of fields. * Otherwise all fields are read. * * <p>If the given list of fields is empty, all fields will be read.</p> * <p>When dealing with versionless records e.g. Records without any versioned fields then an empty list will be * returned</p> */ List<Record> readVersions(RecordId recordId, Long fromversion, Long toVersion, QName... fieldNames) throws RepositoryException, InterruptedException; /** * @param recordId id of the record to read * @param versions the list of versions to read, should not contain null values * @param fieldNames list of fields to read, if null all fields will be read * @return a list of records. The list can be smaller than the number of requested versions if some requested * versions * have a higher number than the highest existing version. * @deprecated in favor of using varargs for the fieldNames. Please use * {@link #read(RecordId, List<Long>, QName...)} instead. * * Reads all versions of a record listed the <code>versions</code>, limited to a subset of the fields. */ @Deprecated List<Record> readVersions(RecordId recordId, List<Long> versions, List<QName> fieldNames) throws RepositoryException, InterruptedException; /** * Reads all versions of a record listed the <code>versions</code>. * If fieldNames are specified, the read is limited to include only this subset of fields. * Otherwise all fields are read. * * @param recordId id of the record to read * @param versions the list of versions to read, should not contain null values * @param fieldNames names of fields to read, if null all fields will be read * @return a list of records. The list can be smaller than the number of requested versions if some requested * versions * have a higher number than the highest existing version. */ List<Record> readVersions(RecordId recordId, List<Long> versions, QName... fieldNames) throws RepositoryException, InterruptedException; /** * Reads a Record and also returns the mapping from QNames to IDs. * * <p>See {@link IdRecord} for more information. * * @param version version to load. Optional, can be null. * @param fieldIds load only the fields with these ids. optional, can be null. */ IdRecord readWithIds(RecordId recordId, Long version, List<SchemaId> fieldIds) throws RepositoryException, InterruptedException; /** * Delete a {@link Record} from the repository. * * @param recordId id of the record to delete */ void delete(RecordId recordId) throws RepositoryException, InterruptedException; /** * Conditionally delete a record from the repository. * * <p>The delete will only succeed if all the supplied conditions are satisfied. The conditions can check * on fields from all scopes, it are the values from the latest version which are used.</p> * * <p>In case the conditions are satisfied, this method returns null.</p> * * <p>In case the conditions are not satisfied, this method returns a Record object with its responseStatus * field set to {@link ResponseStatus#CONFLICT}. The fields contained in the record object will be those * referred to in the conditions, as far as they exist.</p> */ Record delete(RecordId recordId, List<MutationCondition> conditions) throws RepositoryException, InterruptedException; /** * Delete a {@link Record} from the repository. * * @param record the record to delete. The provided record will be consulted for recordId and attributes. */ void delete(Record record) throws RepositoryException, InterruptedException; /** * Returns an {@link java.io.OutputStream} for a blob. The binary data of a blob * must be written to this outputStream and the stream must be closed before * the blob may be stored in a {@link Record}. The method * {@link Blob#setValue(byte[])} will be called internally to update the * blob with the reference to where the blob is stored (possibly inlined). * * <p> * The {@link BlobStoreAccessFactory} will decide to which underlying * blobstore the data will be written. * * @param blob the blob for which to open an OutputStream * @return an OutputStream * @throws RepositoryException when an unexpected exception occurs */ OutputStream getOutputStream(Blob blob) throws RepositoryException, InterruptedException; /** * Returns a {@link BlobAccess} object which provides access to the blob metadata and the input stream to * read the blob's data. * * <p>A blob is retrieved by specifying the {record id, version, field name} coordinates. * And in case of ListValueType or PathValueType fields an array of indexes. * * @param recordId the id of the record containing the blob * @param version optionally a version of the record, if null the latest record version is used * @param fieldName the QName of the field containing the blob * @param indexes optionally, the position of a blob in a List or Path field, * where each index gives the position at a deeper level * @throws BlobNotFoundException thrown when no blob can be found at the given location * @throws BlobException thrown when opening an InputStream on the blob fails */ BlobAccess getBlob(RecordId recordId, Long version, QName fieldName, int... indexes) throws RepositoryException, InterruptedException; /** * Backwards compatibility method, which accepts null arguments for the indexes. * * <p>Due to autoboxing, this method will also be called in preference to the varargs variant when the number * of indexes is two or less. Therefore I've not marked it as deprecated, though it really is deprecated. * * <p>See {@link #getBlob(RecordId, Long, QName, int...)} */ BlobAccess getBlob(RecordId recordId, Long version, QName fieldName, Integer mvIndex, Integer hIndex) throws RepositoryException, InterruptedException; /** * Shortcut getBlob method where version and indexes are set to null. */ BlobAccess getBlob(RecordId recordId, QName fieldName) throws RepositoryException, InterruptedException; /** * Returns an {@link java.io.InputStream} from which the binary data of a blob can be read. * * <p>A blob is retrieved by specifying the {record id, version, field name} coordinates. * And in case of ListValueType or PathValueType fields an array of indexes. * * @param recordId the id of the record containing the blob * @param version optionally a version of the record, if null the latest record version is used * @param fieldName the QName of the field containing the blob * @param indexes optionally, the position of a blob in a List or Path field, * where each index gives the position at a deeper level * @throws BlobNotFoundException thrown when no blob can be found at the given location * @throws BlobException thrown when opening an InputStream on the blob fails */ InputStream getInputStream(RecordId recordId, Long version, QName fieldName, int... indexes) throws RepositoryException, InterruptedException; /** * Backwards compatibility method, which accepts null arguments for the indexes. * * <p>Due to autoboxing, this method will also be called in preference to the varargs variant when the number * of indexes is two or less. Therefore I've not marked it as deprecated, though it really is deprecated. * * <p>See {@link #getInputStream(RecordId, Long, QName, int...)} */ InputStream getInputStream(RecordId recordId, Long version, QName fieldName, Integer mvIndex, Integer hIndex) throws RepositoryException, InterruptedException; /** * Shortcut getInputStream method where version, and indexes are set to null. */ InputStream getInputStream(RecordId recordId, QName fieldName) throws RepositoryException, InterruptedException; /** * getInputStream method where the record containing the blob is given instead of its recordId. * This avoids an extra call on the repository to read the record. * This is especially useful for inline blobs. */ InputStream getInputStream(Record record, QName fieldName, int... indexes) throws RepositoryException, InterruptedException; /** * Backwards compatibility method, which accepts null arguments for the indexes. * * <p>Due to autoboxing, this method will also be called in preference to the varargs variant when the number * of indexes is two or less. Therefore I've not marked it as deprecated, though it really is deprecated. * * <p>See {@link #getInputStream(Record, QName, int...)} */ InputStream getInputStream(Record record, QName fieldName, Integer mvIndex, Integer hIndex) throws RepositoryException, InterruptedException; /** * Get all the variants that exist for the given recordId. * * @param recordId typically a master record id, if you specify a variant record id, its master will automatically * be used * @return the set of variants, including the master record id. Returns an empty list if the record would not * exist. */ Set<RecordId> getVariants(RecordId recordId) throws RepositoryException, InterruptedException; /** * Get a scanner to sequentially run over all, or a subset of, the records in the repository. * * <p>To run over a subset of the records, use the methods {@link RecordScan#setStartRecordId(RecordId)} * and {@link RecordScan#setStopRecordId(RecordId)}. Additionally, you can set filters on * the RecordScan to further restrain the returned records.</p> * * <p>Scanners always return the latest versions of records.</p> * * <p>Note that scans, and the filters defined as part of it, are not based on indexes: * the scan always runs over the defined range of records and checks each record one * by one. Hence, this is intended for batch-use, or for cases where you scan over a * manageable amount of records</p> * * @param scan the details of the scan to be performed */ RecordScanner getScanner(RecordScan scan) throws RepositoryException, InterruptedException; /** * Get a scanner to sequentially run over all, or a subset of, the records in the repository. This is the same as * {@link #getScanner(RecordScan)}, except that it returns {@link IdRecord} in stead of {@link Record} instances. * * <p>See {@link IdRecord} for more information. */ IdRecordScanner getScannerWithIds(RecordScan scan) throws RepositoryException, InterruptedException; /** * Returns a record builder object which can be used to compose a record object and create or update it on the * repository. */ RecordBuilder recordBuilder() throws RecordException, InterruptedException; /** * Returns the table name of this table. */ String getTableName(); /** * Returns the name of the repository to which this table belongs. */ String getRepositoryName(); }