/* * Copyright 2010 Outerthought bvba * * 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.util.List; import java.util.Map; /** * A Record is the core entity managed by the {@link Repository}. * * <p>A Record can be instantiated via {@link Repository#newRecord() Repository.newRecord} or retrieved via * {@link Repository#read(RecordId) Repository.read}. As all entities within this API, records are dumb data objects. * * <p>All Record-related CRUD operations are available on the {@link Repository} interface: * {@link Repository#create(Record) Create}, {@link Repository#read(RecordId) Read}, * {@link Repository#update(Record) Update}, {@link Repository#delete(RecordId) Delete}. * * <p>A Record object is not necessarily a representation of a complete record. * When {@link Repository#read(RecordId, java.util.List) reading} * a record, you can specify to only read certain fields. Likewise, when {@link Repository#update(Record) updating}, * you only need to put the fields in this object that you want to be updated. But it is not necessary to remove * unchanged fields from this object, the repository will compare with the current situation and ignore * unchanged fields. * * <p>Since for an update, this object only needs to contain the fields you want to update, fields that are * not in this object will not be automatically removed from the record. Rather, you have to say explicitly which * fields should be deleted by adding them to the {@link #getFieldsToDelete() fields-to-delete} list. If the * fields-to-delete list contains field names that do not exist in the record, then these will be ignored upon * update, rather than causing an exception. * * <p>The {@link RecordType} and its version define the schema of the record. As is explained in more detail in * Lily's repository model documentation, a record has a pointer to three (possibly) different record types, one * for each scope. * */ public interface Record { void setId(RecordId recordId); RecordId getId(); void setVersion(Long version); /** * Returns the version. * * <p>For a record without versions, this returns null. In all other cases, this returns the version number * of the loaded version (= the latest one by default), even if none of the versioned fields would actually be * loaded. */ Long getVersion(); /** * Sets the record type and record type version. * * <p>This actually sets the record type of the non-versioned scope, which is considered to be the primary * record type. Upon save, the record types of the other scopes will also be set to this record type (if there * are any fields changed in those scopes, thus if a new version will be created). * * @param version version number, or null if you want the repository to pick the last version available when * storing the record. */ void setRecordType(QName name, Long version); /** * Shortcut for setRecordType(name, null) */ void setRecordType(QName name); /** * Returns the record type of the non-versioned scope. */ QName getRecordTypeName(); /** * Returns the record type version of the non-versioned scope. */ Long getRecordTypeVersion(); /** * Sets the record type of a specific scope */ void setRecordType(Scope scope, QName name, Long version); /** * Returns the record type of a specific scope */ QName getRecordTypeName(Scope scope); /** * Returns the record type version of a specific scope */ Long getRecordTypeVersion(Scope scope); /** * Sets a field to a value, possibly replacing a previous value. * * <p>The provided QName should be the QName of {@link FieldType} that exists within the repository. * * <p>The type of the given value should correspond to the * {@link ValueType#getClass() value type} of the {@link FieldType#getValueType() field type}. */ void setField(QName fieldName, Object value); /** * Deletes a field from this object and optionally adds it to the list of fields to delete upon save. * * <p>If the field is not present, this does not throw an exception. In this case, the field will still * be added to the list of fields to delete. Use {@link #hasField} if you want to check if a field is * present in this Record instance. * * @param addToFieldsToDelete if false, the field will only be removed from this value object, but not be added * to the {@link #getFieldsToDelete}. */ void delete(QName fieldName, boolean addToFieldsToDelete); /** * Gets a field, throws an exception if it is not present. * * <p>To do anything useful with the returned value, you have to cast it to the type you expect. * See also {@link ValueType}. * * @throws FieldNotFoundException if the field is not present in this Record object. To avoid the exception, * use {@link #hasField}. */ <T> T getField(QName fieldName) throws FieldNotFoundException; /** * Checks if the field with the given name is present in this Record object. */ boolean hasField(QName fieldName); /** * Returns the map of fields in this Record. * * <p>Important: changing this map or the values contained within them will alter the content of this Record. */ Map<QName, Object> getFields(); /** * Adds the given fields to the list of fields to delete. */ void addFieldsToDelete(List<QName> fieldNames); /** * Removes the given fields to the list of fields to delete. */ void removeFieldsToDelete(List<QName> fieldNames); /** * Gets the lists of fields to delete. Modifying the returned list modifies the state of this Record object. * * <p>This list will be used by {@link Repository#update(Record)} to delete fields. * * <p>If a record contains the field both as a normal field and in the list of fields to delete, updating the record * will result in the field to be deleted rather than updated. So, deleting a field takes preference over updating a field. */ List<QName> getFieldsToDelete(); /** * After performing a create or update, gives some information as to whether a record was created, updated, * or unchanged. Especially useful when using {@link Repository#createOrUpdate}. * * <p>This property is output-only (= assigned by the repository, ignored on input) and ephemeral (not stored). */ ResponseStatus getResponseStatus(); void setResponseStatus(ResponseStatus status); /** * Creates a clone of the record object * <p> * A new record object is created with the same id, version, recordTypes, * fields and deleteFields. * <p> * Of the fields that are not of an immutable type, a deep copy is * performed. This includes List, HierarchyPath, Blob and Record. * <p> * The response status is not copied into the new Record object. * * @throws RuntimeException * when a record is nested in itself */ Record clone() throws RuntimeException; /** * Creates a clone of the record object * <p> * A new record object is created with the same id, version, recordTypes, * fields and deleteFields. The response status is not copied into the * new Record object. * <p> * Of the fields that are not of an immutable type, a deep copy is * performed. This includes List, HierarchyPath, Blob and Record. * <p> * When checking if the record is nested in itself, it is also checked if * the record is contained in the map of parentRecords * * @param parentRecords * a stack of parent records of the record used to check if the record * is nested in itself or one of its parents. * @throws RecordException * when the record is contained in the parentRecords map or if * it is nested in itself. */ Record cloneRecord(IdentityRecordStack parentRecords) throws RecordException; /** * Shortcut method for {@link #cloneRecord(IdentityRecordStack)} with * parentRecords set to an empty stack */ Record cloneRecord() throws RecordException; boolean equals(Object obj); /** * Compares the two records, ignoring some aspects. This method is intended to compare * a record loaded from the repository with a newly instantiated one in which some things * are typically not supplied. * * <p>The aspects which are ignored are: * <ul> * <li>the version * <li>the record types: only the name of the non-versioned record type is compared, if * it is not null in both. The version of the record type is not compared. * </ul> */ boolean softEquals(Object obj); /** * Sets a default namespace on a record object. * <p> * The default namespace is used to resovle a QName when only the name part is given, * like for instance in {@link #getField(String)}. </br> * When resolving a name into a QName, it is first checked if the default namespace is set. * If not, the namespace of the recordType is used. If that is not set either, * a RecordException is thrown. </br> * The resolving happens at the moment a related method is called. * This means that if the default namespace or record type is changed afterwards, this has * no influence on the already resolved QNames.</br> * <p> * The default namespace is only used for resolving names into QNames. It is not sent to * the server, nor stored on the repository. * * @param namespace the default namespace to use */ void setDefaultNamespace(String namespace); /** * Resolves the QName of the record type and sets it on the record. * <p> * @see {@link #setDefaultNamespace(String)} * @see {@link #setRecordType(QName)} * @param recordTypeName the name part of the record type to set * @throws RecordException when the QName cannot be resolved */ void setRecordType(String recordTypeName) throws RecordException; /** * Resolves the QName of the record type and sets it on the record. * <p> * @see {@link #setDefaultNamespace(String)} * @see {@link #setRecordType(QName, Long)} * @param recordTypeName the name part of the record type to set * @throws RecordException when the QName cannot be resolved */ void setRecordType(String recordTypeName, Long version) throws RecordException; /** * Resolves the QName of the record type and sets it on the record. * <p> * @see {@link #setDefaultNamespace(String)} * @see {@link #setRecordType(Scope, QName, Long)} * @param recordTypeName the name part of the record type to set * @throws RecordException when the QName cannot be resolved */ void setRecordType(Scope scope, String recordTypeName, Long version) throws RecordException; /** * Resolves the QName of the field and sets it on the record. * <p> * @see {@link #setDefaultNamespace(String)} * @see {@link #setField(QName)} * @param fieldName the name part of the field to set * @param value the value to set on the field * @throws RecordException when the QName cannot be resolved */ void setField(String fieldName, Object value) throws RecordException; /** * Resolves the QName of the field and gets it from the record. * <p> * @see {@link #setDefaultNamespace(String)} * @see {@link #getField(QName)} * @param fieldName the name part of the field to get * @return the value of the field * @throws FieldNotFoundException if the field does not exist on the record * @throws RecordException when the QName cannot be resolved */ <T> T getField(String fieldName) throws FieldNotFoundException, RecordException; /** * Resolves the QName of the field and deletes it from the record. * * @see {@link #setDefaultNamespace(String)} * @see {@link #delete(QName, boolean)} * @param fieldName the name part of the field to delete * @param addToFieldsToDelete true if the field needs to be deleted from the record in the repository as well * @throws RecordException when the QName cannot be resolved */ void delete(String fieldName, boolean addToFieldsToDelete) throws RecordException; /** * Resolves the QName of the field and checks if it exists. * * @see {@link #setDefaultNamespace(String)} * @see {@link #hasField(QName)} * @param fieldName the name part of the field to check * @return true if the field exists on the record * @throws RecordException when the QName cannot be resolved */ boolean hasField(String fieldName) throws RecordException; /** * Gets the transient set of attributes associated with this record. The returned map is never null * and can be modified. For more details on transient attributes see {@link #setAttributes(Map)}. */ Map<String, String> getAttributes(); /** * Returns true if attributes are set in this record object. This method is cheaper than * calling {@link #getAttributes()} when the record has no attributes. See {@link #setAttributes(Map)}. */ boolean hasAttributes(); /** * Set the transient attributes associated with this record. * * <p>These transient attributes are not persisted in the repository, thus they do not behave like fields. * As such, they are not returned when doing a {@link Repository#read}. Instead, the attributes are * related to a particular create/update/delete operation.</p> * * <p>The purpose of these attributes is to be able to pass along data/hints to certain * components, such as server-side repository decorators, secondary actions, message queue listeners * and the like. The attributes are stored as part of the MQ payload of a repository mutation event.</p> * * <p>The attributes are not preserved when cloning the record, or on a round-trip from the repository, * thus in the record object returned from create, update, etc.</p> */ void setAttributes(Map<String, String> attributes); /** * Returns the {@link Metadata} object for the specified field. * * <p>For more information on metadata, see {@link #setMetadata(QName, Metadata)}.</p> * * @return null if there is no metadata for the field */ Metadata getMetadata(QName fieldName); /** * Sets the metadata for some field. * * <p>Metadata is a set of schema-free key-values associated with a field value. It can be used for metadata * about the value itself, for example to describe its source, creator or quality. Such information could * also be modelled within the Lily schema (for example using complex field types), but this could make the * schema very complex.</p> * * <p>If you set metadata for a field which does not exist, it will be ignored.</p> * * <p>Similar to record fields, metadata can be partially updated: you only need to specify the metadata keys you * want to change, the other metadata will be inherited from the current record state. Therefore, deleting * metadata needs to be done explicitly through {@link MetadataBuilder#delete(String)}</p>. * * <p>For versioned fields, changing only the metadata (without changing the field value itself) will also * cause a new version to be created.</p> * * <p>Metadata is currently not supported for versioned-mutable fields.</p> */ void setMetadata(QName fieldName, Metadata metadata); /** * Returns the metadata for all fields. * * <p>The returned map can be manipulated, except if it is empty, because in that case a * Collections.emptyCollection() is returned.</p> */ Map<QName, Metadata> getMetadataMap(); }